//! FROST Round 2 functionality and types, for signature share generation use std::fmt::{self, Debug}; use crate::{ challenge, frost::{self, round1, *}, Challenge, Ciphersuite, Error, Field, Group, }; /// A representation of a single signature share used in FROST structures and messages. #[derive(Clone, Copy)] pub struct SignatureResponse { /// The [`Scalar`] contribution to the group signature. pub z_share: <::Field as Field>::Scalar, } impl SignatureResponse where C: Ciphersuite, { /// Deserialize [`SignatureResponse`] from bytes pub fn from_bytes( bytes: <::Field as Field>::Serialization, ) -> Result { <::Field as Field>::deserialize(&bytes) .map(|scalar| Self { z_share: scalar }) } /// Serialize [`SignatureResponse`] to bytes pub fn to_bytes(&self) -> <::Field as Field>::Serialization { <::Field as Field>::serialize(&self.z_share) } } 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.to_bytes())) .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 } } /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. #[derive(Clone, Copy, Eq, PartialEq)] pub struct SignatureShare { /// Represents the participant index. pub index: u16, /// This participant's signature over the message. pub signature: SignatureResponse, } impl SignatureShare where C: Ciphersuite, { /// Gets the participant index associated with this [`SignatureShare`]. pub fn index(&self) -> &u16 { &self.index } /// 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-03.html#section-5.3 pub fn verify( &self, group_commitment_share: &round1::GroupCommitmentShare, public_key: &frost::keys::Public, lambda_i: <::Field as Field>::Scalar, challenge: &Challenge, ) -> Result<(), &'static str> { if (::generator() * self.signature.z_share) != (group_commitment_share.0 + (public_key.0 * challenge.0 * lambda_i)) { return Err("Invalid signature share"); } Ok(()) } } impl Debug for SignatureShare where C: Ciphersuite, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("SignatureShare") .field("index", &self.index) .field("signature", &self.signature) .finish() } } // // Zeroizes `SignatureShare` to be the `Default` value on drop (when it goes out // // of scope). Luckily the derived `Default` includes the `Default` impl of // // Scalar, which is four 0u64's under the hood, and u16, which is // // 0u16. // impl DefaultIsZeroes for SignatureShare {} /// Performed once by each participant selected for the signing operation. /// /// Implements [`sign`] from the spec. /// /// Receives the message to be signed and a set of signing commitments and a set /// of randomizing commitments to be used in that signing operation, including /// that for this participant. /// /// Assumes the participant has already determined which nonce corresponds with /// the commitment that was assigned by the coordinator in the SigningPackage. /// /// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.2 pub fn sign( signing_package: &SigningPackage, signer_nonces: &round1::SigningNonces, key_package: &frost::keys::KeyPackage, ) -> Result, &'static str> { // Encodes the signing commitment list produced in round one as part of generating [`Rho`], the // binding factor. let rho: frost::Rho = signing_package.into(); // Compute the group commitment from signing commitments produced in round one. let group_commitment = GroupCommitment::::try_from(signing_package)?; // Compute Lagrange coefficient. let lambda_i = frost::derive_lagrange_coeff(*key_package.index(), signing_package)?; // Compute the per-message challenge. let challenge = challenge::( &group_commitment.0, &key_package.group_public.element, signing_package.message.as_slice(), ); // Compute the Schnorr signature share. let z_share: <::Field as Field>::Scalar = signer_nonces.hiding.0 + (signer_nonces.binding.0 * rho.0) + (lambda_i * key_package.secret_share.0 * challenge.0); let signature_share = SignatureShare:: { index: *key_package.index(), signature: SignatureResponse:: { z_share }, }; Ok(signature_share) }