frost_evm/
lib.rs

1use frost_core::round2::compute_signature_share;
2use frost_core::{
3    compute_binding_factor_list, compute_group_commitment, derive_interpolating_value, Challenge,
4};
5use frost_secp256k1::keys::{KeyPackage, PublicKeyPackage};
6use frost_secp256k1::round1::SigningNonces;
7use frost_secp256k1::round2::SignatureShare;
8use std::collections::HashMap;
9
10pub type Scalar = frost_core::Scalar<frost_secp256k1::Secp256K1Sha256>;
11#[cfg(feature = "serde")]
12pub type ScalarSerialization =
13    frost_core::serialization::ScalarSerialization<frost_secp256k1::Secp256K1Sha256>;
14
15pub use frost_core;
16pub use frost_secp256k1;
17pub use frost_secp256k1::round1;
18pub use frost_secp256k1::{Error, Identifier, SigningKey, SigningPackage};
19pub use schnorr_evm as schnorr;
20pub use schnorr_evm::*;
21
22/// FROST(secp256k1, SHA-256) keys, key generation, key shares.
23pub mod keys {
24    pub use frost_secp256k1::keys::*;
25}
26
27pub mod round2 {
28    use super::*;
29
30    /// Performed once by each participant selected for the signing operation.
31    ///
32    /// Implements [`sign`] from the spec.
33    ///
34    /// Receives the message to be signed and a set of signing commitments and a set
35    /// of randomizing commitments to be used in that signing operation, including
36    /// that for this participant.
37    ///
38    /// Assumes the participant has already determined which nonce corresponds with
39    /// the commitment that was assigned by the coordinator in the SigningPackage.
40    ///
41    /// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-two-signature-share-g
42    pub fn sign(
43        signing_package: &SigningPackage,
44        signer_nonces: &SigningNonces,
45        key_package: &KeyPackage,
46    ) -> Result<SignatureShare, Error> {
47        if signing_package.signing_commitments().len() < *key_package.min_signers() as usize {
48            return Err(Error::IncorrectNumberOfCommitments);
49        }
50
51        // Validate the signer's commitment is present in the signing package
52        let commitment = signing_package
53            .signing_commitments()
54            .get(key_package.identifier())
55            .ok_or(Error::MissingCommitment)?;
56
57        // Validate if the signer's commitment exists
58        if commitment != &signer_nonces.into() {
59            return Err(Error::IncorrectCommitment);
60        }
61
62        // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
63        // binding factor.
64        let binding_factor_list =
65            compute_binding_factor_list(signing_package, key_package.verifying_key(), &[]);
66        let binding_factor = binding_factor_list
67            .get(key_package.identifier())
68            .ok_or(Error::UnknownIdentifier)?
69            .clone();
70
71        // Compute the group commitment from signing commitments produced in round one.
72        let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
73
74        // Compute Lagrange coefficient.
75        let lambda_i = derive_interpolating_value(key_package.identifier(), signing_package)?;
76
77        // Compute the per-message challenge.
78        // NOTE: here we diverge from frost by using a different challenge format.
79        let group_public = VerifyingKey::new(key_package.verifying_key().to_element());
80        let challenge = Challenge::from_scalar(group_public.challenge(
81            VerifyingKey::message_hash(signing_package.message().as_slice()),
82            group_commitment.to_element(),
83        ));
84
85        // Compute the Schnorr signature share.
86        let signature_share = compute_signature_share(
87            signer_nonces,
88            binding_factor,
89            lambda_i,
90            key_package,
91            challenge,
92        );
93
94        Ok(signature_share)
95    }
96
97    pub use frost_secp256k1::round2::SignatureShare;
98}
99
100////////////////////////////////////////////////////////////////////////////////
101// Aggregation
102////////////////////////////////////////////////////////////////////////////////
103
104/// Aggregates the signature shares to produce a final signature that
105/// can be verified with the group public key.
106///
107/// `signature_shares` maps the identifier of each participant to the
108/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping
109/// the coordinator has between communication channels and participants, i.e.
110/// they must have assurance that the [`round2::SignatureShare`] came from
111/// the participant with that identifier.
112///
113/// This operation is performed by a coordinator that can communicate with all
114/// the signing participants before publishing the final signature. The
115/// coordinator can be one of the participants or a semi-trusted third party
116/// (who is trusted to not perform denial of service attacks, but does not learn
117/// any secret information). Note that because the coordinator is trusted to
118/// report misbehaving parties in order to avoid publishing an invalid
119/// signature, if the coordinator themselves is a signer and misbehaves, they
120/// can avoid that step. However, at worst, this results in a denial of
121/// service attack due to publishing an invalid signature.
122pub fn aggregate(
123    signing_package: &SigningPackage,
124    signature_shares: &HashMap<Identifier, SignatureShare>,
125    pubkeys: &PublicKeyPackage,
126) -> Result<Signature, Error> {
127    // Check if signing_package.signing_commitments and signature_shares have
128    // the same set of identifiers, and if they are all in pubkeys.verifying_shares.
129    if signing_package.signing_commitments().len() != signature_shares.len() {
130        return Err(Error::UnknownIdentifier);
131    }
132    if !signing_package.signing_commitments().keys().all(|id| {
133        return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id);
134    }) {
135        return Err(Error::UnknownIdentifier);
136    }
137
138    // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
139    // binding factor.
140    let binding_factor_list =
141        compute_binding_factor_list(signing_package, pubkeys.verifying_key(), &[]);
142
143    // Compute the group commitment from signing commitments produced in round one.
144    let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
145
146    // The aggregation of the signature shares by summing them up, resulting in
147    // a plain Schnorr signature.
148    //
149    // Implements [`aggregate`] from the spec.
150    //
151    // [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3
152    let mut z = Scalar::ZERO;
153
154    for signature_share in signature_shares.values() {
155        z += signature_share.share();
156    }
157
158    let group_public = VerifyingKey::new(pubkeys.verifying_key().to_element());
159    let challenge = group_public.challenge(
160        VerifyingKey::message_hash(signing_package.message().as_slice()),
161        group_commitment.to_element(),
162    );
163
164    let signature = Signature::new(challenge, z);
165
166    // Verify the aggregate signature
167    let verification_result = group_public
168        .verify(signing_package.message(), &signature)
169        .map_err(|_| Error::MalformedSignature);
170
171    // Only if the verification of the aggregate signature failed; verify each share to find the cheater.
172    // This approach is more efficient since we don't need to verify all shares
173    // if the aggregate signature is valid (which should be the common case).
174    if let Err(err) = verification_result {
175        // Verify the signature shares.
176        for (signature_share_identifier, signature_share) in signature_shares {
177            // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_,
178            // and where s[i] is a secret share of the constant term of _f_, the secret polynomial.
179            let signer_pubkey = pubkeys
180                .verifying_shares()
181                .get(signature_share_identifier)
182                .unwrap();
183
184            // Compute Lagrange coefficient.
185            let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?;
186
187            let binding_factor = binding_factor_list
188                .get(signature_share_identifier)
189                .ok_or(Error::UnknownIdentifier)?
190                .clone();
191
192            // Compute the commitment share.
193            let r_share = signing_package
194                .signing_commitment(signature_share_identifier)
195                .ok_or(Error::UnknownIdentifier)?
196                .to_group_commitment_share(&binding_factor);
197
198            // Compute relation values to verify this signature share.
199            signature_share.verify(
200                *signature_share_identifier,
201                &r_share,
202                signer_pubkey,
203                lambda_i,
204                &Challenge::from_scalar(challenge),
205            )?;
206        }
207
208        // We should never reach here; but we return the verification error to be safe.
209        return Err(err);
210    }
211
212    Ok(signature)
213}