diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index fa740a6..69905f2 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -6,6 +6,22 @@ Entries are listed in reverse chronological order. ## 0.6.0 +* The following structs had a `Identifier` field removed, which affects + how they are encoded and instantiated: + * `dkg::round1::Package` + * `dkg::round2::Package` + * `SigningCommitments` + * `SignatureShare` +* The following functions and methods changed parameters from `Vec` to `HashMap` + so that callers need to indicate the identifier of the source of each + value being passed: + * `aggregate()` + * `dkg::part2()` + * `dkg::part3()` + * `SigningPackage::new()` +* `commit()` and `preprocess()` no longer take an identifier as input +* `SignatureResponse` was removed. `SignatureShare` can now be encoded directly with + `from/to_bytes()`. * rename all `to_bytes()`/`from_bytes()` to `serialize()`/`deserialize()` ## Released diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index 24bbf59..d91b9f4 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -1,6 +1,6 @@ //! Ciphersuite-generic benchmark functions. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use criterion::{BenchmarkId, Criterion, Throughput}; use rand_core::{CryptoRng, RngCore}; @@ -126,7 +126,6 @@ pub fn bench_sign( b.iter(|| { let participant_identifier = 1u16.try_into().expect("should be nonzero"); frost::round1::commit( - participant_identifier, key_packages .get(&participant_identifier) .unwrap() @@ -138,12 +137,11 @@ pub fn bench_sign( ); let mut nonces: HashMap<_, _> = HashMap::new(); - let mut commitments: HashMap<_, _> = HashMap::new(); + let mut commitments: BTreeMap<_, _> = BTreeMap::new(); for participant_index in 1..=min_signers { let participant_identifier = participant_index.try_into().expect("should be nonzero"); let (nonce, commitment) = frost::round1::commit( - participant_identifier, key_packages .get(&participant_identifier) .unwrap() @@ -155,8 +153,7 @@ pub fn bench_sign( } let message = "message to sign".as_bytes(); - let comms = commitments.clone().into_values().collect(); - let signing_package = frost::SigningPackage::new(comms, message); + let signing_package = frost::SigningPackage::new(commitments, message); group.bench_with_input( BenchmarkId::new("Round 2", min_signers), @@ -175,13 +172,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( @@ -189,7 +186,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 d47e55d..7bc3d8a 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, }; @@ -162,14 +162,14 @@ fn derive_interpolating_value( // Ala the sorting of B, just always sort by identifier in ascending order // // https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding - for commitment in signing_package.signing_commitments().values() { - if commitment.identifier == *signer_id { + for commitment_identifier in signing_package.signing_commitments().keys() { + if *commitment_identifier == *signer_id { continue; } - num *= commitment.identifier; + num *= *commitment_identifier; - den *= commitment.identifier - *signer_id; + den *= *commitment_identifier - *signer_id; } if den == zero { @@ -189,7 +189,7 @@ fn derive_interpolating_value( #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SigningPackage { /// The set of commitments participants published in the first round of the - /// protocol, ordered by their identifiers. + /// protocol. signing_commitments: BTreeMap, round1::SigningCommitments>, /// Message which each participant will sign. /// @@ -224,14 +224,11 @@ where /// /// The `signing_commitments` are sorted by participant `identifier`. pub fn new( - signing_commitments: Vec>, + signing_commitments: BTreeMap, round1::SigningCommitments>, message: &[u8], ) -> SigningPackage { SigningPackage { - signing_commitments: signing_commitments - .into_iter() - .map(|s| (s.identifier, s)) - .collect(), + signing_commitments, message: message.to_vec(), ciphersuite: (), } @@ -261,13 +258,13 @@ where binding_factor_input_prefix.extend_from_slice(additional_prefix); self.signing_commitments() - .values() - .map(|c| { + .keys() + .map(|identifier| { let mut binding_factor_input = vec![]; binding_factor_input.extend_from_slice(&binding_factor_input_prefix); - binding_factor_input.extend_from_slice(c.identifier.serialize().as_ref()); - (c.identifier, binding_factor_input) + binding_factor_input.extend_from_slice(identifier.serialize().as_ref()); + (*identifier, binding_factor_input) }) .collect() } @@ -317,14 +314,14 @@ where // Ala the sorting of B, just always sort by identifier in ascending order // // https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding - for commitment in signing_package.signing_commitments().values() { + for (commitment_identifier, commitment) in signing_package.signing_commitments() { // The following check prevents a party from accidentally revealing their share. // Note that the '&&' operator would be sufficient. if identity == commitment.binding.0 || identity == commitment.hiding.0 { return Err(Error::IdentityCommitment); } - let binding_factor = binding_factor_list[commitment.identifier].clone(); + let binding_factor = binding_factor_list[*commitment_identifier].clone(); // Collect the binding commitments and their binding factors for one big // multiscalar multiplication at the end. @@ -349,6 +346,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 @@ -360,7 +363,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 @@ -382,8 +385,8 @@ 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 { - z = z + signature_share.signature.z_share; + for signature_share in signature_shares.values() { + z = z + signature_share.share; } let signature = Signature { @@ -408,27 +411,32 @@ 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)?; + let lambda_i = 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/identifier.rs b/frost-core/src/frost/identifier.rs index 89d323b..8febb05 100644 --- a/frost-core/src/frost/identifier.rs +++ b/frost-core/src/frost/identifier.rs @@ -93,7 +93,9 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Identifier") - .field(&<::Field>::serialize(&self.0).as_ref()) + .field(&hex::encode( + <::Field>::serialize(&self.0).as_ref(), + )) .finish() } } diff --git a/frost-core/src/frost/keys/dkg.rs b/frost-core/src/frost/keys/dkg.rs index 34372fb..6b6a0a2 100644 --- a/frost-core/src/frost/keys/dkg.rs +++ b/frost-core/src/frost/keys/dkg.rs @@ -58,8 +58,6 @@ pub mod round1 { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Package { - /// The identifier of the participant who is sending the package (i). - pub(crate) sender_identifier: Identifier, /// The public commitment from the participant (C_i) pub(crate) commitment: VerifiableSecretSharingCommitment, /// The proof of knowledge of the temporary secret (σ_i = (R_i, μ_i)) @@ -83,12 +81,10 @@ pub mod round1 { { /// Create a new [`Package`] instance. pub fn new( - sender_identifier: Identifier, commitment: VerifiableSecretSharingCommitment, proof_of_knowledge: Signature, ) -> Self { Self { - sender_identifier, commitment, proof_of_knowledge, ciphersuite: (), @@ -159,10 +155,6 @@ pub mod round2 { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Package { - /// The identifier of the participant that generated the package (i). - pub(crate) sender_identifier: Identifier, - /// The identifier of the participant what will receive the package (ℓ). - pub(crate) receiver_identifier: Identifier, /// The secret share being sent. pub(crate) secret_share: SigningShare, /// Ciphersuite ID for serialization @@ -183,14 +175,8 @@ pub mod round2 { C: Ciphersuite, { /// Create a new [`Package`] instance. - pub fn new( - sender_identifier: Identifier, - receiver_identifier: Identifier, - secret_share: SigningShare, - ) -> Self { + pub fn new(secret_share: SigningShare) -> Self { Self { - sender_identifier, - receiver_identifier, secret_share, ciphersuite: (), } @@ -284,7 +270,6 @@ pub fn part1( max_signers, }; let package = round1::Package { - sender_identifier: identifier, commitment, proof_of_knowledge: Signature { R: R_i, z: mu_i }, ciphersuite: (), @@ -315,21 +300,33 @@ where /// for the participant holding the given [`round1::SecretPackage`], /// given the received [`round1::Package`]s received from the other participants. /// +/// `round1_packages` maps the identifier of each participant to the +/// [`round1::Package`] 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 [`round1::Package`] came from +/// the participant with that identifier. +/// /// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the [`round2::Package`]s that -/// must be sent to other participants. +/// by the participant for the final step, and the a map of [`round2::Package`]s that +/// must be sent to each participant who has the given identifier in the map key. pub fn part2( secret_package: round1::SecretPackage, - round1_packages: &[round1::Package], -) -> Result<(round2::SecretPackage, Vec>), Error> { + round1_packages: &HashMap, round1::Package>, +) -> Result< + ( + round2::SecretPackage, + HashMap, round2::Package>, + ), + Error, +> { if round1_packages.len() != (secret_package.max_signers - 1) as usize { return Err(Error::IncorrectNumberOfPackages); } - let mut round2_packages = Vec::new(); + let mut round2_packages = HashMap::new(); - for round1_package in round1_packages { - let ell = round1_package.sender_identifier; + for (sender_identifier, round1_package) in round1_packages { + let ell = *sender_identifier; // Round 1, Step 5 // // > Upon receiving C⃗_ℓ, σ_ℓ from participants 1 ≤ ℓ ≤ n, ℓ ≠ i, participant @@ -351,12 +348,13 @@ pub fn part2( // > which they keep for themselves. let value = evaluate_polynomial(ell, &secret_package.coefficients); - round2_packages.push(round2::Package { - sender_identifier: secret_package.identifier, - receiver_identifier: ell, - secret_share: SigningShare(value), - ciphersuite: (), - }); + round2_packages.insert( + ell, + round2::Package { + secret_share: SigningShare(value), + ciphersuite: (), + }, + ); } let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients); Ok(( @@ -373,8 +371,7 @@ pub fn part2( /// Computes the verifying keys of the other participants for the third step /// of the DKG protocol. fn compute_verifying_keys( - round2_packages: &[round2::Package], - round1_packages_map: HashMap, &round1::Package>, + round1_packages: &HashMap, round1::Package>, round2_secret_package: &round2::SecretPackage, ) -> Result, VerifyingShare>, Error> { // Round 2, Step 4 @@ -385,18 +382,18 @@ fn compute_verifying_keys( // Note that in this loop, "i" refers to the other participant whose public verification share // we are computing, and not the current participant. - for i in round2_packages.iter().map(|p| p.sender_identifier) { + for i in round1_packages.keys().cloned() { let mut y_i = ::identity(); // We need to iterate through all commitment vectors, including our own, // so chain it manually - for commitments in round2_packages - .iter() - .map(|p| { + for commitment in round1_packages + .keys() + .map(|k| { // Get the commitment vector for this participant Ok::<&VerifiableSecretSharingCommitment, Error>( - &round1_packages_map - .get(&p.sender_identifier) + &round1_packages + .get(k) .ok_or(Error::PackageNotFound)? .commitment, ) @@ -404,7 +401,7 @@ fn compute_verifying_keys( // Chain our own commitment vector .chain(iter::once(Ok(&round2_secret_package.commitment))) { - y_i = y_i + evaluate_vss(commitments?, i); + y_i = y_i + evaluate_vss(commitment?, i); } let y_i = VerifyingShare(y_i); others_verifying_keys.insert(i, y_i); @@ -417,14 +414,22 @@ fn compute_verifying_keys( /// given the received [`round1::Package`]s and [`round2::Package`]s received from /// the other participants. /// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each participant to the +/// [`round2::Package`] 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::Package`] came from +/// the participant with that identifier. +/// /// It returns the [`KeyPackage`] that has the long-lived key share for the /// participant, and the [`PublicKeyPackage`]s that has public information /// about all participants; both of which are required to compute FROST /// signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, - round1_packages: &[round1::Package], - round2_packages: &[round2::Package], + round1_packages: &HashMap, round1::Package>, + round2_packages: &HashMap, round2::Package>, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { if round1_packages.len() != (round2_secret_package.max_signers - 1) as usize { return Err(Error::IncorrectNumberOfPackages); @@ -432,30 +437,26 @@ pub fn part3( if round1_packages.len() != round2_packages.len() { return Err(Error::IncorrectNumberOfPackages); } + if round1_packages + .keys() + .any(|id| !round2_packages.contains_key(id)) + { + return Err(Error::IncorrectPackage); + } let mut signing_share = <::Field>::zero(); let mut group_public = ::identity(); - let round1_packages_map: HashMap, &round1::Package> = round1_packages - .iter() - .map(|package| (package.sender_identifier, package)) - .collect(); - - for round2_package in round2_packages { - // Sanity check; was the package really meant to us? - if round2_package.receiver_identifier != round2_secret_package.identifier { - return Err(Error::IncorrectPackage); - } - + for (sender_identifier, round2_package) in round2_packages { // Round 2, Step 2 // // > Each P_i verifies their shares by calculating: // > g^{f_ℓ(i)} ≟ ∏^{t−1}_{k=0} φ^{i^k mod q}_{ℓk}, aborting if the // > check fails. - let ell = round2_package.sender_identifier; + let ell = *sender_identifier; let f_ell_i = round2_package.secret_share; - let commitment = &round1_packages_map + let commitment = &round1_packages .get(&ell) .ok_or(Error::PackageNotFound)? .commitment; @@ -501,8 +502,7 @@ pub fn part3( // // > Any participant can compute the public verification share of any other participant // > by calculating Y_i = ∏_{j=1}^n ∏_{k=0}^{t−1} φ_{jk}^{i^k mod q}. - let mut all_verifying_keys = - compute_verifying_keys(round2_packages, round1_packages_map, round2_secret_package)?; + let mut all_verifying_keys = compute_verifying_keys(round1_packages, round2_secret_package)?; // Add the participant's own public verification share for consistency all_verifying_keys.insert(round2_secret_package.identifier, verifying_key); diff --git a/frost-core/src/frost/round1.rs b/frost-core/src/frost/round1.rs index 6d271e2..0de6af0 100644 --- a/frost-core/src/frost/round1.rs +++ b/frost-core/src/frost/round1.rs @@ -248,8 +248,6 @@ where #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SigningCommitments { - /// The participant identifier. - pub(crate) identifier: Identifier, /// Commitment to the hiding [`Nonce`]. pub(crate) hiding: NonceCommitment, /// Commitment to the binding [`Nonce`]. @@ -272,13 +270,8 @@ where C: Ciphersuite, { /// Create new SigningCommitments - pub fn new( - identifier: Identifier, - hiding: NonceCommitment, - binding: NonceCommitment, - ) -> Self { + pub fn new(hiding: NonceCommitment, binding: NonceCommitment) -> Self { Self { - identifier, hiding, binding, ciphersuite: (), @@ -297,13 +290,12 @@ where } } -impl From<(Identifier, &SigningNonces)> for SigningCommitments +impl From<&SigningNonces> for SigningCommitments where C: Ciphersuite, { - fn from((identifier, nonces): (Identifier, &SigningNonces)) -> Self { + fn from(nonces: &SigningNonces) -> Self { Self { - identifier, hiding: nonces.hiding.clone().into(), binding: nonces.binding.clone().into(), ciphersuite: (), @@ -335,8 +327,8 @@ pub(super) fn encode_group_commitments( ) -> Vec { let mut bytes = vec![]; - for item in signing_commitments.values() { - bytes.extend_from_slice(item.identifier.serialize().as_ref()); + for (item_identifier, item) in signing_commitments { + bytes.extend_from_slice(item_identifier.serialize().as_ref()); bytes.extend_from_slice(::serialize(&item.hiding.0).as_ref()); bytes.extend_from_slice(::serialize(&item.binding.0).as_ref()); } @@ -361,7 +353,6 @@ pub(super) fn encode_group_commitments( // https://github.com/ZcashFoundation/redjubjub/issues/111 pub fn preprocess( num_nonces: u8, - participant_identifier: Identifier, secret: &SigningShare, rng: &mut R, ) -> (Vec>, Vec>) @@ -375,7 +366,7 @@ where for _ in 0..num_nonces { let nonces = SigningNonces::new(secret, rng); - signing_commitments.push(SigningCommitments::from((participant_identifier, &nonces))); + signing_commitments.push(SigningCommitments::from(&nonces)); signing_nonces.push(nonces); } @@ -391,7 +382,6 @@ where /// /// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#name-round-one-commitment pub fn commit( - participant_identifier: Identifier, secret: &SigningShare, rng: &mut R, ) -> (SigningNonces, SigningCommitments) @@ -399,8 +389,7 @@ where C: Ciphersuite, R: CryptoRng + RngCore, { - let (mut vec_signing_nonces, mut vec_signing_commitments) = - preprocess(1, participant_identifier, secret, rng); + let (mut vec_signing_nonces, mut vec_signing_commitments) = preprocess(1, secret, rng); ( vec_signing_nonces.pop().expect("must have 1 element"), vec_signing_commitments.pop().expect("must have 1 element"), diff --git a/frost-core/src/frost/round2.rs b/frost-core/src/frost/round2.rs index a43075c..9f31f0e 100644 --- a/frost-core/src/frost/round2.rs +++ b/frost-core/src/frost/round2.rs @@ -11,77 +11,36 @@ use crate::{ #[cfg(feature = "serde")] use crate::ScalarSerialization; -/// A representation of a single signature share used in FROST structures and messages. -#[derive(Clone, Copy, Getters)] +// Used to help encoding a SignatureShare. Since it has a Scalar it can't +// be directly encoded with serde, so we use this struct to wrap the scalar. +#[cfg(feature = "serde")] +#[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] #[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] -pub struct SignatureResponse { - /// The scalar contribution to the group signature. - pub(crate) z_share: Scalar, -} - -impl SignatureResponse -where - C: Ciphersuite, -{ - /// Deserialize [`SignatureResponse`] from bytes - pub fn deserialize( - bytes: <::Field as Field>::Serialization, - ) -> Result> { - <::Field>::deserialize(&bytes) - .map(|scalar| Self { z_share: scalar }) - .map_err(|e| e.into()) - } - - /// Serialize [`SignatureResponse`] to bytes - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.z_share) - } -} +struct SignatureShareHelper(Scalar); #[cfg(feature = "serde")] -impl TryFrom> for SignatureResponse +impl TryFrom> for SignatureShareHelper where C: Ciphersuite, { type Error = Error; fn try_from(value: ScalarSerialization) -> Result { - Self::deserialize(value.0) + <::Field>::deserialize(&value.0) + .map(|scalar| Self(scalar)) + .map_err(|e| e.into()) } } #[cfg(feature = "serde")] -impl From> for ScalarSerialization +impl From> for ScalarSerialization where C: Ciphersuite, { - fn from(value: SignatureResponse) -> Self { - Self(value.serialize()) - } -} - -impl Debug for SignatureResponse -where - C: Ciphersuite, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("SignatureResponse") - .field("z_share", &hex::encode(self.serialize())) - .finish() - } -} - -impl Eq for SignatureResponse where C: Ciphersuite {} - -impl PartialEq for SignatureResponse -where - C: Ciphersuite, -{ - // TODO: should this have any constant-time guarantees? I think signature shares are public. - fn eq(&self, other: &Self) -> bool { - self.z_share == other.z_share + fn from(value: SignatureShareHelper) -> Self { + Self(<::Field>::serialize(&value.0)) } } @@ -90,11 +49,63 @@ where #[derive(Clone, Copy, Eq, PartialEq, Getters)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[cfg_attr(feature = "serde", serde(try_from = "SignatureShareSerialization"))] +#[cfg_attr(feature = "serde", serde(into = "SignatureShareSerialization"))] pub struct SignatureShare { - /// Represents the participant identifier. - pub(crate) identifier: Identifier, /// This participant's signature over the message. - pub(crate) signature: SignatureResponse, + pub(crate) share: Scalar, +} + +impl SignatureShare +where + C: Ciphersuite, +{ + /// Deserialize [`SignatureShare`] from bytes + pub fn deserialize( + bytes: <::Field as Field>::Serialization, + ) -> Result> { + <::Field>::deserialize(&bytes) + .map(|scalar| Self { share: scalar }) + .map_err(|e| e.into()) + } + + /// Serialize [`SignatureShare`] to bytes + pub fn serialize(&self) -> <::Field as Field>::Serialization { + <::Field>::serialize(&self.share) + } + + /// Tests if a signature share issued by a participant is valid before + /// aggregating it into a final joint signature to publish. + /// + /// 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 + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn verify( + &self, + identifier: Identifier, + group_commitment_share: &round1::GroupCommitmentShare, + public_key: &frost::keys::VerifyingShare, + lambda_i: Scalar, + challenge: &Challenge, + ) -> Result<(), Error> { + if (::generator() * self.share) + != (group_commitment_share.0 + (public_key.0 * challenge.0 * lambda_i)) + { + return Err(Error::InvalidSignatureShare { + culprit: identifier, + }); + } + + Ok(()) + } +} + +#[cfg(feature = "serde")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +struct SignatureShareSerialization { + share: SignatureShareHelper, /// Ciphersuite ID for serialization #[cfg_attr( feature = "serde", @@ -107,41 +118,28 @@ pub struct SignatureShare { ciphersuite: (), } -impl SignatureShare +#[cfg(feature = "serde")] +impl From> for SignatureShare where C: Ciphersuite, { - /// Create a new [`SignatureShare`]. - pub fn new(identifier: Identifier, signature: SignatureResponse) -> Self { + fn from(value: SignatureShareSerialization) -> Self { Self { - identifier, - signature, - ciphersuite: (), + share: value.share.0, } } +} - /// Tests if a signature share issued by a participant is valid before - /// aggregating it into a final joint signature to publish. - /// - /// 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( - &self, - group_commitment_share: &round1::GroupCommitmentShare, - public_key: &frost::keys::VerifyingShare, - lambda_i: Scalar, - challenge: &Challenge, - ) -> Result<(), Error> { - if (::generator() * self.signature.z_share) - != (group_commitment_share.0 + (public_key.0 * challenge.0 * lambda_i)) - { - return Err(Error::InvalidSignatureShare { - culprit: self.identifier, - }); +#[cfg(feature = "serde")] +impl From> for SignatureShareSerialization +where + C: Ciphersuite, +{ + fn from(value: SignatureShare) -> Self { + Self { + share: SignatureShareHelper(value.share), + ciphersuite: (), } - - Ok(()) } } @@ -151,8 +149,7 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("SignatureShare") - .field("identifier", &self.identifier) - .field("signature", &self.signature) + .field("share", &hex::encode(self.serialize())) .finish() } } @@ -170,11 +167,7 @@ fn compute_signature_share( + (signer_nonces.binding.0 * binding_factor.0) + (lambda_i * key_package.secret_share.0 * challenge.0); - SignatureShare:: { - identifier: *key_package.identifier(), - signature: SignatureResponse:: { z_share }, - ciphersuite: (), - } + SignatureShare:: { share: z_share } } /// Performed once by each participant selected for the signing operation. diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index d23c9bc..a15cc25 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -1,5 +1,8 @@ //! Ciphersuite-generic test functions. -use std::{collections::HashMap, convert::TryFrom}; +use std::{ + collections::{BTreeMap, HashMap}, + convert::TryFrom, +}; use crate::{ frost::{self, Identifier}, @@ -94,8 +97,8 @@ fn check_sign( ) -> (Vec, Signature, VerifyingKey) { let mut nonces_map: HashMap, frost::round1::SigningNonces> = HashMap::new(); - let mut commitments_map: HashMap, frost::round1::SigningCommitments> = - HashMap::new(); + let mut commitments_map: BTreeMap, frost::round1::SigningCommitments> = + BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -105,7 +108,6 @@ fn check_sign( // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _min_signers_. let (nonces, commitments) = frost::round1::commit( - participant_identifier, key_packages .get(&participant_identifier) .unwrap() @@ -119,10 +121,9 @@ 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 comms = commitments_map.clone().into_values().collect(); - let signing_package = frost::SigningPackage::new(comms, message); + let signing_package = frost::SigningPackage::new(commitments_map, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -136,7 +137,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); } //////////////////////////////////////////////////////////////////////////// @@ -152,7 +153,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). @@ -182,20 +183,16 @@ fn check_sign( fn check_aggregate_error( signing_package: frost::SigningPackage, - mut signature_shares: Vec>, + mut signature_shares: HashMap, frost::round2::SignatureShare>, pubkey_package: frost::keys::PublicKeyPackage, ) { let one = <::Group as Group>::Field::one(); // Corrupt a share - signature_shares[0].signature.z_share = signature_shares[0].signature.z_share + one; - let e = frost::aggregate(&signing_package, &signature_shares[..], &pubkey_package).unwrap_err(); - assert_eq!(e.culprit(), Some(*signature_shares[0].identifier())); - assert_eq!( - e, - Error::InvalidSignatureShare { - culprit: *signature_shares[0].identifier() - } - ); + let id = *signature_shares.keys().next().unwrap(); + signature_shares.get_mut(&id).unwrap().share = signature_shares[&id].share + one; + let e = frost::aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap_err(); + assert_eq!(e.culprit(), Some(id)); + assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); } /// Test FROST signing with trusted dealer with a Ciphersuite. @@ -225,7 +222,7 @@ where // will be sent through some communication channel. let mut received_round1_packages: HashMap< frost::Identifier, - Vec>, + HashMap, frost::keys::dkg::round1::Package>, > = HashMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -252,8 +249,8 @@ where .expect("should be nonzero"); received_round1_packages .entry(receiver_participant_identifier) - .or_insert_with(Vec::new) - .push(round1_package.clone()); + .or_insert_with(HashMap::new) + .insert(participant_identifier, round1_package.clone()); } } @@ -292,11 +289,11 @@ where // sent through some communication channel. // Note that, in contrast to the previous part, here each other participant // gets its own specific package. - for round2_package in round2_packages { + for (receiver_identifier, round2_package) in round2_packages { received_round2_packages - .entry(round2_package.receiver_identifier) - .or_insert_with(Vec::new) - .push(round2_package); + .entry(receiver_identifier) + .or_insert_with(HashMap::new) + .insert(participant_identifier, round2_package); } } @@ -412,19 +409,16 @@ pub fn check_sign_with_dealer_and_identifiers( round1_secret_package: frost::keys::dkg::round1::SecretPackage, - mut round1_packages: Vec>, + mut round1_packages: HashMap, frost::keys::dkg::round1::Package>, ) { let one = <::Group as Group>::Field::one(); // Corrupt a PoK - round1_packages[0].proof_of_knowledge.z = round1_packages[0].proof_of_knowledge.z + one; + let id = *round1_packages.keys().next().unwrap(); + round1_packages.get_mut(&id).unwrap().proof_of_knowledge.z = + round1_packages[&id].proof_of_knowledge.z + one; let e = frost::keys::dkg::part2(round1_secret_package, &round1_packages).unwrap_err(); - assert_eq!(e.culprit(), Some(*round1_packages[0].sender_identifier())); - assert_eq!( - e, - Error::InvalidProofOfKnowledge { - culprit: *round1_packages[0].sender_identifier() - } - ); + assert_eq!(e.culprit(), Some(id)); + assert_eq!(e, Error::InvalidProofOfKnowledge { culprit: id }); } /// Test Error culprit method. diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index b440285..5859810 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -1,5 +1,5 @@ //! Helper function for testing with test vectors. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use debugless_unwrap::DebuglessUnwrap; use hex::{self, FromHex}; @@ -20,7 +20,7 @@ pub struct TestVectors { hiding_nonces_randomness: HashMap, Vec>, binding_nonces_randomness: HashMap, Vec>, signer_nonces: HashMap, SigningNonces>, - signer_commitments: HashMap, SigningCommitments>, + signer_commitments: BTreeMap, SigningCommitments>, binding_factor_inputs: HashMap, Vec>, binding_factors: HashMap, BindingFactor>, signature_shares: HashMap, SignatureShare>, @@ -80,7 +80,7 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors, Vec> = HashMap::new(); let mut binding_nonces_randomness: HashMap, Vec> = HashMap::new(); let mut signer_nonces: HashMap, SigningNonces> = HashMap::new(); - let mut signer_commitments: HashMap, SigningCommitments> = HashMap::new(); + let mut signer_commitments: BTreeMap, SigningCommitments> = BTreeMap::new(); let mut binding_factor_inputs: HashMap, Vec> = HashMap::new(); let mut binding_factors: HashMap, BindingFactor> = HashMap::new(); @@ -104,7 +104,6 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors::new( - identifier, NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap()).unwrap(), NonceCommitment::from_hex(signer["binding_nonce_commitment"].as_str().unwrap()) .unwrap(), @@ -136,12 +135,7 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors::new( - i.try_into().unwrap(), - SignatureResponse { - z_share: <::Field>::deserialize(&sig_share).unwrap(), - }, - ); + let signature_share = SignatureShare::::deserialize(sig_share).unwrap(); signature_shares.insert(i.try_into().unwrap(), signature_share); } @@ -259,9 +253,7 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { // Round 2: each participant generates their signature share ///////////////////////////////////////////////////////////////////////////// - let signer_commitments_vec = signer_commitments.into_values().collect(); - - let signing_package = frost::SigningPackage::new(signer_commitments_vec, &message_bytes); + let signing_package = frost::SigningPackage::new(signer_commitments, &message_bytes); for (identifier, input) in signing_package.binding_factor_preimages(&[]).iter() { assert_eq!(*input, binding_factor_inputs[identifier]); @@ -274,7 +266,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() { @@ -284,12 +276,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() @@ -304,14 +294,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-ed25519/README.md b/frost-ed25519/README.md index 2096a1f..e43eb58 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -12,7 +12,7 @@ scenario in a single thread and it abstracts away any communication between peer # // ANCHOR: tkg_gen use frost_ed25519 as frost; use rand::thread_rng; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; let mut rng = thread_rng(); let max_signers = 5; @@ -38,7 +38,7 @@ for (identifier, secret_share) in shares { } let mut nonces_map = HashMap::new(); -let mut commitments_map = HashMap::new(); +let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -52,8 +52,7 @@ for participant_index in 1..(min_signers as u16 + 1) { // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - participant_identifier, - key_package.secret_share(), + key_packages[&participant_identifier].secret_share(), &mut rng, ); # // ANCHOR_END: round1_commit @@ -68,14 +67,13 @@ 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 commitments_received = commitments_map.clone().into_values().collect(); +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 # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message); +let signing_package = frost::SigningPackage::new(commitments_map, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// @@ -95,7 +93,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); } //////////////////////////////////////////////////////////////////////////// @@ -105,7 +103,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-ed25519/dkg.md b/frost-ed25519/dkg.md index ec1c631..3c2119b 100644 --- a/frost-ed25519/dkg.md +++ b/frost-ed25519/dkg.md @@ -80,8 +80,8 @@ for participant_index in 1..=max_signers { .expect("should be nonzero"); received_round1_packages .entry(receiver_participant_identifier) - .or_insert_with(Vec::new) - .push(round1_package.clone()); + .or_insert_with(HashMap::new) + .insert(participant_identifier, round1_package.clone()); } } @@ -121,11 +121,11 @@ for participant_index in 1..=max_signers { // sent through some communication channel. // Note that, in contrast to the previous part, here each other participant // gets its own specific package. - for round2_package in round2_packages { + for (receiver_identifier, round2_package) in round2_packages { received_round2_packages - .entry(*round2_package.receiver_identifier()) - .or_insert_with(Vec::new) - .push(round2_package); + .entry(receiver_identifier) + .or_insert_with(HashMap::new) + .insert(participant_identifier, round2_package); } } diff --git a/frost-ed25519/src/keys/dkg.rs b/frost-ed25519/src/keys/dkg.rs index 934fbec..304565b 100644 --- a/frost-ed25519/src/keys/dkg.rs +++ b/frost-ed25519/src/keys/dkg.rs @@ -64,8 +64,8 @@ pub fn part1( /// must be sent to other participants. pub fn part2( secret_package: round1::SecretPackage, - round1_packages: &[round1::Package], -) -> Result<(round2::SecretPackage, Vec), Error> { + round1_packages: &HashMap, +) -> Result<(round2::SecretPackage, HashMap), Error> { frost::keys::dkg::part2(secret_package, round1_packages) } @@ -80,8 +80,8 @@ pub fn part2( /// signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, - round1_packages: &[round1::Package], - round2_packages: &[round2::Package], + round1_packages: &HashMap, + round2_packages: &HashMap, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) } diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 63618f1..a1f03f2 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -2,6 +2,8 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] +use std::collections::HashMap; + use curve25519_dalek::{ constants::ED25519_BASEPOINT_POINT, edwards::{CompressedEdwardsY, EdwardsPoint}, @@ -338,15 +340,11 @@ pub mod round1 { /// /// Generates the signing nonces and commitments to be used in the signing /// operation. - pub fn commit( - participant_identifier: frost::Identifier, - secret: &SigningShare, - rng: &mut RNG, - ) -> (SigningNonces, SigningCommitments) + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where RNG: CryptoRng + RngCore, { - frost::round1::commit::(participant_identifier, secret, rng) + frost::round1::commit::(secret, rng) } } @@ -362,9 +360,6 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare; - /// A representation of a single signature share used in FROST structures and messages. - pub type SignatureResponse = frost::round2::SignatureResponse; - /// Performed once by each participant selected for the signing operation. /// /// Receives the message to be signed and a set of signing commitments and a set @@ -402,7 +397,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-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index d13b0be..eb3e86f 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -1,6 +1,6 @@ //! Generate sample, fixed instances of structs for testing. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use frost_core::{Ciphersuite, Element, Group, Scalar}; use frost_ed25519::{ @@ -10,7 +10,7 @@ use frost_ed25519::{ VerifyingShare, }, round1::{NonceCommitment, SigningCommitments}, - round2::{SignatureResponse, SignatureShare}, + round2::SignatureShare, Field, Signature, SigningPackage, VerifyingKey, }; @@ -38,18 +38,14 @@ pub fn signing_commitments() -> SigningCommitments { let serialized_element2 = ::Group::serialize(&element2()); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); - let identifier = 42u16.try_into().unwrap(); - SigningCommitments::new( - identifier, - hiding_nonce_commitment, - binding_nonce_commitment, - ) + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } /// Generate a sample SigningPackage. pub fn signing_package() -> SigningPackage { - let commitments = vec![signing_commitments()]; + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); let message = "hello world".as_bytes(); SigningPackage::new(commitments, message) @@ -57,11 +53,9 @@ 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::deserialize(serialized_scalar).unwrap(); - SignatureShare::new(identifier, signature_response) + SignatureShare::deserialize(serialized_scalar).unwrap() } /// Generate a sample SecretShare. @@ -103,7 +97,6 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()); let serialized_signature = serialized_element @@ -118,14 +111,13 @@ pub fn round1_package() -> round1::Package { VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); let signature = Signature::deserialize(serialized_signature).unwrap(); - round1::Package::new(identifier, vss_commitment, signature) + round1::Package::new(vss_commitment, signature) } /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - round2::Package::new(identifier, identifier, signing_share) + round2::Package::new(signing_share) } diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 7084578..a6dfc0a 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -19,10 +19,9 @@ use helpers::samples; #[test] fn check_signing_commitments_recreation() { let commitments = samples::signing_commitments(); - let identifier = commitments.identifier(); let hiding = commitments.hiding(); let binding = commitments.binding(); - let new_commitments = SigningCommitments::new(*identifier, *hiding, *binding); + let new_commitments = SigningCommitments::new(*hiding, *binding); assert!(commitments == new_commitments); } @@ -31,14 +30,10 @@ fn check_signing_commitments_recreation() { fn check_signing_package_recreation() { let signing_package = samples::signing_package(); - let commitments = signing_package - .signing_commitments() - .values() - .cloned() - .collect(); + let commitments = signing_package.signing_commitments(); let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments, message); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } @@ -47,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 encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::new(*identifier, *signature_response); + let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); assert!(signature_share == new_signature_share); } @@ -106,11 +100,10 @@ fn check_public_key_package_recreation() { fn check_round1_package_recreation() { let round1_package = samples::round1_package(); - let identifier = round1_package.sender_identifier(); let vss_commitment = round1_package.commitment(); let signature = round1_package.proof_of_knowledge(); - let new_round1_package = round1::Package::new(*identifier, vss_commitment.clone(), *signature); + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); assert!(round1_package == new_round1_package); } @@ -120,12 +113,9 @@ fn check_round1_package_recreation() { fn check_round2_package_recreation() { let round2_package = samples::round2_package(); - let sender_identifier = round2_package.sender_identifier(); - let receiver_identifier = round2_package.receiver_identifier(); let signing_share = round2_package.secret_share(); - let new_round2_package = - round2::Package::new(*sender_identifier, *receiver_identifier, *signing_share); + let new_round2_package = round2::Package::new(*signing_share); assert!(round2_package == new_round2_package); } diff --git a/frost-ed25519/tests/serde_tests.rs b/frost-ed25519/tests/serde_tests.rs index bf3c1b1..f29f1ad 100644 --- a/frost-ed25519/tests/serde_tests.rs +++ b/frost-ed25519/tests/serde_tests.rs @@ -25,7 +25,6 @@ fn check_signing_commitments_serialization() { assert!(commitments == decoded_commitments); let json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Ed25519, SHA-512)" @@ -38,7 +37,6 @@ fn check_signing_commitments_serialization() { // Wrong ciphersuite let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Wrong, SHA-512)" @@ -47,7 +45,6 @@ fn check_signing_commitments_serialization() { // Invalid identifier let invalid_json = r#"{ - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" }"#; @@ -55,8 +52,7 @@ fn check_signing_commitments_serialization() { // Invalid field let invalid_json = r#"{ - "foo": "0000000000000000000000000000000000000000000000000000000000000000", - "hiding": "5866666666666666666666666666666666666666666666666666666666666666", + "foo": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -70,7 +66,6 @@ fn check_signing_commitments_serialization() { // Extra field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "extra": 1 @@ -94,7 +89,6 @@ fn check_signing_package_serialization() { let json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Ed25519, SHA-512)" @@ -110,7 +104,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "0000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Ed25519, SHA-512)" @@ -125,7 +118,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "foo": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Ed25519, SHA-512)" @@ -140,7 +132,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Ed25519, SHA-512)" } @@ -154,7 +145,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "5866666666666666666666666666666666666666666666666666666666666666", "binding": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "ciphersuite": "FROST(Ed25519, SHA-512)" @@ -179,8 +169,7 @@ fn check_signature_share_serialization() { assert!(signature_share == decoded_signature_share); let json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "signature": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", + "share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); @@ -189,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(Ed25519, SHA-512)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "foo": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; @@ -207,15 +187,13 @@ fn check_signature_share_serialization() { // Missing field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000",, "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "signature": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", + "share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "extra": 1, "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; @@ -430,7 +408,6 @@ fn check_round1_package_serialization() { assert!(round1_package == decoded_round1_package); let json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "5866666666666666666666666666666666666666666666666666666666666666" ], @@ -443,20 +420,8 @@ fn check_round1_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "commitment": [ - "5866666666666666666666666666666666666666666666666666666666666666" - ], - "proof_of_knowledge": "5866666666666666666666666666666666666666666666666666666666666666498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", - "ciphersuite": "FROST(Ed25519, SHA-512)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "5866666666666666666666666666666666666666666666666666666666666666" ], @@ -467,7 +432,6 @@ fn check_round1_package_serialization() { // Missing field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "5866666666666666666666666666666666666666666666666666666666666666" ], @@ -477,7 +441,6 @@ fn check_round1_package_serialization() { // Extra field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "5866666666666666666666666666666666666666666666666666666666666666" ], @@ -499,8 +462,6 @@ fn check_round2_package_serialization() { assert!(round2_package == decoded_round2_package); let json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; @@ -510,36 +471,21 @@ fn check_round2_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", - "ciphersuite": "FROST(Ed25519, SHA-512)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "foo": "2a00000000000000000000000000000000000000000000000000000000000000", - "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", + "foo": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Missing field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(Ed25519, SHA-512)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "extra": 1, "ciphersuite": "FROST(Ed25519, SHA-512)" diff --git a/frost-ed448/README.md b/frost-ed448/README.md index 220c8a9..83310b1 100644 --- a/frost-ed448/README.md +++ b/frost-ed448/README.md @@ -12,7 +12,7 @@ scenario in a single thread and it abstracts away any communication between peer # // ANCHOR: tkg_gen use frost_ed448 as frost; use rand::thread_rng; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; let mut rng = thread_rng(); let max_signers = 5; @@ -38,7 +38,7 @@ for (identifier, secret_share) in shares { } let mut nonces_map = HashMap::new(); -let mut commitments_map = HashMap::new(); +let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -52,8 +52,7 @@ for participant_index in 1..(min_signers as u16 + 1) { // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - participant_identifier, - key_package.secret_share(), + key_packages[&participant_identifier].secret_share(), &mut rng, ); # // ANCHOR_END: round1_commit @@ -68,14 +67,13 @@ 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 commitments_received = commitments_map.clone().into_values().collect(); +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 # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message); +let signing_package = frost::SigningPackage::new(commitments_map, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// @@ -95,7 +93,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); } //////////////////////////////////////////////////////////////////////////// @@ -105,7 +103,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-ed448/dkg.md b/frost-ed448/dkg.md index 7ac5e5a..05fb701 100644 --- a/frost-ed448/dkg.md +++ b/frost-ed448/dkg.md @@ -80,8 +80,8 @@ for participant_index in 1..=max_signers { .expect("should be nonzero"); received_round1_packages .entry(receiver_participant_identifier) - .or_insert_with(Vec::new) - .push(round1_package.clone()); + .or_insert_with(HashMap::new) + .insert(participant_identifier, round1_package.clone()); } } @@ -121,11 +121,11 @@ for participant_index in 1..=max_signers { // sent through some communication channel. // Note that, in contrast to the previous part, here each other participant // gets its own specific package. - for round2_package in round2_packages { + for (receiver_identifier, round2_package) in round2_packages { received_round2_packages - .entry(*round2_package.receiver_identifier()) - .or_insert_with(Vec::new) - .push(round2_package); + .entry(receiver_identifier) + .or_insert_with(HashMap::new) + .insert(participant_identifier, round2_package); } } diff --git a/frost-ed448/src/keys/dkg.rs b/frost-ed448/src/keys/dkg.rs index 934fbec..304565b 100644 --- a/frost-ed448/src/keys/dkg.rs +++ b/frost-ed448/src/keys/dkg.rs @@ -64,8 +64,8 @@ pub fn part1( /// must be sent to other participants. pub fn part2( secret_package: round1::SecretPackage, - round1_packages: &[round1::Package], -) -> Result<(round2::SecretPackage, Vec), Error> { + round1_packages: &HashMap, +) -> Result<(round2::SecretPackage, HashMap), Error> { frost::keys::dkg::part2(secret_package, round1_packages) } @@ -80,8 +80,8 @@ pub fn part2( /// signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, - round1_packages: &[round1::Package], - round2_packages: &[round2::Package], + round1_packages: &HashMap, + round2_packages: &HashMap, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) } diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 37c9a54..fddbba0 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -2,6 +2,8 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] +use std::collections::HashMap; + use ed448_goldilocks::{ curve::{edwards::CompressedEdwardsY, ExtendedPoint}, Scalar, @@ -332,15 +334,11 @@ pub mod round1 { /// /// Generates the signing nonces and commitments to be used in the signing /// operation. - pub fn commit( - participant_identifier: frost::Identifier, - secret: &SigningShare, - rng: &mut RNG, - ) -> (SigningNonces, SigningCommitments) + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where RNG: CryptoRng + RngCore, { - frost::round1::commit::(participant_identifier, secret, rng) + frost::round1::commit::(secret, rng) } } @@ -356,9 +354,6 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare; - /// A representation of a single signature share used in FROST structures and messages. - pub type SignatureResponse = frost::round2::SignatureResponse; - /// Performed once by each participant selected for the signing operation. /// /// Receives the message to be signed and a set of signing commitments and a set @@ -396,7 +391,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-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index ae1dd4b..679a5d9 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -1,6 +1,6 @@ //! Generate sample, fixed instances of structs for testing. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use frost_core::{Ciphersuite, Element, Group, Scalar}; use frost_ed448::{ @@ -10,7 +10,7 @@ use frost_ed448::{ VerifyingShare, }, round1::{NonceCommitment, SigningCommitments}, - round2::{SignatureResponse, SignatureShare}, + round2::SignatureShare, Field, Signature, SigningPackage, VerifyingKey, }; @@ -38,18 +38,14 @@ pub fn signing_commitments() -> SigningCommitments { let serialized_element2 = ::Group::serialize(&element2()); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); - let identifier = 42u16.try_into().unwrap(); - SigningCommitments::new( - identifier, - hiding_nonce_commitment, - binding_nonce_commitment, - ) + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } /// Generate a sample SigningPackage. pub fn signing_package() -> SigningPackage { - let commitments = vec![signing_commitments()]; + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); let message = "hello world".as_bytes(); SigningPackage::new(commitments, message) @@ -57,11 +53,9 @@ 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::deserialize(serialized_scalar).unwrap(); - SignatureShare::new(identifier, signature_response) + SignatureShare::deserialize(serialized_scalar).unwrap() } /// Generate a sample SecretShare. @@ -103,7 +97,6 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()); let serialized_signature = serialized_element @@ -118,14 +111,13 @@ pub fn round1_package() -> round1::Package { VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); let signature = Signature::deserialize(serialized_signature).unwrap(); - round1::Package::new(identifier, vss_commitment, signature) + round1::Package::new(vss_commitment, signature) } /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - round2::Package::new(identifier, identifier, signing_share) + round2::Package::new(signing_share) } diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index 7dbd835..c3a07e1 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -19,10 +19,9 @@ use helpers::samples; #[test] fn check_signing_commitments_recreation() { let commitments = samples::signing_commitments(); - let identifier = commitments.identifier(); let hiding = commitments.hiding(); let binding = commitments.binding(); - let new_commitments = SigningCommitments::new(*identifier, *hiding, *binding); + let new_commitments = SigningCommitments::new(*hiding, *binding); assert!(commitments == new_commitments); } @@ -31,14 +30,10 @@ fn check_signing_commitments_recreation() { fn check_signing_package_recreation() { let signing_package = samples::signing_package(); - let commitments = signing_package - .signing_commitments() - .values() - .cloned() - .collect(); + let commitments = signing_package.signing_commitments(); let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments, message); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } @@ -47,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 encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::new(*identifier, *signature_response); + let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); assert!(signature_share == new_signature_share); } @@ -106,11 +100,10 @@ fn check_public_key_package_recreation() { fn check_round1_package_recreation() { let round1_package = samples::round1_package(); - let identifier = round1_package.sender_identifier(); let vss_commitment = round1_package.commitment(); let signature = round1_package.proof_of_knowledge(); - let new_round1_package = round1::Package::new(*identifier, vss_commitment.clone(), *signature); + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); assert!(round1_package == new_round1_package); } @@ -120,12 +113,9 @@ fn check_round1_package_recreation() { fn check_round2_package_recreation() { let round2_package = samples::round2_package(); - let sender_identifier = round2_package.sender_identifier(); - let receiver_identifier = round2_package.receiver_identifier(); let signing_share = round2_package.secret_share(); - let new_round2_package = - round2::Package::new(*sender_identifier, *receiver_identifier, *signing_share); + let new_round2_package = round2::Package::new(*signing_share); assert!(round2_package == new_round2_package); } diff --git a/frost-ed448/tests/serde_tests.rs b/frost-ed448/tests/serde_tests.rs index 0a0b6c3..1ba91f7 100644 --- a/frost-ed448/tests/serde_tests.rs +++ b/frost-ed448/tests/serde_tests.rs @@ -25,7 +25,6 @@ fn check_signing_commitments_serialization() { assert!(commitments == decoded_commitments); let json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Ed448, SHAKE256)" @@ -38,7 +37,6 @@ fn check_signing_commitments_serialization() { // Wrong ciphersuite let invalid_json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Wrong, SHA-512)" @@ -47,7 +45,6 @@ fn check_signing_commitments_serialization() { // Invalid identifier let invalid_json = r#"{ - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" }"#; @@ -55,8 +52,7 @@ fn check_signing_commitments_serialization() { // Invalid field let invalid_json = r#"{ - "foo": "0000000000000000000000000000000000000000000000000000000000000000", - "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", + "foo": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -70,7 +66,6 @@ fn check_signing_commitments_serialization() { // Extra field let invalid_json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "extra": 1 @@ -94,7 +89,6 @@ fn check_signing_package_serialization() { let json = r#"{ "signing_commitments": { "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Ed448, SHAKE256)" @@ -110,7 +104,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "0000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Ed448, SHAKE256)" @@ -125,7 +118,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "foo": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Ed448, SHAKE256)" @@ -140,7 +132,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Ed448, SHAKE256)" } @@ -154,7 +145,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "hiding": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "binding": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "ciphersuite": "FROST(Ed448, SHAKE256)" @@ -179,8 +169,7 @@ fn check_signature_share_serialization() { assert!(signature_share == decoded_signature_share); let json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "signature": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", + "share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); @@ -189,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": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", - "ciphersuite": "FROST(Ed448, SHAKE256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "foo": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; @@ -207,15 +187,13 @@ fn check_signature_share_serialization() { // Missing field let invalid_json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",, "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "signature": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", + "share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "extra": 1, "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; @@ -430,7 +408,6 @@ fn check_round1_package_serialization() { assert!(round1_package == decoded_round1_package); let json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "commitment": [ "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" ], @@ -443,20 +420,8 @@ fn check_round1_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "commitment": [ - "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" - ], - "proof_of_knowledge": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69004d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", - "ciphersuite": "FROST(Ed448, SHAKE256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "commitment": [ "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" ], @@ -467,7 +432,6 @@ fn check_round1_package_serialization() { // Missing field let invalid_json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "commitment": [ "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" ], @@ -477,7 +441,6 @@ fn check_round1_package_serialization() { // Extra field let invalid_json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "commitment": [ "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" ], @@ -499,8 +462,6 @@ fn check_round2_package_serialization() { assert!(round2_package == decoded_round2_package); let json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "secret_share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; @@ -510,36 +471,21 @@ fn check_round2_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "secret_share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", - "ciphersuite": "FROST(Ed448, SHAKE256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "foo": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "secret_share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", + "foo": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Missing field let invalid_json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "secret_share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "ciphersuite": "FROST(Ed448, SHAKE256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "sender_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "secret_share": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "extra": 1, "ciphersuite": "FROST(Ed448, SHAKE256)" diff --git a/frost-p256/README.md b/frost-p256/README.md index a561091..951c29a 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -12,7 +12,7 @@ scenario in a single thread and it abstracts away any communication between peer # // ANCHOR: tkg_gen use frost_p256 as frost; use rand::thread_rng; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; let mut rng = thread_rng(); let max_signers = 5; @@ -38,7 +38,7 @@ for (identifier, secret_share) in shares { } let mut nonces_map = HashMap::new(); -let mut commitments_map = HashMap::new(); +let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -52,8 +52,7 @@ for participant_index in 1..(min_signers as u16 + 1) { // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - participant_identifier, - key_package.secret_share(), + key_packages[&participant_identifier].secret_share(), &mut rng, ); # // ANCHOR_END: round1_commit @@ -68,14 +67,13 @@ 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 commitments_received = commitments_map.clone().into_values().collect(); +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 # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message); +let signing_package = frost::SigningPackage::new(commitments_map, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// @@ -95,7 +93,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); } //////////////////////////////////////////////////////////////////////////// @@ -105,7 +103,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-p256/dkg.md b/frost-p256/dkg.md index 7138898..9993cbb 100644 --- a/frost-p256/dkg.md +++ b/frost-p256/dkg.md @@ -80,8 +80,8 @@ for participant_index in 1..=max_signers { .expect("should be nonzero"); received_round1_packages .entry(receiver_participant_identifier) - .or_insert_with(Vec::new) - .push(round1_package.clone()); + .or_insert_with(HashMap::new) + .insert(participant_identifier, round1_package.clone()); } } @@ -121,11 +121,11 @@ for participant_index in 1..=max_signers { // sent through some communication channel. // Note that, in contrast to the previous part, here each other participant // gets its own specific package. - for round2_package in round2_packages { + for (receiver_identifier, round2_package) in round2_packages { received_round2_packages - .entry(*round2_package.receiver_identifier()) - .or_insert_with(Vec::new) - .push(round2_package); + .entry(receiver_identifier) + .or_insert_with(HashMap::new) + .insert(participant_identifier, round2_package); } } diff --git a/frost-p256/src/keys/dkg.rs b/frost-p256/src/keys/dkg.rs index f9cfcc9..7d5480f 100644 --- a/frost-p256/src/keys/dkg.rs +++ b/frost-p256/src/keys/dkg.rs @@ -64,8 +64,8 @@ pub fn part1( /// must be sent to other participants. pub fn part2( secret_package: round1::SecretPackage, - round1_packages: &[round1::Package], -) -> Result<(round2::SecretPackage, Vec), Error> { + round1_packages: &HashMap, +) -> Result<(round2::SecretPackage, HashMap), Error> { frost::keys::dkg::part2(secret_package, round1_packages) } @@ -80,8 +80,8 @@ pub fn part2( /// signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, - round1_packages: &[round1::Package], - round2_packages: &[round2::Package], + round1_packages: &HashMap, + round2_packages: &HashMap, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) } diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 45633f1..8143c17 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -2,6 +2,8 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] +use std::collections::HashMap; + use p256::{ elliptic_curve::{ hash2curve::{hash_to_field, ExpandMsgXmd}, @@ -364,15 +366,11 @@ pub mod round1 { /// /// Generates the signing nonces and commitments to be used in the signing /// operation. - pub fn commit( - participant_identifier: frost::Identifier

, - secret: &SigningShare, - rng: &mut RNG, - ) -> (SigningNonces, SigningCommitments) + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where RNG: CryptoRng + RngCore, { - frost::round1::commit::(participant_identifier, secret, rng) + frost::round1::commit::(secret, rng) } } @@ -388,9 +386,6 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare

; - /// A representation of a single signature share used in FROST structures and messages. - pub type SignatureResponse = frost::round2::SignatureResponse

; - /// Performed once by each participant selected for the signing operation. /// /// Receives the message to be signed and a set of signing commitments and a set @@ -428,7 +423,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-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index cc6427c..eec57a8 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -1,6 +1,6 @@ //! Generate sample, fixed instances of structs for testing. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use frost_core::{Ciphersuite, Element, Group, Scalar}; use frost_p256::{ @@ -10,7 +10,7 @@ use frost_p256::{ VerifyingShare, }, round1::{NonceCommitment, SigningCommitments}, - round2::{SignatureResponse, SignatureShare}, + round2::SignatureShare, Field, Signature, SigningPackage, VerifyingKey, }; @@ -38,18 +38,14 @@ pub fn signing_commitments() -> SigningCommitments { let serialized_element2 = ::Group::serialize(&element2()); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); - let identifier = 42u16.try_into().unwrap(); - SigningCommitments::new( - identifier, - hiding_nonce_commitment, - binding_nonce_commitment, - ) + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } /// Generate a sample SigningPackage. pub fn signing_package() -> SigningPackage { - let commitments = vec![signing_commitments()]; + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); let message = "hello world".as_bytes(); SigningPackage::new(commitments, message) @@ -57,11 +53,9 @@ 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::deserialize(serialized_scalar).unwrap(); - SignatureShare::new(identifier, signature_response) + SignatureShare::deserialize(serialized_scalar).unwrap() } /// Generate a sample SecretShare. @@ -103,7 +97,6 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()); let serialized_signature = serialized_element @@ -118,14 +111,13 @@ pub fn round1_package() -> round1::Package { VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); let signature = Signature::deserialize(serialized_signature).unwrap(); - round1::Package::new(identifier, vss_commitment, signature) + round1::Package::new(vss_commitment, signature) } /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - round2::Package::new(identifier, identifier, signing_share) + round2::Package::new(signing_share) } diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index 4c7e99f..5f6a0e8 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -19,10 +19,9 @@ use helpers::samples; #[test] fn check_signing_commitments_recreation() { let commitments = samples::signing_commitments(); - let identifier = commitments.identifier(); let hiding = commitments.hiding(); let binding = commitments.binding(); - let new_commitments = SigningCommitments::new(*identifier, *hiding, *binding); + let new_commitments = SigningCommitments::new(*hiding, *binding); assert!(commitments == new_commitments); } @@ -31,14 +30,10 @@ fn check_signing_commitments_recreation() { fn check_signing_package_recreation() { let signing_package = samples::signing_package(); - let commitments = signing_package - .signing_commitments() - .values() - .cloned() - .collect(); + let commitments = signing_package.signing_commitments(); let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments, message); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } @@ -47,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 encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::new(*identifier, *signature_response); + let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); assert!(signature_share == new_signature_share); } @@ -106,11 +100,10 @@ fn check_public_key_package_recreation() { fn check_round1_package_recreation() { let round1_package = samples::round1_package(); - let identifier = round1_package.sender_identifier(); let vss_commitment = round1_package.commitment(); let signature = round1_package.proof_of_knowledge(); - let new_round1_package = round1::Package::new(*identifier, vss_commitment.clone(), *signature); + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); assert!(round1_package == new_round1_package); } @@ -120,12 +113,9 @@ fn check_round1_package_recreation() { fn check_round2_package_recreation() { let round2_package = samples::round2_package(); - let sender_identifier = round2_package.sender_identifier(); - let receiver_identifier = round2_package.receiver_identifier(); let signing_share = round2_package.secret_share(); - let new_round2_package = - round2::Package::new(*sender_identifier, *receiver_identifier, *signing_share); + let new_round2_package = round2::Package::new(*signing_share); assert!(round2_package == new_round2_package); } diff --git a/frost-p256/tests/serde_tests.rs b/frost-p256/tests/serde_tests.rs index dfc2f2a..5a791cf 100644 --- a/frost-p256/tests/serde_tests.rs +++ b/frost-p256/tests/serde_tests.rs @@ -25,7 +25,6 @@ fn check_signing_commitments_serialization() { assert!(commitments == decoded_commitments); let json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(P-256, SHA-256)" @@ -38,7 +37,6 @@ fn check_signing_commitments_serialization() { // Wrong ciphersuite let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(Wrong, SHA-512)" @@ -47,7 +45,6 @@ fn check_signing_commitments_serialization() { // Invalid identifier let invalid_json = r#"{ - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" }"#; @@ -55,8 +52,7 @@ fn check_signing_commitments_serialization() { // Invalid field let invalid_json = r#"{ - "foo": "0000000000000000000000000000000000000000000000000000000000000000", - "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "foo": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -70,7 +66,6 @@ fn check_signing_commitments_serialization() { // Extra field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "extra": 1 @@ -94,7 +89,6 @@ fn check_signing_package_serialization() { let json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(P-256, SHA-256)" @@ -110,7 +104,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "0000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(P-256, SHA-256)" @@ -125,7 +118,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "foo": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(P-256, SHA-256)" @@ -140,7 +132,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(P-256, SHA-256)" } @@ -154,7 +145,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "binding": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "ciphersuite": "FROST(P-256, SHA-256)" @@ -179,8 +169,7 @@ fn check_signature_share_serialization() { assert!(signature_share == decoded_signature_share); let json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "signature": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", + "share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "ciphersuite": "FROST(P-256, SHA-256)" }"#; let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); @@ -189,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": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", - "ciphersuite": "FROST(P-256, SHA-256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "foo": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "ciphersuite": "FROST(P-256, SHA-256)" }"#; @@ -207,15 +187,13 @@ fn check_signature_share_serialization() { // Missing field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a",, "ciphersuite": "FROST(P-256, SHA-256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "signature": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", + "share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "extra": 1, "ciphersuite": "FROST(P-256, SHA-256)" }"#; @@ -430,7 +408,6 @@ fn check_round1_package_serialization() { assert!(round1_package == decoded_round1_package); let json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" ], @@ -443,20 +420,8 @@ fn check_round1_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "commitment": [ - "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" - ], - "proof_of_knowledge": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", - "ciphersuite": "FROST(P-256, SHA-256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" ], @@ -467,7 +432,6 @@ fn check_round1_package_serialization() { // Missing field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" ], @@ -477,7 +441,6 @@ fn check_round1_package_serialization() { // Extra field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" ], @@ -499,8 +462,6 @@ fn check_round2_package_serialization() { assert!(round2_package == decoded_round2_package); let json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "receiver_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "secret_share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "ciphersuite": "FROST(P-256, SHA-256)" }"#; @@ -510,36 +471,21 @@ fn check_round2_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "secret_share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", - "ciphersuite": "FROST(P-256, SHA-256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "foo": "000000000000000000000000000000000000000000000000000000000000002a", - "secret_share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", + "foo": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "ciphersuite": "FROST(P-256, SHA-256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Missing field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "secret_share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "ciphersuite": "FROST(P-256, SHA-256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "receiver_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "secret_share": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "extra": 1, "ciphersuite": "FROST(P-256, SHA-256)" diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 9645f8e..3577ff2 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -5,6 +5,8 @@ #[cfg(any(test, feature = "test-impl"))] pub mod tests; +use std::collections::HashMap; + pub use frost_core; use frost_core::{ @@ -91,7 +93,7 @@ pub fn sign( /// service attack due to publishing an invalid signature. pub fn aggregate( signing_package: &frost::SigningPackage, - signature_shares: &[frost::round2::SignatureShare], + signature_shares: &HashMap, frost::round2::SignatureShare>, pubkeys: &frost::keys::PublicKeyPackage, randomized_params: &RandomizedParams, ) -> Result, Error> @@ -125,8 +127,8 @@ where // [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-12.html#section-5.3 let mut z = <::Field as Field>::zero(); - for signature_share in signature_shares { - z = z + *signature_share.signature().z_share(); + for signature_share in signature_shares.values() { + z = z + *signature_share.share(); } z = z + challenge.clone().to_scalar() * randomized_params.randomizer; @@ -141,27 +143,33 @@ where // if the aggregate signature is valid (which should be the common case). if let Err(err) = verification_result { // 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 = - frost::derive_interpolating_value(signature_share.identifier(), signing_package)?; + frost::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-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index d174c48..6fc1d5b 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -1,6 +1,6 @@ //! Ciphersuite-generic test functions for re-randomized FROST. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use crate::{frost_core::frost, frost_core::Ciphersuite, RandomizedParams}; use frost_core::{Field, Group, Signature, VerifyingKey}; @@ -35,8 +35,8 @@ pub fn check_randomized_sign_with_dealer } let mut nonces: HashMap, frost::round1::SigningNonces> = HashMap::new(); - let mut commitments: HashMap, frost::round1::SigningCommitments> = - HashMap::new(); + let mut commitments: BTreeMap, frost::round1::SigningCommitments> = + BTreeMap::new(); check_from_randomizer(&pubkeys, &mut rng); let randomizer_params = RandomizedParams::new(&pubkeys, &mut rng); @@ -50,7 +50,6 @@ pub fn check_randomized_sign_with_dealer // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _min_signers_. let (nonce, commitment) = frost::round1::commit( - participant_identifier, key_packages .get(&participant_identifier) .unwrap() @@ -64,10 +63,10 @@ pub fn check_randomized_sign_with_dealer // 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> = Vec::new(); + let mut signature_shares: HashMap, frost::round2::SignatureShare<_>> = + HashMap::new(); let message = "message to sign".as_bytes(); - let comms = commitments.clone().into_values().collect(); - let signing_package = frost::SigningPackage::new(comms, message); + let signing_package = frost::SigningPackage::new(commitments, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -86,7 +85,7 @@ pub fn check_randomized_sign_with_dealer randomizer_params.randomizer_point(), ) .unwrap(); - signature_shares.push(signature_share); + signature_shares.insert(*participant_identifier, signature_share); } //////////////////////////////////////////////////////////////////////////// @@ -97,7 +96,7 @@ pub fn check_randomized_sign_with_dealer // Aggregate (also verifies the signature shares) let group_signature_res = crate::aggregate( &signing_package, - &signature_shares[..], + &signature_shares, &pubkeys, &randomizer_params, ); diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index e55011d..d2c8a64 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -12,7 +12,7 @@ scenario in a single thread and it abstracts away any communication between peer # // ANCHOR: tkg_gen use frost_ristretto255 as frost; use rand::thread_rng; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; let mut rng = thread_rng(); let max_signers = 5; @@ -38,7 +38,7 @@ for (identifier, secret_share) in shares { } let mut nonces_map = HashMap::new(); -let mut commitments_map = HashMap::new(); +let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -52,8 +52,7 @@ for participant_index in 1..(min_signers as u16 + 1) { // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - participant_identifier, - key_package.secret_share(), + key_packages[&participant_identifier].secret_share(), &mut rng, ); # // ANCHOR_END: round1_commit @@ -68,14 +67,13 @@ 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 commitments_received = commitments_map.clone().into_values().collect(); +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 # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message); +let signing_package = frost::SigningPackage::new(commitments_map, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// @@ -95,7 +93,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); } //////////////////////////////////////////////////////////////////////////// @@ -105,7 +103,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/dkg.md b/frost-ristretto255/dkg.md index 848c80e..b4ac5dd 100644 --- a/frost-ristretto255/dkg.md +++ b/frost-ristretto255/dkg.md @@ -80,8 +80,8 @@ for participant_index in 1..=max_signers { .expect("should be nonzero"); received_round1_packages .entry(receiver_participant_identifier) - .or_insert_with(Vec::new) - .push(round1_package.clone()); + .or_insert_with(HashMap::new) + .insert(participant_identifier, round1_package.clone()); } } @@ -121,11 +121,11 @@ for participant_index in 1..=max_signers { // sent through some communication channel. // Note that, in contrast to the previous part, here each other participant // gets its own specific package. - for round2_package in round2_packages { + for (receiver_identifier, round2_package) in round2_packages { received_round2_packages - .entry(*round2_package.receiver_identifier()) - .or_insert_with(Vec::new) - .push(round2_package); + .entry(receiver_identifier) + .or_insert_with(HashMap::new) + .insert(participant_identifier, round2_package); } } diff --git a/frost-ristretto255/src/keys/dkg.rs b/frost-ristretto255/src/keys/dkg.rs index eb9f54b..ece47a9 100644 --- a/frost-ristretto255/src/keys/dkg.rs +++ b/frost-ristretto255/src/keys/dkg.rs @@ -64,8 +64,8 @@ pub fn part1( /// must be sent to other participants. pub fn part2( secret_package: round1::SecretPackage, - round1_packages: &[round1::Package], -) -> Result<(round2::SecretPackage, Vec), Error> { + round1_packages: &HashMap, +) -> Result<(round2::SecretPackage, HashMap), Error> { frost::keys::dkg::part2(secret_package, round1_packages) } @@ -80,8 +80,8 @@ pub fn part2( /// signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, - round1_packages: &[round1::Package], - round2_packages: &[round2::Package], + round1_packages: &HashMap, + round2_packages: &HashMap, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) } diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index fcb6628..ea83c14 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}, @@ -326,15 +328,11 @@ pub mod round1 { /// /// Generates the signing nonces and commitments to be used in the signing /// operation. - pub fn commit( - participant_identifier: frost::Identifier, - secret: &SigningShare, - rng: &mut RNG, - ) -> (SigningNonces, SigningCommitments) + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where RNG: CryptoRng + RngCore, { - frost::round1::commit::(participant_identifier, secret, rng) + frost::round1::commit::(secret, rng) } } @@ -350,9 +348,6 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare; - /// A representation of a single signature share used in FROST structures and messages. - pub type SignatureResponse = frost::round2::SignatureResponse; - /// Performed once by each participant selected for the signing operation. /// /// Receives the message to be signed and a set of signing commitments and a set @@ -390,7 +385,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 84392a8..3f07402 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -1,6 +1,6 @@ //! Generate sample, fixed instances of structs for testing. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use frost_core::{Ciphersuite, Element, Group, Scalar}; use frost_ristretto255::{ @@ -10,7 +10,7 @@ use frost_ristretto255::{ VerifyingShare, }, round1::{NonceCommitment, SigningCommitments}, - round2::{SignatureResponse, SignatureShare}, + round2::SignatureShare, Field, Signature, SigningPackage, VerifyingKey, }; @@ -38,18 +38,14 @@ pub fn signing_commitments() -> SigningCommitments { let serialized_element2 = ::Group::serialize(&element2()); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); - let identifier = 42u16.try_into().unwrap(); - SigningCommitments::new( - identifier, - hiding_nonce_commitment, - binding_nonce_commitment, - ) + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } /// Generate a sample SigningPackage. pub fn signing_package() -> SigningPackage { - let commitments = vec![signing_commitments()]; + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); let message = "hello world".as_bytes(); SigningPackage::new(commitments, message) @@ -57,11 +53,9 @@ 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::deserialize(serialized_scalar).unwrap(); - SignatureShare::new(identifier, signature_response) + SignatureShare::deserialize(serialized_scalar).unwrap() } /// Generate a sample SecretShare. @@ -103,7 +97,6 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()); let serialized_signature = serialized_element @@ -118,14 +111,13 @@ pub fn round1_package() -> round1::Package { VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); let signature = Signature::deserialize(serialized_signature).unwrap(); - round1::Package::new(identifier, vss_commitment, signature) + round1::Package::new(vss_commitment, signature) } /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - round2::Package::new(identifier, identifier, signing_share) + round2::Package::new(signing_share) } diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index cf376e9..5103e6f 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -19,10 +19,9 @@ use helpers::samples; #[test] fn check_signing_commitments_recreation() { let commitments = samples::signing_commitments(); - let identifier = commitments.identifier(); let hiding = commitments.hiding(); let binding = commitments.binding(); - let new_commitments = SigningCommitments::new(*identifier, *hiding, *binding); + let new_commitments = SigningCommitments::new(*hiding, *binding); assert!(commitments == new_commitments); } @@ -31,14 +30,10 @@ fn check_signing_commitments_recreation() { fn check_signing_package_recreation() { let signing_package = samples::signing_package(); - let commitments = signing_package - .signing_commitments() - .values() - .cloned() - .collect(); + let commitments = signing_package.signing_commitments(); let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments, message); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } @@ -47,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 encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::new(*identifier, *signature_response); + let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); assert!(signature_share == new_signature_share); } @@ -106,11 +100,10 @@ fn check_public_key_package_recreation() { fn check_round1_package_recreation() { let round1_package = samples::round1_package(); - let identifier = round1_package.sender_identifier(); let vss_commitment = round1_package.commitment(); let signature = round1_package.proof_of_knowledge(); - let new_round1_package = round1::Package::new(*identifier, vss_commitment.clone(), *signature); + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); assert!(round1_package == new_round1_package); } @@ -120,12 +113,9 @@ fn check_round1_package_recreation() { fn check_round2_package_recreation() { let round2_package = samples::round2_package(); - let sender_identifier = round2_package.sender_identifier(); - let receiver_identifier = round2_package.receiver_identifier(); let signing_share = round2_package.secret_share(); - let new_round2_package = - round2::Package::new(*sender_identifier, *receiver_identifier, *signing_share); + let new_round2_package = round2::Package::new(*signing_share); assert!(round2_package == new_round2_package); } diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs index 650aeda..d22da94 100644 --- a/frost-ristretto255/tests/serde_tests.rs +++ b/frost-ristretto255/tests/serde_tests.rs @@ -25,7 +25,6 @@ fn check_signing_commitments_serialization() { assert!(commitments == decoded_commitments); let json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(ristretto255, SHA-512)" @@ -38,7 +37,6 @@ fn check_signing_commitments_serialization() { // Wrong ciphersuite let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(Wrong, SHA-512)" @@ -47,7 +45,6 @@ fn check_signing_commitments_serialization() { // Invalid identifier let invalid_json = r#"{ - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" }"#; @@ -55,8 +52,7 @@ fn check_signing_commitments_serialization() { // Invalid field let invalid_json = r#"{ - "foo": "0000000000000000000000000000000000000000000000000000000000000000", - "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "foo": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -70,7 +66,6 @@ fn check_signing_commitments_serialization() { // Extra field let invalid_json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "extra": 1 @@ -94,7 +89,6 @@ fn check_signing_package_serialization() { let json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(ristretto255, SHA-512)" @@ -110,7 +104,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "0000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(ristretto255, SHA-512)" @@ -125,7 +118,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "foo": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(ristretto255, SHA-512)" @@ -140,7 +132,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(ristretto255, SHA-512)" } @@ -154,7 +145,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "2a00000000000000000000000000000000000000000000000000000000000000": { - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "hiding": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "binding": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "ciphersuite": "FROST(ristretto255, SHA-512)" @@ -179,8 +169,7 @@ fn check_signature_share_serialization() { assert!(signature_share == decoded_signature_share); let json = r#"{ - "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "signature": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", + "share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); @@ -189,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)" }"#; @@ -207,15 +187,13 @@ 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", + "share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "extra": 1, "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; @@ -430,7 +408,6 @@ fn check_round1_package_serialization() { assert!(round1_package == decoded_round1_package); let json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" ], @@ -443,20 +420,8 @@ fn check_round1_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "commitment": [ - "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" - ], - "proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", - "ciphersuite": "FROST(ristretto255, SHA-512)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" ], @@ -467,7 +432,6 @@ fn check_round1_package_serialization() { // Missing field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" ], @@ -477,7 +441,6 @@ fn check_round1_package_serialization() { // Extra field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "commitment": [ "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" ], @@ -499,8 +462,6 @@ fn check_round2_package_serialization() { assert!(round2_package == decoded_round2_package); let json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; @@ -510,36 +471,21 @@ fn check_round2_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", - "ciphersuite": "FROST(ristretto255, SHA-512)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "foo": "2a00000000000000000000000000000000000000000000000000000000000000", - "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", + "foo": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Missing field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "ciphersuite": "FROST(ristretto255, SHA-512)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "sender_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "2a00000000000000000000000000000000000000000000000000000000000000", "secret_share": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "extra": 1, "ciphersuite": "FROST(ristretto255, SHA-512)" diff --git a/frost-secp256k1/README.md b/frost-secp256k1/README.md index 86c24d5..19f4d13 100644 --- a/frost-secp256k1/README.md +++ b/frost-secp256k1/README.md @@ -12,7 +12,7 @@ scenario in a single thread and it abstracts away any communication between peer # // ANCHOR: tkg_gen use frost_secp256k1 as frost; use rand::thread_rng; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; let mut rng = thread_rng(); let max_signers = 5; @@ -38,7 +38,7 @@ for (identifier, secret_share) in shares { } let mut nonces_map = HashMap::new(); -let mut commitments_map = HashMap::new(); +let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // Round 1: generating nonces and signing commitments for each participant @@ -52,8 +52,7 @@ for participant_index in 1..(min_signers as u16 + 1) { // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - participant_identifier, - key_package.secret_share(), + key_packages[&participant_identifier].secret_share(), &mut rng, ); # // ANCHOR_END: round1_commit @@ -68,14 +67,13 @@ 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 commitments_received = commitments_map.clone().into_values().collect(); +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 # // involved in the current signing (at least min_signers participants), # // using an authenticate channel (and confidential if the message is secret). -let signing_package = frost::SigningPackage::new(commitments_received, message); +let signing_package = frost::SigningPackage::new(commitments_map, message); # // ANCHOR_END: round2_package //////////////////////////////////////////////////////////////////////////// @@ -95,7 +93,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); } //////////////////////////////////////////////////////////////////////////// @@ -105,7 +103,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-secp256k1/dkg.md b/frost-secp256k1/dkg.md index 7a31e46..5a73374 100644 --- a/frost-secp256k1/dkg.md +++ b/frost-secp256k1/dkg.md @@ -80,8 +80,8 @@ for participant_index in 1..=max_signers { .expect("should be nonzero"); received_round1_packages .entry(receiver_participant_identifier) - .or_insert_with(Vec::new) - .push(round1_package.clone()); + .or_insert_with(HashMap::new) + .insert(participant_identifier, round1_package.clone()); } } @@ -121,11 +121,11 @@ for participant_index in 1..=max_signers { // sent through some communication channel. // Note that, in contrast to the previous part, here each other participant // gets its own specific package. - for round2_package in round2_packages { + for (receiver_identifier, round2_package) in round2_packages { received_round2_packages - .entry(*round2_package.receiver_identifier()) - .or_insert_with(Vec::new) - .push(round2_package); + .entry(receiver_identifier) + .or_insert_with(HashMap::new) + .insert(participant_identifier, round2_package); } } diff --git a/frost-secp256k1/src/keys/dkg.rs b/frost-secp256k1/src/keys/dkg.rs index f6d0d50..81a8e66 100644 --- a/frost-secp256k1/src/keys/dkg.rs +++ b/frost-secp256k1/src/keys/dkg.rs @@ -64,8 +64,8 @@ pub fn part1( /// must be sent to other participants. pub fn part2( secret_package: round1::SecretPackage, - round1_packages: &[round1::Package], -) -> Result<(round2::SecretPackage, Vec), Error> { + round1_packages: &HashMap, +) -> Result<(round2::SecretPackage, HashMap), Error> { frost::keys::dkg::part2(secret_package, round1_packages) } @@ -80,8 +80,8 @@ pub fn part2( /// signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, - round1_packages: &[round1::Package], - round2_packages: &[round2::Package], + round1_packages: &HashMap, + round2_packages: &HashMap, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) } diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 7051ba0..94e5c68 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -2,6 +2,8 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] +use std::collections::HashMap; + use k256::{ elliptic_curve::{ group::prime::PrimeCurveAffine, @@ -364,15 +366,11 @@ pub mod round1 { /// /// Generates the signing nonces and commitments to be used in the signing /// operation. - pub fn commit( - participant_identifier: frost::Identifier, - secret: &SigningShare, - rng: &mut RNG, - ) -> (SigningNonces, SigningCommitments) + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where RNG: CryptoRng + RngCore, { - frost::round1::commit::(participant_identifier, secret, rng) + frost::round1::commit::(secret, rng) } } @@ -388,9 +386,6 @@ pub mod round2 { /// shares into the joint signature. pub type SignatureShare = frost::round2::SignatureShare; - /// A representation of a single signature share used in FROST structures and messages. - pub type SignatureResponse = frost::round2::SignatureResponse; - /// Performed once by each participant selected for the signing operation. /// /// Receives the message to be signed and a set of signing commitments and a set @@ -428,7 +423,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-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index c20f4f1..f8f960c 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -1,6 +1,6 @@ //! Generate sample, fixed instances of structs for testing. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use frost_core::{Ciphersuite, Element, Group, Scalar}; use frost_secp256k1::{ @@ -10,7 +10,7 @@ use frost_secp256k1::{ VerifyingShare, }, round1::{NonceCommitment, SigningCommitments}, - round2::{SignatureResponse, SignatureShare}, + round2::SignatureShare, Field, Signature, SigningPackage, VerifyingKey, }; @@ -38,18 +38,14 @@ pub fn signing_commitments() -> SigningCommitments { let serialized_element2 = ::Group::serialize(&element2()); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); - let identifier = 42u16.try_into().unwrap(); - SigningCommitments::new( - identifier, - hiding_nonce_commitment, - binding_nonce_commitment, - ) + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } /// Generate a sample SigningPackage. pub fn signing_package() -> SigningPackage { - let commitments = vec![signing_commitments()]; + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); let message = "hello world".as_bytes(); SigningPackage::new(commitments, message) @@ -57,11 +53,9 @@ 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::deserialize(serialized_scalar).unwrap(); - SignatureShare::new(identifier, signature_response) + SignatureShare::deserialize(serialized_scalar).unwrap() } /// Generate a sample SecretShare. @@ -103,7 +97,6 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()); let serialized_signature = serialized_element @@ -118,14 +111,13 @@ pub fn round1_package() -> round1::Package { VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); let signature = Signature::deserialize(serialized_signature).unwrap(); - round1::Package::new(identifier, vss_commitment, signature) + round1::Package::new(vss_commitment, signature) } /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { - let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - round2::Package::new(identifier, identifier, signing_share) + round2::Package::new(signing_share) } diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index 5c2192f..c70c978 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -19,10 +19,9 @@ use helpers::samples; #[test] fn check_signing_commitments_recreation() { let commitments = samples::signing_commitments(); - let identifier = commitments.identifier(); let hiding = commitments.hiding(); let binding = commitments.binding(); - let new_commitments = SigningCommitments::new(*identifier, *hiding, *binding); + let new_commitments = SigningCommitments::new(*hiding, *binding); assert!(commitments == new_commitments); } @@ -31,14 +30,10 @@ fn check_signing_commitments_recreation() { fn check_signing_package_recreation() { let signing_package = samples::signing_package(); - let commitments = signing_package - .signing_commitments() - .values() - .cloned() - .collect(); + let commitments = signing_package.signing_commitments(); let message = signing_package.message(); - let new_signing_package = SigningPackage::new(commitments, message); + let new_signing_package = SigningPackage::new(commitments.clone(), message); assert!(signing_package == new_signing_package); } @@ -47,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 encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::new(*identifier, *signature_response); + let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); assert!(signature_share == new_signature_share); } @@ -106,11 +100,10 @@ fn check_public_key_package_recreation() { fn check_round1_package_recreation() { let round1_package = samples::round1_package(); - let identifier = round1_package.sender_identifier(); let vss_commitment = round1_package.commitment(); let signature = round1_package.proof_of_knowledge(); - let new_round1_package = round1::Package::new(*identifier, vss_commitment.clone(), *signature); + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); assert!(round1_package == new_round1_package); } @@ -120,12 +113,9 @@ fn check_round1_package_recreation() { fn check_round2_package_recreation() { let round2_package = samples::round2_package(); - let sender_identifier = round2_package.sender_identifier(); - let receiver_identifier = round2_package.receiver_identifier(); let signing_share = round2_package.secret_share(); - let new_round2_package = - round2::Package::new(*sender_identifier, *receiver_identifier, *signing_share); + let new_round2_package = round2::Package::new(*signing_share); assert!(round2_package == new_round2_package); } diff --git a/frost-secp256k1/tests/serde_tests.rs b/frost-secp256k1/tests/serde_tests.rs index 87780fe..7fa8237 100644 --- a/frost-secp256k1/tests/serde_tests.rs +++ b/frost-secp256k1/tests/serde_tests.rs @@ -25,7 +25,6 @@ fn check_signing_commitments_serialization() { assert!(commitments == decoded_commitments); let json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(secp256k1, SHA-256)" @@ -38,7 +37,6 @@ fn check_signing_commitments_serialization() { // Wrong ciphersuite let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(Wrong, SHA-512)" @@ -47,7 +45,6 @@ fn check_signing_commitments_serialization() { // Invalid identifier let invalid_json = r#"{ - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" }"#; @@ -55,8 +52,7 @@ fn check_signing_commitments_serialization() { // Invalid field let invalid_json = r#"{ - "foo": "0000000000000000000000000000000000000000000000000000000000000000", - "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); @@ -70,7 +66,6 @@ fn check_signing_commitments_serialization() { // Extra field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "extra": 1 @@ -94,7 +89,6 @@ fn check_signing_package_serialization() { let json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(secp256k1, SHA-256)" @@ -110,7 +104,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "0000000000000000000000000000000000000000000000000000000000000000": { - "identifier": "0000000000000000000000000000000000000000000000000000000000000000", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(secp256k1, SHA-256)" @@ -125,7 +118,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(secp256k1, SHA-256)" @@ -140,7 +132,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(secp256k1, SHA-256)" } @@ -154,7 +145,6 @@ fn check_signing_package_serialization() { let invalid_json = r#"{ "signing_commitments": { "000000000000000000000000000000000000000000000000000000000000002a": { - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "ciphersuite": "FROST(secp256k1, SHA-256)" @@ -179,8 +169,7 @@ fn check_signature_share_serialization() { assert!(signature_share == decoded_signature_share); let json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "signature": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); @@ -189,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": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", - "ciphersuite": "FROST(secp256k1, SHA-256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; @@ -207,15 +187,13 @@ fn check_signature_share_serialization() { // Missing field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a",, "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "signature": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "extra": 1, "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; @@ -430,7 +408,6 @@ fn check_round1_package_serialization() { assert!(round1_package == decoded_round1_package); let json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" ], @@ -443,20 +420,8 @@ fn check_round1_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "commitment": [ - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" - ], - "proof_of_knowledge": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", - "ciphersuite": "FROST(secp256k1, SHA-256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" ], @@ -467,7 +432,6 @@ fn check_round1_package_serialization() { // Missing field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" ], @@ -477,7 +441,6 @@ fn check_round1_package_serialization() { // Extra field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "commitment": [ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" ], @@ -499,8 +462,6 @@ fn check_round2_package_serialization() { assert!(round2_package == decoded_round2_package); let json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "receiver_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; @@ -510,36 +471,21 @@ fn check_round2_package_serialization() { let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid identifier - let invalid_json = r#"{ - "sender_identifier": "0000000000000000000000000000000000000000000000000000000000000000", - "receiver_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", - "ciphersuite": "FROST(secp256k1, SHA-256)" - }"#; - assert!(serde_json::from_str::(invalid_json).is_err()); - // Invalid field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "foo": "000000000000000000000000000000000000000000000000000000000000002a", - "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Missing field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "ciphersuite": "FROST(secp256k1, SHA-256)" }"#; assert!(serde_json::from_str::(invalid_json).is_err()); // Extra field let invalid_json = r#"{ - "sender_identifier": "000000000000000000000000000000000000000000000000000000000000002a", - "receiver_identifier": "000000000000000000000000000000000000000000000000000000000000002a", "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "extra": 1, "ciphersuite": "FROST(secp256k1, SHA-256)"