diff --git a/src/frost.rs b/src/frost.rs index ae3481fa..2fcbf56c 100644 --- a/src/frost.rs +++ b/src/frost.rs @@ -62,11 +62,12 @@ impl From for Public { /// reconstruct the secret; in this case we use Shamir's secret sharing. #[derive(Clone)] pub struct Share { - receiver_index: u64, + /// The receiver id. + receiver_id: u64, /// Secret Key. - pub(crate) value: Secret, + value: Secret, /// The commitments to be distributed among signers. - pub(crate) commitment: ShareCommitment, + commitment: ShareCommitment, } /// A Jubjub point that is a commitment to one coefficient of our secret @@ -105,8 +106,8 @@ pub struct GroupCommitment(pub(crate) jubjub::AffinePoint); pub struct SharePackage { /// The public signing key that represents the entire group. pub(crate) group_public: VerificationKey, - /// Denotes the participant index each share is owned by. - pub index: u64, + /// Denotes the participant id each share is owned by. + pub id: u64, /// This participant's public key. pub(crate) public: Public, /// This participant's share. @@ -128,7 +129,7 @@ impl TryFrom for KeyPackage { verify_share(&sharepackage.share)?; Ok(KeyPackage { - index: sharepackage.index, + id: sharepackage.id, secret_share: sharepackage.share.value, public: sharepackage.public, group_public: sharepackage.group_public, @@ -144,7 +145,7 @@ impl TryFrom for KeyPackage { /// [`KeyPackage`]s, which they store to later use during signing. #[allow(dead_code)] pub struct KeyPackage { - index: u64, + id: u64, secret_share: Secret, public: Public, group_public: VerificationKey, @@ -189,13 +190,13 @@ pub fn keygen_with_dealer( for share in shares { let signer_public = Public(SpendAuth::basepoint() * share.value.0); sharepackages.push(SharePackage { - index: share.receiver_index, + id: share.receiver_id, share: share.clone(), public: signer_public, group_public, }); - signer_pubkeys.insert(share.receiver_index, signer_public); + signer_pubkeys.insert(share.receiver_id, signer_public); } Ok(( @@ -216,7 +217,7 @@ pub fn keygen_with_dealer( fn verify_share(share: &Share) -> Result<(), &'static str> { let f_result = SpendAuth::basepoint() * share.value.0; - let x = Scalar::from(share.receiver_index as u64); + let x = Scalar::from(share.receiver_id as u64); let (_, result) = share.commitment.0.iter().fold( (Scalar::one(), jubjub::ExtendedPoint::identity()), @@ -230,6 +231,29 @@ fn verify_share(share: &Share) -> Result<(), &'static str> { Ok(()) } +/// Generates the identifier for a given participant. +/// +/// The identifier is generated as follows: +/// id = H("FROST_id", g^s, i), where: +/// - g is the basepoint, +/// - s is the secret chosen by the dealer, and +/// - i is the index of the participant. +/// +/// The actual identifier consists of the first 64 bits of the resulting hash. +fn generate_id(secret: &Secret, index: u8) -> u64 { + use std::convert::TryInto; + + let mut hasher = HStar::default(); + + hasher + .update("FROST_id".as_bytes()) + .update(jubjub::AffinePoint::from(SpendAuth::basepoint() * secret.0).to_bytes()) + .update(index.to_le_bytes()); + + let id_bytes = hasher.finalize().to_bytes(); + + u64::from_le_bytes(id_bytes[0..8].try_into().expect("slice of incorrect size")) +} /// Creates secret shares for a given secret. /// /// This function accepts a secret from which shares are generated. While in @@ -293,18 +317,19 @@ fn generate_shares( // and `coeffs` as the other coefficients at the point x=share_index, // using Horner's method. for index in 1..numshares + 1 { - let scalar_index = Scalar::from(index as u64); + let id = generate_id(secret, index); + let scalar_id = Scalar::from(id); let mut value = Scalar::zero(); // Polynomial evaluation, for this index for i in (0..numcoeffs).rev() { value += &coefficients[i as usize]; - value *= scalar_index; + value *= scalar_id; } value += secret.0; shares.push(Share { - receiver_index: index as u64, + receiver_id: id, value: Secret(value), commitment: commitment.clone(), }); @@ -366,19 +391,19 @@ impl SigningNonces { /// SigningCommitment can be used for exactly *one* signature. #[derive(Copy, Clone)] pub struct SigningCommitments { - /// The participant index - pub(crate) index: u64, + /// The participant id. + id: u64, /// The hiding point. - pub(crate) hiding: jubjub::ExtendedPoint, + hiding: jubjub::ExtendedPoint, /// The binding point. - pub(crate) binding: jubjub::ExtendedPoint, + binding: jubjub::ExtendedPoint, } impl From<(u64, &SigningNonces)> for SigningCommitments { /// For SpendAuth signatures only, not Binding signatures, in RedJubjub/Zcash. - fn from((index, nonces): (u64, &SigningNonces)) -> Self { + fn from((id, nonces): (u64, &SigningNonces)) -> Self { Self { - index, + id, hiding: SpendAuth::basepoint() * nonces.hiding, binding: SpendAuth::basepoint() * nonces.binding, } @@ -405,8 +430,8 @@ pub struct SignatureResponse(pub(crate) Scalar); /// with all other signer's shares into the joint signature. #[derive(Clone, Copy, Default)] pub struct SignatureShare { - /// Represents the participant index. - pub(crate) index: u64, + /// Represents the participant id. + pub(crate) id: u64, /// This participant's signature over the message. pub(crate) signature: SignatureResponse, } @@ -443,14 +468,14 @@ impl SignatureShare { /// perform the first round. Batching entails generating more than one /// nonce/commitment pair at a time. Nonces should be stored in secret storage /// for later use, whereas the commitments are published. - +/// /// The number of nonces is limited to 255. This limit can be increased if it /// turns out to be too conservative. // TODO: Make sure the above is a correct statement, fix if needed in: // https://github.com/ZcashFoundation/redjubjub/issues/111 pub fn preprocess( num_nonces: u8, - participant_index: u64, + participant_id: u64, rng: &mut R, ) -> (Vec, Vec) where @@ -461,7 +486,7 @@ where for _ in 0..num_nonces { let nonces = SigningNonces::new(rng); - signing_commitments.push(SigningCommitments::from((participant_index, &nonces))); + signing_commitments.push(SigningCommitments::from((participant_id, &nonces))); signing_nonces.push(nonces); } @@ -470,7 +495,7 @@ where /// Generates the binding factor that ensures each signature share is strongly /// bound to a signing set, specific set of commitments, and a specific message. -fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> Scalar { +fn gen_rho_i(id: u64, signing_package: &SigningPackage) -> Scalar { // Hash signature message with HStar before deriving the binding factor. // // To avoid a collision with other inputs to the hash that generates the @@ -484,11 +509,11 @@ fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> Scalar { let mut hasher = HStar::default(); hasher .update("FROST_rho".as_bytes()) - .update(index.to_be_bytes()) + .update(id.to_be_bytes()) .update(message_hash.to_bytes()); for item in signing_package.signing_commitments.iter() { - hasher.update(item.index.to_be_bytes()); + hasher.update(item.id.to_be_bytes()); let hiding_bytes = jubjub::AffinePoint::from(item.hiding).to_bytes(); hasher.update(hiding_bytes); let binding_bytes = jubjub::AffinePoint::from(item.binding).to_bytes(); @@ -515,8 +540,8 @@ fn gen_group_commitment( } let rho_i = bindings - .get(&commitment.index) - .ok_or("No matching commitment index")?; + .get(&commitment.id) + .ok_or("No matching commitment id")?; accumulator += commitment.hiding + (commitment.binding * rho_i) } @@ -540,17 +565,17 @@ fn gen_challenge( /// Generates the lagrange coefficient for the i'th participant. fn gen_lagrange_coeff( - signer_index: u64, + signer_id: u64, signing_package: &SigningPackage, ) -> Result { let mut num = Scalar::one(); let mut den = Scalar::one(); for commitment in signing_package.signing_commitments.iter() { - if commitment.index == signer_index { + if commitment.id == signer_id { continue; } - num *= Scalar::from(commitment.index as u64); - den *= Scalar::from(commitment.index as u64) - Scalar::from(signer_index as u64); + num *= Scalar::from(commitment.id as u64); + den *= Scalar::from(commitment.id as u64) - Scalar::from(signer_id as u64); } if den == Scalar::zero() { @@ -580,11 +605,11 @@ pub fn sign( HashMap::with_capacity(signing_package.signing_commitments.len()); for comm in signing_package.signing_commitments.iter() { - let rho_i = gen_rho_i(comm.index, &signing_package); - bindings.insert(comm.index, rho_i); + let rho_i = gen_rho_i(comm.id, &signing_package); + bindings.insert(comm.id, rho_i); } - let lambda_i = gen_lagrange_coeff(share_package.index, &signing_package)?; + let lambda_i = gen_lagrange_coeff(share_package.id, &signing_package)?; let group_commitment = gen_group_commitment(&signing_package, &bindings)?; @@ -595,7 +620,7 @@ pub fn sign( ); let participant_rho_i = bindings - .get(&share_package.index) + .get(&share_package.id) .ok_or("No matching binding!")?; // The Schnorr signature share @@ -604,7 +629,7 @@ pub fn sign( + (lambda_i * share_package.share.value.0 * challenge); Ok(SignatureShare { - index: share_package.index, + id: share_package.id, signature: SignatureResponse(signature), }) } @@ -633,8 +658,8 @@ pub fn aggregate( HashMap::with_capacity(signing_package.signing_commitments.len()); for comm in signing_package.signing_commitments.iter() { - let rho_i = gen_rho_i(comm.index, &signing_package); - bindings.insert(comm.index, rho_i); + let rho_i = gen_rho_i(comm.id, &signing_package); + bindings.insert(comm.id, rho_i); } let group_commitment = gen_group_commitment(&signing_package, &bindings)?; @@ -642,16 +667,16 @@ pub fn aggregate( let challenge = gen_challenge(&signing_package, &group_commitment, &pubkeys.group_public); for signing_share in signing_shares { - let signer_pubkey = pubkeys.signer_pubkeys[&signing_share.index]; - let lambda_i = gen_lagrange_coeff(signing_share.index, &signing_package)?; + let signer_pubkey = pubkeys.signer_pubkeys[&signing_share.id]; + let lambda_i = gen_lagrange_coeff(signing_share.id, &signing_package)?; let signer_commitment = signing_package .signing_commitments .iter() - .find(|comm| comm.index == signing_share.index) + .find(|comm| comm.id == signing_share.id) .ok_or("No matching signing commitment for signer")?; let commitment_i = - signer_commitment.hiding + (signer_commitment.binding * bindings[&signing_share.index]); + signer_commitment.hiding + (signer_commitment.binding * bindings[&signing_share.id]); signing_share.check_is_valid(&signer_pubkey, lambda_i, commitment_i, challenge)?; } @@ -691,9 +716,9 @@ mod tests { if j == i { continue; } - num *= Scalar::from(shares[j].receiver_index as u64); - den *= Scalar::from(shares[j].receiver_index as u64) - - Scalar::from(shares[i].receiver_index as u64); + num *= Scalar::from(shares[j].receiver_id as u64); + den *= Scalar::from(shares[j].receiver_id as u64) + - Scalar::from(shares[i].receiver_id as u64); } if den == Scalar::zero() { return Err("Duplicate shares provided"); diff --git a/tests/frost.rs b/tests/frost.rs index fa47b767..315d9c2c 100644 --- a/tests/frost.rs +++ b/tests/frost.rs @@ -15,11 +15,12 @@ fn check_sign_with_dealer() { let mut commitments: Vec = Vec::with_capacity(threshold as usize); // Round 1, generating nonces and signing commitments for each participant. - for participant_index in 1..(threshold + 1) { + // Participants are represented by shares. + for share in &shares { // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. - let (nonce, commitment) = frost::preprocess(1, participant_index as u64, &mut rng); - nonces.insert(participant_index as u64, nonce); + let (nonce, commitment) = frost::preprocess(1, share.id, &mut rng); + nonces.insert(share.id, nonce); commitments.push(commitment[0]); } @@ -34,10 +35,10 @@ fn check_sign_with_dealer() { }; // Round 2: each participant generates their signature share - for (participant_index, nonce) in nonces { + for (participant_id, nonce) in nonces { let share_package = shares .iter() - .find(|share| participant_index == share.index) + .find(|share| participant_id == share.id) .unwrap(); let nonce_to_use = nonce[0]; // Each participant generates their signature share.