From b1f946f3ef974dc9b650a50e95934fa6ab9439c9 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 15 Oct 2024 10:39:15 -0300 Subject: [PATCH] add a verify_signature_share() function (#727) --- frost-core/CHANGELOG.md | 4 + frost-core/src/lib.rs | 106 +++++++++++++++----- frost-core/src/tests/ciphersuite_generic.rs | 35 +++++++ 3 files changed, 122 insertions(+), 23 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 43f03eb0..1ca9cc41 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -11,6 +11,10 @@ Entries are listed in reverse chronological order. implementations are probably just empty structs. The bound makes it possible to use `frost_core::Error` in `Box`. * Added getters to `round1::SecretPackage` and `round2::SecretPackage`. +* Added a `frost_core::verify_signature_share()` function which allows verifying + individual signature shares. This is not required for regular FROST usage but + might useful in certain situations where it is desired to verify each + individual signature share before aggregating the signature. ## 2.0.0-rc.0 diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 1f4e2828..b6914e19 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -650,37 +650,97 @@ fn detect_cheater( )?; // Verify the signature shares. - for (signature_share_identifier, signature_share) in signature_shares { + for (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 + let verifying_share = pubkeys .verifying_shares - .get(signature_share_identifier) + .get(identifier) .ok_or(Error::UnknownIdentifier)?; - // Compute Lagrange coefficient. - let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; - - let binding_factor = binding_factor_list - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute the commitment share. - let R_share = signing_package - .signing_commitment(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)? - .to_group_commitment_share(binding_factor); - - // Compute relation values to verify this signature share. - signature_share.verify( - *signature_share_identifier, - &R_share, - signer_pubkey, - lambda_i, - &challenge, + verify_signature_share_precomputed( + *identifier, + signing_package, + binding_factor_list, + signature_share, + verifying_share, + challenge, )?; } // We should never reach here; but we return an error to be safe. Err(Error::InvalidSignature) } + +/// Verify a signature share for the given participant `identifier`, +/// `verifying_share` and `signature_share`; with the `signing_package` +/// for which the signature share was produced and with the group's +/// `verifying_key`. +/// +/// This is not required for regular FROST usage but might useful in certain +/// situations where it is desired to verify each individual signature share +/// before aggregating the signature. +pub fn verify_signature_share( + identifier: Identifier, + verifying_share: &keys::VerifyingShare, + signature_share: &round2::SignatureShare, + signing_package: &SigningPackage, + verifying_key: &VerifyingKey, +) -> Result<(), Error> { + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the + // binding factor. + let binding_factor_list: BindingFactorList = + compute_binding_factor_list(signing_package, verifying_key, &[])?; + // Compute the group commitment from signing commitments produced in round one. + let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + + // Compute the per-message challenge. + let challenge = crate::challenge::( + &group_commitment.to_element(), + verifying_key, + signing_package.message().as_slice(), + )?; + + verify_signature_share_precomputed( + identifier, + signing_package, + &binding_factor_list, + signature_share, + verifying_share, + challenge, + ) +} + +/// Similar to [`verify_signature_share()`] but using a precomputed +/// `binding_factor_list` and `challenge`. +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +fn verify_signature_share_precomputed( + signature_share_identifier: Identifier, + signing_package: &SigningPackage, + binding_factor_list: &BindingFactorList, + signature_share: &round2::SignatureShare, + verifying_share: &keys::VerifyingShare, + challenge: Challenge, +) -> Result<(), Error> { + let lambda_i = derive_interpolating_value(&signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(&signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + let R_share = signing_package + .signing_commitment(&signature_share_identifier) + .ok_or(Error::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + signature_share.verify( + signature_share_identifier, + &R_share, + verifying_share, + lambda_i, + &challenge, + )?; + + Ok(()) +} diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 4528e9b7..df23fb8a 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -263,6 +263,8 @@ pub fn check_sign( signature_shares.clone(), ); + check_verify_signature_share(&pubkey_package, &signing_package, &signature_shares); + // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; @@ -860,3 +862,36 @@ fn check_verifying_shares( assert_eq!(e.culprit(), Some(id)); assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); } + +// Checks if `verify_signature_share()` works correctly. +fn check_verify_signature_share( + pubkeys: &PublicKeyPackage, + signing_package: &SigningPackage, + signature_shares: &BTreeMap, SignatureShare>, +) { + for (identifier, signature_share) in signature_shares { + frost::verify_signature_share( + *identifier, + pubkeys.verifying_shares().get(identifier).unwrap(), + signature_share, + signing_package, + pubkeys.verifying_key(), + ) + .expect("should pass"); + } + + for (identifier, signature_share) in signature_shares { + let one = <::Group as Group>::Field::one(); + // Corrupt share + let signature_share = SignatureShare::new(signature_share.to_scalar() + one); + + frost::verify_signature_share( + *identifier, + pubkeys.verifying_shares().get(identifier).unwrap(), + &signature_share, + signing_package, + pubkeys.verifying_key(), + ) + .expect_err("should have failed"); + } +}