diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index 4d226304..1a8a8c6e 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -162,13 +162,13 @@ pub fn bench_sign( }, ); - let mut signature_shares = Vec::new(); + let mut signature_shares = HashMap::new(); for participant_identifier in nonces.keys() { let key_package = key_packages.get(participant_identifier).unwrap(); let nonces_to_use = &nonces.get(participant_identifier).unwrap(); let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package).unwrap(); - signature_shares.push(signature_share); + signature_shares.insert(*key_package.identifier(), signature_share); } group.bench_with_input( @@ -176,7 +176,7 @@ pub fn bench_sign( &(signing_package.clone(), signature_shares.clone(), pubkeys), |b, (signing_package, signature_shares, pubkeys)| { b.iter(|| { - frost::aggregate(signing_package, &signature_shares[..], pubkeys).unwrap(); + frost::aggregate(signing_package, &signature_shares, pubkeys).unwrap(); }) }, ); diff --git a/frost-core/src/frost.rs b/frost-core/src/frost.rs index 1868d2c9..4c039c08 100644 --- a/frost-core/src/frost.rs +++ b/frost-core/src/frost.rs @@ -11,7 +11,7 @@ //! Sharing, where shares are generated using Shamir Secret Sharing. use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, fmt::{self, Debug}, ops::Index, }; @@ -354,6 +354,12 @@ where /// Aggregates the signature shares to produce a final signature that /// can be verified with the group public key. /// +/// `signature_shares` maps the identifier of each participant to the +/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping +/// the coordinator has between communication channels and participants, i.e. +/// they must have assurance that the [`round2::SignatureShare`] came from +/// the participant with that identifier. +/// /// This operation is performed by a coordinator that can communicate with all /// the signing participants before publishing the final signature. The /// coordinator can be one of the participants or a semi-trusted third party @@ -365,7 +371,7 @@ where /// service attack due to publishing an invalid signature. pub fn aggregate( signing_package: &SigningPackage, - signature_shares: &[round2::SignatureShare], + signature_shares: &HashMap, round2::SignatureShare>, pubkeys: &keys::PublicKeyPackage, ) -> Result, Error> where @@ -387,7 +393,7 @@ where // [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-5.3 let mut z = <::Field>::zero(); - for signature_share in signature_shares { + for signature_share in signature_shares.values() { z = z + signature_share.signature.z_share; } @@ -413,27 +419,33 @@ where ); // Verify the signature shares. - for signature_share in signature_shares { + for (signature_share_identifier, signature_share) in signature_shares { // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. let signer_pubkey = pubkeys .signer_pubkeys - .get(&signature_share.identifier) + .get(&signature_share_identifier) .unwrap(); // Compute Lagrange coefficient. let lambda_i = - derive_interpolating_value(&signature_share.identifier, signing_package)?; + derive_interpolating_value(&signature_share_identifier, signing_package)?; - let binding_factor = binding_factor_list[signature_share.identifier].clone(); + let binding_factor = binding_factor_list[*signature_share_identifier].clone(); // Compute the commitment share. let R_share = signing_package - .signing_commitment(&signature_share.identifier) + .signing_commitment(&signature_share_identifier) .to_group_commitment_share(&binding_factor); // Compute relation values to verify this signature share. - signature_share.verify(&R_share, signer_pubkey, lambda_i, &challenge)?; + signature_share.verify( + *signature_share_identifier, + &R_share, + signer_pubkey, + lambda_i, + &challenge, + )?; } // We should never reach here; but we return the verification error to be safe. diff --git a/frost-core/src/frost/round2.rs b/frost-core/src/frost/round2.rs index 01a6bdd9..41733fa6 100644 --- a/frost-core/src/frost/round2.rs +++ b/frost-core/src/frost/round2.rs @@ -91,8 +91,6 @@ where #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SignatureShare { - /// Represents the participant identifier. - pub(crate) identifier: Identifier, /// This participant's signature over the message. pub(crate) signature: SignatureResponse, /// Ciphersuite ID for serialization @@ -112,9 +110,8 @@ where C: Ciphersuite, { /// Create a new [`SignatureShare`]. - pub fn new(identifier: Identifier, signature: SignatureResponse) -> Self { + pub fn new(signature: SignatureResponse) -> Self { Self { - identifier, signature, ciphersuite: (), } @@ -126,8 +123,9 @@ where /// This is the final step of [`verify_signature_share`] from the spec. /// /// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#name-signature-share-verificatio - pub fn verify( + pub(crate) fn verify( &self, + identifier: Identifier, group_commitment_share: &round1::GroupCommitmentShare, public_key: &frost::keys::VerifyingShare, lambda_i: Scalar, @@ -136,9 +134,7 @@ where if (::generator() * self.signature.z_share) != (group_commitment_share.0 + (public_key.0 * challenge.0 * lambda_i)) { - return Err(Error::InvalidSignatureShare { - signer: self.identifier, - }); + return Err(Error::InvalidSignatureShare { signer: identifier }); } Ok(()) @@ -151,7 +147,6 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("SignatureShare") - .field("identifier", &self.identifier) .field("signature", &self.signature) .finish() } @@ -171,7 +166,6 @@ fn compute_signature_share( + (lambda_i * key_package.secret_share.0 * challenge.0); SignatureShare:: { - identifier: *key_package.identifier(), signature: SignatureResponse:: { z_share }, ciphersuite: (), } diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index d1eddec4..90c84cc1 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -113,7 +113,7 @@ fn check_sign( // This is what the signature aggregator / coordinator needs to do: // - decide what message to sign // - take one (unused) commitment per signing participant - let mut signature_shares = Vec::new(); + let mut signature_shares = HashMap::new(); let message = "message to sign".as_bytes(); let signing_package = frost::SigningPackage::new(commitments_map, message); @@ -129,7 +129,7 @@ fn check_sign( // Each participant generates their signature share. let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package).unwrap(); - signature_shares.push(signature_share); + signature_shares.insert(*participant_identifier, signature_share); } //////////////////////////////////////////////////////////////////////////// @@ -139,7 +139,7 @@ fn check_sign( // Aggregate (also verifies the signature shares) let group_signature = - frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package).unwrap(); + frost::aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap(); // Check that the threshold signature can be verified by the group public // key (the verification key). diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index 9678f46e..5ef0e3de 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -147,12 +147,9 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors::new( - u16::from_str(i).unwrap().try_into().unwrap(), - SignatureResponse { - z_share: <::Field>::deserialize(&sig_share).unwrap(), - }, - ); + let signature_share = SignatureShare::::new(SignatureResponse { + z_share: <::Field>::deserialize(&sig_share).unwrap(), + }); signature_shares.insert( u16::from_str(i).unwrap().try_into().unwrap(), @@ -285,7 +282,7 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { assert_eq!(*binding_factor, binding_factors[identifier]); } - let mut our_signature_shares: Vec> = Vec::new(); + let mut our_signature_shares = HashMap::new(); // Each participant generates their signature share for identifier in signer_nonces.keys() { @@ -295,12 +292,10 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { // Each participant generates their signature share. let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap(); - our_signature_shares.push(signature_share); + our_signature_shares.insert(*identifier, signature_share); } - for sig_share in our_signature_shares.clone() { - assert_eq!(sig_share, signature_shares[sig_share.identifier()]); - } + assert_eq!(our_signature_shares, signature_shares); let signer_pubkeys = key_packages .into_iter() @@ -315,14 +310,8 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { //////////////////////////////////////////////////////////////////////////// // Aggregate the FROST signature from test vector sig shares - let group_signature_result = frost::aggregate( - &signing_package, - &signature_shares - .values() - .cloned() - .collect::>>(), - &pubkey_package, - ); + let group_signature_result = + frost::aggregate(&signing_package, &signature_shares, &pubkey_package); // Check that the aggregation passed signature share verification and generation assert!(group_signature_result.is_ok()); diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index 1c625542..3d92ea64 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -62,7 +62,7 @@ for participant_index in 1..(min_signers as u16 + 1) { // This is what the signature aggregator / coordinator needs to do: // - decide what message to sign // - take one (unused) commitment per signing participant -let mut signature_shares = Vec::new(); +let mut signature_shares = HashMap::new(); # // ANCHOR: round2_package let message = "message to sign".as_bytes(); # // In practice, the SigningPackage must be sent to all participants @@ -88,7 +88,7 @@ for participant_identifier in nonces_map.keys() { // In practice, the signature share must be sent to the Coordinator // using an authenticated channel. - signature_shares.push(signature_share); + signature_shares.insert(*participant_identifier, signature_share); } //////////////////////////////////////////////////////////////////////////// @@ -98,7 +98,7 @@ for participant_identifier in nonces_map.keys() { // Aggregate (also verifies the signature shares) # // ANCHOR: aggregate -let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package)?; +let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; # // ANCHOR_END: aggregate diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index baca089d..cdfcebea 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -2,6 +2,8 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] +use std::collections::HashMap; + use curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT, ristretto::{CompressedRistretto, RistrettoPoint}, @@ -316,10 +318,7 @@ pub mod round1 { /// /// Generates the signing nonces and commitments to be used in the signing /// operation. - pub fn commit( - secret: &SigningShare, - rng: &mut RNG, - ) -> (SigningNonces, SigningCommitments) + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where RNG: CryptoRng + RngCore, { @@ -379,7 +378,7 @@ pub type Signature = frost_core::Signature; /// service attack due to publishing an invalid signature. pub fn aggregate( signing_package: &SigningPackage, - signature_shares: &[round2::SignatureShare], + signature_shares: &HashMap, pubkeys: &keys::PublicKeyPackage, ) -> Result { frost::aggregate(signing_package, signature_shares, pubkeys) diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index 5f6bd0bc..67d9f3c3 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -53,11 +53,10 @@ pub fn signing_package() -> SigningPackage { /// Generate a sample SignatureShare. pub fn signature_share() -> SignatureShare { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let signature_response = SignatureResponse::from_bytes(serialized_scalar).unwrap(); - SignatureShare::new(identifier, signature_response) + SignatureShare::new(signature_response) } /// Generate a sample SecretShare. diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index 6edc93d4..a1ee790c 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -42,10 +42,9 @@ fn check_signing_package_recreation() { fn check_signature_share_recreation() { let signature_share = samples::signature_share(); - let identifier = signature_share.identifier(); let signature_response = signature_share.signature(); - let new_signature_share = SignatureShare::new(*identifier, *signature_response); + let new_signature_share = SignatureShare::new(*signature_response); assert!(signature_share == new_signature_share); } diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs index afb42579..657e0eb8 100644 --- a/frost-ristretto255/tests/serde_tests.rs +++ b/frost-ristretto255/tests/serde_tests.rs @@ -169,7 +169,6 @@ fn check_signature_share_serialization() { assert!(signature_share == decoded_signature_share); let json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "signature": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; @@ -179,17 +178,8 @@ fn check_signature_share_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "signature": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", - "ciphersuite": "FROST(ristretto255, SHA-512)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "foo": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; @@ -197,14 +187,12 @@ fn check_signature_share_serialization() { // Missing field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000",, "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "signature": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "extra": 1, "ciphersuite": "FROST(ristretto255, SHA-512)"