//! An implementation of FROST (Flexible Round-Optimized Schnorr Threshold) //! signatures. //! //! If you are interested in deploying FROST, please do not hesitate to consult the FROST authors. //! //! This implementation currently only supports key generation using a central //! dealer. In the future, we will add support for key generation via a DKG, //! as specified in the FROST paper. //! //! Internally, generate_with_dealer generates keys using Verifiable Secret //! Sharing, where shares are generated using Shamir Secret Sharing. use std::{ collections::{BTreeMap, BTreeSet, HashMap}, fmt::{self, Debug}, ops::Index, }; use derive_getters::Getters; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; mod identifier; pub mod keys; pub mod round1; pub mod round2; use crate::{ scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, Error, Field, Group, Scalar, Signature, VerifyingKey, }; pub use self::identifier::Identifier; /// The binding factor, also known as _rho_ (ρ) /// /// Ensures each signature share is strongly bound to a signing set, specific set /// of commitments, and a specific message. /// /// #[derive(Clone, PartialEq, Eq)] pub struct BindingFactor(Scalar); impl BindingFactor where C: Ciphersuite, { /// Deserializes [`BindingFactor`] from bytes. pub fn deserialize( bytes: <::Field as Field>::Serialization, ) -> Result> { <::Field>::deserialize(&bytes) .map(|scalar| Self(scalar)) .map_err(|e| e.into()) } /// Serializes [`BindingFactor`] to bytes. pub fn serialize(&self) -> <::Field as Field>::Serialization { <::Field>::serialize(&self.0) } } impl Debug for BindingFactor where C: Ciphersuite, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("BindingFactor") .field(&hex::encode(self.serialize())) .finish() } } /// A list of binding factors and their associated identifiers. #[derive(Clone)] pub struct BindingFactorList(BTreeMap, BindingFactor>); impl BindingFactorList where C: Ciphersuite, { /// Create a new [`BindingFactorList`] from a vector of binding factors. #[cfg(feature = "internals")] pub fn new(binding_factors: BTreeMap, BindingFactor>) -> Self { Self(binding_factors) } /// Return iterator through all factors. pub fn iter(&self) -> impl Iterator, &BindingFactor)> { self.0.iter() } } impl Index> for BindingFactorList where C: Ciphersuite, { type Output = BindingFactor; // Get the binding factor of a participant in the list. // // [`binding_factor_for_participant`] in the spec // // [`binding_factor_for_participant`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-4.3 fn index(&self, identifier: Identifier) -> &Self::Output { &self.0[&identifier] } } /// [`compute_binding_factors`] in the spec /// /// [`compute_binding_factors`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-10.html#section-4.4 #[cfg_attr(feature = "internals", visibility::make(pub))] pub(crate) fn compute_binding_factor_list( signing_package: &SigningPackage, group_public: &VerifyingKey, additional_prefix: &[u8], ) -> BindingFactorList where C: Ciphersuite, { let preimages = signing_package.binding_factor_preimages(group_public, additional_prefix); BindingFactorList( preimages .iter() .map(|(identifier, preimage)| { let binding_factor = C::H1(preimage); (*identifier, BindingFactor(binding_factor)) }) .collect(), ) } #[cfg(any(test, feature = "test-impl"))] impl FromHex for BindingFactor where C: Ciphersuite, { type Error = &'static str; fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; match v.try_into() { Ok(bytes) => Self::deserialize(bytes).map_err(|_| "malformed scalar encoding"), Err(_) => Err("malformed scalar encoding"), } } } /// Generates a lagrange coefficient. /// /// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k /// is ∑_{i=0}^k y_i.ℓ_i(x), where ℓ_i(x) is the Lagrange basis polynomial: /// /// ℓ_i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j). /// /// This computes ℓ_j(x) for the set of points `xs` and for the j corresponding /// to the given xj. /// /// If `x` is None, it uses 0 for it (since Identifiers can't be 0) #[cfg_attr(feature = "internals", visibility::make(pub))] fn compute_lagrange_coefficient( x_set: &BTreeSet>, x: Option>, x_i: Identifier, ) -> Result, Error> { if x_set.is_empty() { return Err(Error::IncorrectNumberOfIdentifiers); } let mut num = <::Field>::one(); let mut den = <::Field>::one(); let mut x_i_found = false; for x_j in x_set.iter() { if x_i == *x_j { x_i_found = true; continue; } if let Some(x) = x { num *= x - *x_j; den *= x_i - *x_j; } else { // Both signs inverted just to avoid requiring Neg (-*xj) num *= *x_j; den *= *x_j - x_i; } } if !x_i_found { return Err(Error::UnknownIdentifier); } Ok(num * <::Field>::invert(&den).map_err(|_| Error::DuplicatedIdentifiers)?) } /// Generates the lagrange coefficient for the i'th participant (for `signer_id`). #[cfg_attr(feature = "internals", visibility::make(pub))] fn derive_interpolating_value( signer_id: &Identifier, signing_package: &SigningPackage, ) -> Result, Error> { compute_lagrange_coefficient( &signing_package .signing_commitments() .keys() .cloned() .collect(), None, *signer_id, ) } /// Generated by the coordinator of the signing operation and distributed to /// each signing party #[derive(Clone, Debug, PartialEq, Eq, Getters)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. signing_commitments: BTreeMap, round1::SigningCommitments>, /// Message which each participant will sign. /// /// Each signer should perform protocol-specific verification on the /// message. #[cfg_attr( feature = "serde", serde( serialize_with = "serdect::slice::serialize_hex_lower_or_bin", deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" ) )] message: Vec, /// Ciphersuite ID for serialization #[cfg_attr( feature = "serde", serde(serialize_with = "crate::ciphersuite_serialize::<_, C>") )] #[cfg_attr( feature = "serde", serde(deserialize_with = "crate::ciphersuite_deserialize::<_, C>") )] #[getter(skip)] ciphersuite: (), } impl SigningPackage where C: Ciphersuite, { /// Create a new `SigningPackage` /// /// The `signing_commitments` are sorted by participant `identifier`. pub fn new( signing_commitments: BTreeMap, round1::SigningCommitments>, message: &[u8], ) -> SigningPackage { SigningPackage { signing_commitments, message: message.to_vec(), ciphersuite: (), } } /// Get a signing commitment by its participant identifier. pub fn signing_commitment(&self, identifier: &Identifier) -> round1::SigningCommitments { self.signing_commitments[identifier] } /// Compute the preimages to H1 to compute the per-signer binding factors // We separate this out into its own method so it can be tested #[cfg_attr(feature = "internals", visibility::make(pub))] pub fn binding_factor_preimages( &self, group_public: &VerifyingKey, additional_prefix: &[u8], ) -> Vec<(Identifier, Vec)> { let mut binding_factor_input_prefix = vec![]; // The length of a serialized verifying key of the same cipersuite does // not change between runs of the protocol, so we don't need to hash to // get a fixed length. // // TODO: when serde serialization merges, change this to be simpler? binding_factor_input_prefix.extend_from_slice(group_public.serialize().as_ref()); // The message is hashed with H4 to force the variable-length message // into a fixed-length byte string, same for hashing the variable-sized // (between runs of the protocol) set of group commitments, but with H5. binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref()); binding_factor_input_prefix.extend_from_slice( C::H5(&round1::encode_group_commitments(self.signing_commitments())[..]).as_ref(), ); binding_factor_input_prefix.extend_from_slice(additional_prefix); self.signing_commitments() .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(identifier.serialize().as_ref()); (*identifier, binding_factor_input) }) .collect() } } /// The product of all signers' individual commitments, published as part of the /// final signature. #[derive(Clone, PartialEq, Eq)] pub struct GroupCommitment(pub(super) Element); impl GroupCommitment where C: Ciphersuite, { /// Return the underlying element. #[cfg(feature = "internals")] pub fn to_element(self) -> ::Element { self.0 } } /// Generates the group commitment which is published as part of the joint /// Schnorr signature. /// /// Implements [`compute_group_commitment`] from the spec. /// /// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-10.html#section-4.5 #[cfg_attr(feature = "internals", visibility::make(pub))] fn compute_group_commitment( signing_package: &SigningPackage, binding_factor_list: &BindingFactorList, ) -> Result, Error> where C: Ciphersuite, { let identity = ::identity(); let mut group_commitment = ::identity(); // Number of signing participants we are iterating over. let n = signing_package.signing_commitments().len(); let mut binding_scalars = Vec::with_capacity(n); let mut binding_elements = Vec::with_capacity(n); // 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_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(); // Collect the binding commitments and their binding factors for one big // multiscalar multiplication at the end. binding_elements.push(commitment.binding.0); binding_scalars.push(binding_factor.0); group_commitment = group_commitment + commitment.hiding.0; } let accumulated_binding_commitment: Element = VartimeMultiscalarMul::::vartime_multiscalar_mul(binding_scalars, binding_elements); group_commitment = group_commitment + accumulated_binding_commitment; Ok(GroupCommitment(group_commitment)) } //////////////////////////////////////////////////////////////////////////////// // Aggregation //////////////////////////////////////////////////////////////////////////////// /// 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 /// (who is trusted to not perform denial of service attacks, but does not learn /// any secret information). Note that because the coordinator is trusted to /// report misbehaving parties in order to avoid publishing an invalid /// signature, if the coordinator themselves is a signer and misbehaves, they /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. pub fn aggregate( signing_package: &SigningPackage, signature_shares: &HashMap, round2::SignatureShare>, pubkeys: &keys::PublicKeyPackage, ) -> Result, Error> where C: Ciphersuite, { // 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, &pubkeys.group_public, &[]); // Compute the group commitment from signing commitments produced in round one. let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; // The aggregation of the signature shares by summing them up, resulting in // a plain Schnorr signature. // // Implements [`aggregate`] from the spec. // // [`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.values() { z = z + signature_share.share; } let signature = Signature { R: group_commitment.0, z, }; // Verify the aggregate signature let verification_result = pubkeys .group_public .verify(signing_package.message(), &signature); // Only if the verification of the aggregate signature failed; verify each share to find the cheater. // This approach is more efficient since we don't need to verify all shares // if the aggregate signature is valid (which should be the common case). if let Err(err) = verification_result { // Compute the per-message challenge. let challenge = crate::challenge::( &group_commitment.0, &pubkeys.group_public.element, signing_package.message().as_slice(), ); // Verify the 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) .unwrap(); // Compute Lagrange coefficient. let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; let binding_factor = binding_factor_list[*signature_share_identifier].clone(); // Compute the commitment share. let R_share = signing_package .signing_commitment(signature_share_identifier) .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, )?; } // We should never reach here; but we return the verification error to be safe. return Err(err); } Ok(signature) }