-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from pacu/swift-trusted-dealer
Swift trusted dealer and coordinator for swift
- Loading branch information
Showing
23 changed files
with
1,632 additions
and
884 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# By default, SwiftLint uses a set of sensible default rules you can adjust: | ||
disabled_rules: # rule identifiers turned on by default to exclude from running | ||
- colon | ||
- comma | ||
- control_statement | ||
opt_in_rules: # some rules are turned off by default, so you need to opt-in | ||
- empty_count # find all the available rules by running: `swiftlint rules` | ||
|
||
# Alternatively, specify all rules explicitly by uncommenting this option: | ||
# only_rules: # delete `disabled_rules` & `opt_in_rules` if using this | ||
# - empty_parameters | ||
# - vertical_whitespace | ||
|
||
analyzer_rules: # rules run by `swiftlint analyze` | ||
- explicit_self | ||
|
||
# Case-sensitive paths to include during linting. Directory paths supplied on the | ||
# command line will be ignored. | ||
included: | ||
- FrostSwift/Sources/FrostSwift | ||
excluded: # case-sensitive paths to ignore during linting. Takes precedence over `included` | ||
- Carthage | ||
- Pods | ||
- .build | ||
- Sources/FrostSwiftFFI | ||
|
||
# If true, SwiftLint will not fail if no lintable files are found. | ||
allow_zero_lintable_files: false | ||
|
||
# If true, SwiftLint will treat all warnings as errors. | ||
strict: false | ||
|
||
# The path to a baseline file, which will be used to filter out detected violations. | ||
baseline: Baseline.json | ||
|
||
# The path to save detected violations to as a new baseline. | ||
write_baseline: Baseline.json | ||
|
||
# configurable rules can be customized from this configuration file | ||
# binary rules can set their severity level | ||
force_cast: warning # implicitly | ||
force_try: | ||
severity: warning # explicitly | ||
# rules that have both warning and error levels, can set just the warning level | ||
# implicitly | ||
line_length: 110 | ||
# they can set both implicitly with an array | ||
type_body_length: | ||
- 300 # warning | ||
- 400 # error | ||
# or they can set both explicitly | ||
file_length: | ||
warning: 500 | ||
error: 1200 | ||
# naming rules can set warnings/errors for min_length and max_length | ||
# additionally they can set excluded names | ||
type_name: | ||
min_length: 4 # only warning | ||
max_length: # warning and error | ||
warning: 50 | ||
error: 50 | ||
excluded: iPhone # excluded via string | ||
allowed_symbols: ["_"] # these are allowed in type names | ||
identifier_name: | ||
min_length: # only min_length | ||
error: 3 # only error | ||
excluded: # excluded via string array | ||
- i | ||
- id | ||
- URL | ||
- GlobalAPIKey | ||
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging, summary) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
// | ||
// Coordinator.swift | ||
// | ||
// | ||
// Created by Pacu on 27-06-2024. | ||
// | ||
|
||
import Foundation | ||
import FrostSwiftFFI | ||
|
||
enum FROSTCoordinatorError: Error { | ||
case repeatedCommitmentFromIdentifier(Identifier) | ||
case repeatedSignatureShareFromIdentifier(Identifier) | ||
case incorrectNumberOfCommitments(min: UInt16, max: UInt16, found: UInt16) | ||
case signingPackageAlreadyCreated | ||
case signingPackageMissing | ||
// mapped from FFI | ||
case failedToCreateSigningPackage | ||
case invalidSigningCommitment | ||
case identifierDeserializationError | ||
case signingPackageSerializationError | ||
case signatureShareDeserializationError | ||
case publicKeyPackageDeserializationError | ||
case signatureShareAggregationFailed(message: String) | ||
case invalidRandomizer | ||
|
||
/// some error we don't have mapped | ||
case otherError(CoordinationError) | ||
|
||
init(coordinationError: CoordinationError) { | ||
switch coordinationError { | ||
case .FailedToCreateSigningPackage: | ||
self = .failedToCreateSigningPackage | ||
case .InvalidSigningCommitment: | ||
self = .invalidSigningCommitment | ||
case .IdentifierDeserializationError: | ||
self = .identifierDeserializationError | ||
case .SigningPackageSerializationError: | ||
self = .signingPackageSerializationError | ||
case .SignatureShareDeserializationError: | ||
self = .signatureShareDeserializationError | ||
case .PublicKeyPackageDeserializationError: | ||
self = .publicKeyPackageDeserializationError | ||
case let .SignatureShareAggregationFailed(message: message): | ||
self = .signatureShareAggregationFailed(message: message) | ||
case .InvalidRandomizer: | ||
self = .invalidRandomizer | ||
} | ||
} | ||
} | ||
|
||
/// Protocol that defines the minimum functionality of a FROST signature | ||
/// scheme Coordinator. A coordinator can be part of the signature or not. For your | ||
/// convenience, ``NonSigningCoordinator`` and ``SigningCoordinator`` | ||
/// implementations are provided. | ||
/// - Important: Fallback because of misbehaving participants are not considered | ||
/// within this protocol. Implementors must define their own strategies to deal with such | ||
/// scenarios. | ||
/// - Note: This protocol is bound to actors because it is assumed that several | ||
/// participants may try to interact with it concurrently. | ||
public protocol FROSTCoordinator: Actor { | ||
/// configuration of the FROST threshold scheme | ||
var configuration: Configuration { get } | ||
/// public key tha represent this signature scheme | ||
var publicKeyPackage: PublicKeyPackage { get } | ||
/// message to be signed by the _t_ participants | ||
var message: Message { get } | ||
/// this is the configuration of Round 2. | ||
var round2Config: Round2Configuration? { get } | ||
/// receives a signing commitment from a given participant | ||
/// - parameter commitment: the SigningCommitment from signature participant | ||
/// - throws: should throw ``FROSTCoordinatorError.alreadyReceivedCommitmentFromIdentifier`` when | ||
/// receiving a commitment from an Identifier that was received already independently from whether | ||
/// it is the same or a different one. | ||
func receive(commitment: SigningCommitments) async throws | ||
/// Creates a ``Round2Configuration`` struct with a ``SigningPackage`` and | ||
/// ``Randomizer`` for the case that Re-Randomized FROST is used. | ||
/// - throws: ``FROSTCoordinatorError.signingPackageAlreadyCreated`` if this function was | ||
/// called already or ``FROSTCoordinatorError.incorrectNumberOfCommitments`` | ||
/// when the number of commitments gathered is less or more than the specified by the ``Configuration`` | ||
func createSigningPackage() async throws -> Round2Configuration | ||
/// Receives a ``SignatureShare`` from a ``SigningParticipant``. | ||
/// - throws: ``FROSTCoordinatorError.alreadyReceivedSignatureShareFromIdentifier`` | ||
/// when the same participant sends the same share repeatedly | ||
func receive(signatureShare: SignatureShare) async throws | ||
/// Aggregates all shares and creates the FROST ``Signature`` | ||
/// - throws: several things can go wrong here. 🥶 | ||
func aggregate() async throws -> Signature | ||
/// Verify a ``Signature`` with the ``PublicKeyPackage`` of this coordinator. | ||
func verify(signature: Signature) async throws | ||
} | ||
|
||
/// A signature scheme coordinator that does not participate in the signature scheme | ||
public actor NonSigningCoordinator: FROSTCoordinator { | ||
public let configuration: Configuration | ||
public let publicKeyPackage: PublicKeyPackage | ||
public let message: Message | ||
public var round2Config: Round2Configuration? | ||
var commitments: [Identifier: SigningCommitments] = [:] | ||
var signatureShares: [Identifier: SignatureShare] = [:] | ||
|
||
public init(configuration: Configuration, publicKeyPackage: PublicKeyPackage, message: Message) throws { | ||
self.configuration = configuration | ||
self.publicKeyPackage = publicKeyPackage | ||
self.message = message | ||
} | ||
|
||
public func receive(commitment: SigningCommitments) throws { | ||
// TODO: validate that the commitment belongs to a known identifier | ||
guard commitments[commitment.identifier] == nil else { | ||
throw FROSTCoordinatorError.repeatedCommitmentFromIdentifier(commitment.identifier) | ||
} | ||
|
||
commitments[commitment.identifier] = commitment | ||
} | ||
|
||
public func createSigningPackage() throws -> Round2Configuration { | ||
guard round2Config?.signingPackage == nil else { | ||
throw FROSTCoordinatorError.signingPackageAlreadyCreated | ||
} | ||
|
||
try validateNumberOfCommitments() | ||
|
||
let package = try SigningPackage( | ||
package: newSigningPackage( | ||
message: message, | ||
commitments: commitments.values.map { $0.commitment } | ||
) | ||
) | ||
|
||
let randomizedParams = try RandomizedParams( | ||
publicKey: publicKeyPackage, | ||
signingPackage: package | ||
) | ||
|
||
let randomizer = try randomizedParams.randomizer() | ||
|
||
let config = Round2Configuration(signingPackage: package, randomizer: randomizer) | ||
round2Config = config | ||
|
||
return config | ||
} | ||
|
||
/// receives the signature share from a partipicant | ||
public func receive(signatureShare: SignatureShare) throws { | ||
// TODO: validate that the commitment belongs to a known identifier | ||
guard signatureShares[signatureShare.identifier] == nil else { | ||
throw FROSTCoordinatorError.repeatedSignatureShareFromIdentifier(signatureShare.identifier) | ||
} | ||
|
||
signatureShares[signatureShare.identifier] = signatureShare | ||
} | ||
|
||
public func aggregate() throws -> Signature { | ||
try validateNumberOfCommitments() | ||
let round2config = try round2ConfigPresent() | ||
|
||
guard let randomizer = round2config.randomizer?.randomizer else { | ||
throw FROSTCoordinatorError.invalidRandomizer | ||
} | ||
|
||
let signature = try FrostSwiftFFI.aggregate( | ||
signingPackage: round2config.signingPackage.package, | ||
signatureShares: signatureShares.values.map { $0.share }, | ||
pubkeyPackage: publicKeyPackage.package, | ||
randomizer: randomizer | ||
) | ||
|
||
return Signature(signature: signature) | ||
} | ||
|
||
public func verify(signature _: Signature) throws { | ||
throw FrostError.invalidSignature | ||
} | ||
|
||
func round2ConfigPresent() throws -> Round2Configuration { | ||
guard let config = round2Config else { | ||
throw FROSTCoordinatorError.signingPackageMissing | ||
} | ||
|
||
return config | ||
} | ||
|
||
func validateNumberOfCommitments() throws { | ||
guard commitments.count >= configuration.minSigners && | ||
commitments.count <= configuration.maxSigners | ||
else { | ||
throw FROSTCoordinatorError.incorrectNumberOfCommitments( | ||
min: configuration.minSigners, | ||
max: configuration.maxSigners, | ||
found: UInt16(commitments.count) | ||
) | ||
} | ||
} | ||
} | ||
|
||
/// A Coordinator that also participates in the signature production. | ||
public actor SigningCoordinator: FROSTCoordinator { | ||
public let configuration: Configuration | ||
public let publicKeyPackage: PublicKeyPackage | ||
public let message: Message | ||
public var round2Config: Round2Configuration? | ||
let nonSigningCoordinator: NonSigningCoordinator | ||
let signingParticipant: SigningParticipant | ||
|
||
public init( | ||
configuration: Configuration, | ||
publicKeyPackage: PublicKeyPackage, | ||
signingParticipant: SigningParticipant, | ||
message: Message | ||
) throws { | ||
self.configuration = configuration | ||
self.publicKeyPackage = publicKeyPackage | ||
self.message = message | ||
nonSigningCoordinator = try NonSigningCoordinator( | ||
configuration: configuration, | ||
publicKeyPackage: publicKeyPackage, | ||
message: message | ||
) | ||
self.signingParticipant = signingParticipant | ||
} | ||
|
||
public init(configuration: Configuration, publicKeyPackage: PublicKeyPackage, keyPackage: KeyPackage, message: Message) throws { | ||
let signingParticipant = SigningParticipant( | ||
keyPackage: keyPackage, | ||
publicKey: publicKeyPackage | ||
) | ||
|
||
try self.init( | ||
configuration: configuration, | ||
publicKeyPackage: publicKeyPackage, | ||
signingParticipant: signingParticipant, | ||
message: message | ||
) | ||
} | ||
|
||
public func receive(commitment: SigningCommitments) async throws { | ||
try await nonSigningCoordinator.receive(commitment: commitment) | ||
} | ||
|
||
public func createSigningPackage() async throws -> Round2Configuration { | ||
// sends its own commitment before creating the signign package | ||
let commitment = try signingParticipant.commit() | ||
try await nonSigningCoordinator.receive(commitment: commitment) | ||
|
||
// create the signing package | ||
let round2Config = try await nonSigningCoordinator.createSigningPackage() | ||
self.round2Config = round2Config | ||
return round2Config | ||
} | ||
|
||
public func receive(signatureShare: SignatureShare) async throws { | ||
try await nonSigningCoordinator.receive(signatureShare: signatureShare) | ||
} | ||
|
||
public func aggregate() async throws -> Signature { | ||
let round2Config = try await nonSigningCoordinator.round2ConfigPresent() | ||
// create own signature share | ||
signingParticipant.receive(round2Config: round2Config) | ||
let signatureShare = try signingParticipant.sign() | ||
|
||
// sends its own share before creating the signature | ||
try await nonSigningCoordinator.receive(signatureShare: signatureShare) | ||
|
||
// produce signature by aggregating all shares. | ||
let signature = try await nonSigningCoordinator.aggregate() | ||
|
||
return signature | ||
} | ||
|
||
public func verify(signature: Signature) async throws { | ||
try await nonSigningCoordinator.verify(signature: signature) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// Error.swift | ||
// | ||
// | ||
// Created by Pacu in 2024. | ||
// | ||
|
||
import Foundation | ||
import FrostSwiftFFI | ||
|
||
public enum FrostError: Error { | ||
case invalidConfiguration | ||
case invalidSignature | ||
case malformedIdentifier | ||
case otherError(FrostSwiftFFI.FrostError) | ||
} |
Oops, something went wrong.