From ff41fd6e5735bd1091066d69599f3898ada1b813 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 11 Sep 2024 14:25:41 -0300 Subject: [PATCH] add a verify_signature_share() function --- 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 915ef475..78cc00e5 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -10,6 +10,10 @@ Entries are listed in reverse chronological order. but it's likely to not require any code changes since most ciphersuite implementations are probably just empty structs. The bound makes it possible to use `frost_core::Error` in `Box`. +* 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 fbecfe07..41d5aabf 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -649,37 +649,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"); + } +}