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}