Skip to main content

frost_secp256k1_evm/
lib.rs

1#![no_std]
2#![allow(non_snake_case)]
3#![deny(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc = include_str!("../README.md")]
6#![doc = document_features::document_features!()]
7
8extern crate alloc;
9
10use alloc::collections::BTreeMap;
11
12use frost_rerandomized::RandomizedCiphersuite;
13use k256::{
14    elliptic_curve::{
15        group::prime::PrimeCurveAffine,
16        hash2curve::{hash_to_field, ExpandMsgXmd},
17        sec1::{FromEncodedPoint, ToEncodedPoint},
18        Field as FFField, PrimeField,
19    },
20    AffinePoint, ProjectivePoint, Scalar,
21};
22use rand_core::{CryptoRng, RngCore};
23use sha3::{Digest, Keccak256};
24
25use frost_core as frost;
26
27#[cfg(test)]
28mod tests;
29
30// Re-exports in our public API
31#[cfg(feature = "serde")]
32pub use frost_core::serde;
33pub use frost_core::{Ciphersuite, Field, FieldError, Group, GroupError};
34pub use rand_core;
35
36/// An error.
37pub type Error = frost_core::Error<Secp256K1Keccak256>;
38
39/// An implementation of the FROST(secp256k1, KECCAK-256) ciphersuite scalar field.
40#[derive(Clone, Copy)]
41pub struct Secp256K1ScalarField;
42
43impl Field for Secp256K1ScalarField {
44    type Scalar = Scalar;
45
46    type Serialization = [u8; 32];
47
48    fn zero() -> Self::Scalar {
49        Scalar::ZERO
50    }
51
52    fn one() -> Self::Scalar {
53        Scalar::ONE
54    }
55
56    fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, FieldError> {
57        // [`Scalar`]'s Eq/PartialEq does a constant-time comparison
58        if *scalar == <Self as Field>::zero() {
59            Err(FieldError::InvalidZeroScalar)
60        } else {
61            Ok(scalar.invert().unwrap())
62        }
63    }
64
65    fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
66        Scalar::random(rng)
67    }
68
69    fn serialize(scalar: &Self::Scalar) -> Self::Serialization {
70        scalar.to_bytes().into()
71    }
72
73    fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError> {
74        let field_bytes: &k256::FieldBytes = buf.into();
75        match Scalar::from_repr(*field_bytes).into() {
76            Some(s) => Ok(s),
77            None => Err(FieldError::MalformedScalar),
78        }
79    }
80
81    fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
82        let mut array = Self::serialize(scalar);
83        array.reverse();
84        array
85    }
86}
87
88/// An implementation of the FROST(secp256k1, KECCAK-256) ciphersuite group.
89#[derive(Clone, Copy, PartialEq, Eq)]
90pub struct Secp256K1Group;
91
92impl Group for Secp256K1Group {
93    type Field = Secp256K1ScalarField;
94
95    type Element = ProjectivePoint;
96
97    /// [SEC 1][1] serialization of a compressed point in secp256k1 takes 33 bytes
98    /// (1-byte prefix and 32 bytes for the coordinate).
99    ///
100    /// Note that, in the SEC 1 spec, the identity is encoded as a single null byte;
101    /// but here we pad with zeroes. This is acceptable as the identity _should_ never
102    /// be serialized in FROST, else we error.
103    ///
104    /// [1]: https://secg.org/sec1-v2.pdf
105    type Serialization = [u8; 33];
106
107    fn cofactor() -> <Self::Field as Field>::Scalar {
108        Scalar::ONE
109    }
110
111    fn identity() -> Self::Element {
112        ProjectivePoint::IDENTITY
113    }
114
115    fn generator() -> Self::Element {
116        ProjectivePoint::GENERATOR
117    }
118
119    fn serialize(element: &Self::Element) -> Result<Self::Serialization, GroupError> {
120        if *element == Self::identity() {
121            return Err(GroupError::InvalidIdentityElement);
122        }
123        let mut fixed_serialized = [0; 33];
124        let serialized_point = element.to_affine().to_encoded_point(true);
125        let serialized = serialized_point.as_bytes();
126        fixed_serialized.copy_from_slice(serialized);
127        Ok(fixed_serialized)
128    }
129
130    fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError> {
131        let encoded_point =
132            k256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?;
133
134        match Option::<AffinePoint>::from(AffinePoint::from_encoded_point(&encoded_point)) {
135            Some(point) => {
136                if point.is_identity().into() {
137                    // This is actually impossible since the identity is encoded a a single byte
138                    // which will never happen since we receive a 33-byte buffer.
139                    // We leave the check for consistency.
140                    Err(GroupError::InvalidIdentityElement)
141                } else {
142                    Ok(ProjectivePoint::from(point))
143                }
144            }
145            None => Err(GroupError::MalformedElement),
146        }
147    }
148}
149
150fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] {
151    let mut h = Keccak256::new();
152    for i in inputs {
153        h.update(i);
154    }
155    let mut output = [0u8; 32];
156    output.copy_from_slice(h.finalize().as_ref());
157    output
158}
159
160fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar {
161    let mut u = [Secp256K1ScalarField::zero()];
162    hash_to_field::<ExpandMsgXmd<Keccak256>, Scalar>(&[msg], domain, &mut u)
163        .expect("should never return error according to error cases described in ExpandMsgXmd");
164    u[0]
165}
166
167/// Context string from the ciphersuite in the [spec].
168///
169/// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-1
170const CONTEXT_STRING: &str = "FROST-secp256k1-KECCAK256-v1";
171
172/// An implementation of the FROST(secp256k1, KECCAK-256) ciphersuite.
173#[derive(Clone, Copy, PartialEq, Eq, Debug)]
174pub struct Secp256K1Keccak256;
175
176impl Ciphersuite for Secp256K1Keccak256 {
177    const ID: &'static str = CONTEXT_STRING;
178
179    type Group = Secp256K1Group;
180
181    type HashOutput = [u8; 32];
182
183    type SignatureSerialization = [u8; 65];
184
185    /// H1 for FROST(secp256k1, KECCAK-256)
186    ///
187    /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.2
188    fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
189        hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho"], m)
190    }
191
192    /// H2 for FROST(secp256k1, KECCAK-256)
193    ///
194    /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.4
195    fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
196        hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"chal"], m)
197    }
198
199    /// H3 for FROST(secp256k1, KECCAK-256)
200    ///
201    /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.6
202    fn H3(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
203        hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce"], m)
204    }
205
206    /// H4 for FROST(secp256k1, KECCAK-256)
207    ///
208    /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.8
209    fn H4(m: &[u8]) -> Self::HashOutput {
210        hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m])
211    }
212
213    /// H5 for FROST(secp256k1, KECCAK-256)
214    ///
215    /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.10
216    fn H5(m: &[u8]) -> Self::HashOutput {
217        hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m])
218    }
219
220    /// HDKG for FROST(secp256k1, KECCAK-256)
221    fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
222        Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg"], m))
223    }
224
225    /// HID for FROST(secp256k1, KECCAK-256)
226    fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
227        Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id"], m))
228    }
229}
230
231impl RandomizedCiphersuite for Secp256K1Keccak256 {
232    fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
233        Some(hash_to_scalar(
234            &[CONTEXT_STRING.as_bytes(), b"randomizer"],
235            m,
236        ))
237    }
238}
239
240type S = Secp256K1Keccak256;
241
242/// A FROST(secp256k1, KECCAK-256) participant identifier.
243pub type Identifier = frost::Identifier<S>;
244
245/// FROST(secp256k1, KECCAK-256) keys, key generation, key shares.
246pub mod keys {
247    use super::*;
248
249    /// The identifier list to use when generating key shares.
250    pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, S>;
251
252    /// Allows all participants' keys to be generated using a central, trusted
253    /// dealer.
254    pub fn generate_with_dealer<RNG: RngCore + CryptoRng>(
255        max_signers: u16,
256        min_signers: u16,
257        identifiers: IdentifierList,
258        mut rng: RNG,
259    ) -> Result<(BTreeMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
260        frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng)
261    }
262
263    /// Splits an existing key into FROST shares.
264    ///
265    /// This is identical to [`generate_with_dealer`] but receives an existing key
266    /// instead of generating a fresh one. This is useful in scenarios where
267    /// the key needs to be generated externally or must be derived from e.g. a
268    /// seed phrase.
269    pub fn split<R: RngCore + CryptoRng>(
270        secret: &SigningKey,
271        max_signers: u16,
272        min_signers: u16,
273        identifiers: IdentifierList,
274        rng: &mut R,
275    ) -> Result<(BTreeMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
276        frost::keys::split(secret, max_signers, min_signers, identifiers, rng)
277    }
278
279    /// Recompute the secret from t-of-n secret shares using Lagrange interpolation.
280    ///
281    /// This can be used if for some reason the original key must be restored; e.g.
282    /// if threshold signing is not required anymore.
283    ///
284    /// This is NOT required to sign with FROST; the whole point of FROST is being
285    /// able to generate signatures only using the shares, without having to
286    /// reconstruct the original key.
287    ///
288    /// The caller is responsible for providing at least `min_signers` shares;
289    /// if less than that is provided, a different key will be returned.
290    pub fn reconstruct(secret_shares: &[KeyPackage]) -> Result<SigningKey, Error> {
291        frost::keys::reconstruct(secret_shares)
292    }
293
294    /// Secret and public key material generated by a dealer performing
295    /// [`generate_with_dealer`].
296    ///
297    /// # Security
298    ///
299    /// To derive a FROST(secp256k1, KECCAK-256) keypair, the receiver of the [`SecretShare`] *must* call
300    /// .into(), which under the hood also performs validation.
301    pub type SecretShare = frost::keys::SecretShare<S>;
302
303    /// A secret scalar value representing a signer's share of the group secret.
304    pub type SigningShare = frost::keys::SigningShare<S>;
305
306    /// A public group element that represents a single signer's public verification share.
307    pub type VerifyingShare = frost::keys::VerifyingShare<S>;
308
309    /// A FROST(secp256k1, KECCAK-256) keypair, which can be generated either by a trusted dealer or using
310    /// a DKG.
311    ///
312    /// When using a central dealer, [`SecretShare`]s are distributed to
313    /// participants, who then perform verification, before deriving
314    /// [`KeyPackage`]s, which they store to later use during signing.
315    pub type KeyPackage = frost::keys::KeyPackage<S>;
316
317    /// Public data that contains all the signers' public keys as well as the
318    /// group public key.
319    ///
320    /// Used for verification purposes before publishing a signature.
321    pub type PublicKeyPackage = frost::keys::PublicKeyPackage<S>;
322
323    /// Contains the commitments to the coefficients for our secret polynomial _f_,
324    /// used to generate participants' key shares.
325    ///
326    /// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which
327    /// themselves are scalars) for a secret polynomial f, where f is used to
328    /// generate each ith participant's key share f(i). Participants use this set of
329    /// commitments to perform verifiable secret sharing.
330    ///
331    /// Note that participants MUST be assured that they have the *same*
332    /// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using
333    /// some agreed-upon public location for publication, where each participant can
334    /// ensure that they received the correct (and same) value.
335    pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment<S>;
336
337    pub mod dkg;
338    pub mod refresh;
339    pub mod repairable;
340}
341
342/// FROST(secp256k1, KECCAK-256) Round 1 functionality and types.
343pub mod round1 {
344    use crate::keys::SigningShare;
345
346    use super::*;
347
348    /// Comprised of FROST(secp256k1, KECCAK-256) hiding and binding nonces.
349    ///
350    /// Note that [`SigningNonces`] must be used *only once* for a signing
351    /// operation; re-using nonces will result in leakage of a signer's long-lived
352    /// signing key.
353    pub type SigningNonces = frost::round1::SigningNonces<S>;
354
355    /// Published by each participant in the first round of the signing protocol.
356    ///
357    /// This step can be batched if desired by the implementation. Each
358    /// SigningCommitment can be used for exactly *one* signature.
359    pub type SigningCommitments = frost::round1::SigningCommitments<S>;
360
361    /// A commitment to a signing nonce share.
362    pub type NonceCommitment = frost::round1::NonceCommitment<S>;
363
364    /// Performed once by each participant selected for the signing operation.
365    ///
366    /// Generates the signing nonces and commitments to be used in the signing
367    /// operation.
368    pub fn commit<RNG>(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments)
369    where
370        RNG: CryptoRng + RngCore,
371    {
372        frost::round1::commit::<S, RNG>(secret, rng)
373    }
374}
375
376/// Generated by the coordinator of the signing operation and distributed to
377/// each signing party.
378pub type SigningPackage = frost::SigningPackage<S>;
379
380/// FROST(secp256k1, KECCAK-256) Round 2 functionality and types, for signature share generation.
381pub mod round2 {
382    use super::*;
383
384    /// A FROST(secp256k1, KECCAK-256) participant's signature share, which the Coordinator will aggregate with all other signer's
385    /// shares into the joint signature.
386    pub type SignatureShare = frost::round2::SignatureShare<S>;
387
388    /// Performed once by each participant selected for the signing operation.
389    ///
390    /// Receives the message to be signed and a set of signing commitments and a set
391    /// of randomizing commitments to be used in that signing operation, including
392    /// that for this participant.
393    ///
394    /// Assumes the participant has already determined which nonce corresponds with
395    /// the commitment that was assigned by the coordinator in the SigningPackage.
396    pub fn sign(
397        signing_package: &SigningPackage,
398        signer_nonces: &round1::SigningNonces,
399        key_package: &keys::KeyPackage,
400    ) -> Result<SignatureShare, Error> {
401        frost::round2::sign(signing_package, signer_nonces, key_package)
402    }
403}
404
405/// A Schnorr signature on FROST(secp256k1, KECCAK-256).
406pub type Signature = frost_core::Signature<S>;
407
408/// Verifies each FROST(secp256k1, KECCAK-256) participant's signature share, and if all are valid,
409/// aggregates the shares into a signature to publish.
410///
411/// Resulting signature is compatible with verification of a plain Schnorr
412/// signature.
413///
414/// This operation is performed by a coordinator that can communicate with all
415/// the signing participants before publishing the final signature. The
416/// coordinator can be one of the participants or a semi-trusted third party
417/// (who is trusted to not perform denial of service attacks, but does not learn
418/// any secret information). Note that because the coordinator is trusted to
419/// report misbehaving parties in order to avoid publishing an invalid
420/// signature, if the coordinator themselves is a signer and misbehaves, they
421/// can avoid that step. However, at worst, this results in a denial of
422/// service attack due to publishing an invalid signature.
423pub fn aggregate(
424    signing_package: &SigningPackage,
425    signature_shares: &BTreeMap<Identifier, round2::SignatureShare>,
426    pubkeys: &keys::PublicKeyPackage,
427) -> Result<Signature, Error> {
428    frost::aggregate(signing_package, signature_shares, pubkeys)
429}
430
431/// The type of cheater detection to use.
432pub type CheaterDetection = frost::CheaterDetection;
433
434/// Like [`aggregate()`], but allow specifying a specific cheater detection
435/// strategy.
436pub fn aggregate_custom(
437    signing_package: &SigningPackage,
438    signature_shares: &BTreeMap<Identifier, round2::SignatureShare>,
439    pubkeys: &keys::PublicKeyPackage,
440    cheater_detection: CheaterDetection,
441) -> Result<Signature, Error> {
442    frost::aggregate_custom(
443        signing_package,
444        signature_shares,
445        pubkeys,
446        cheater_detection,
447    )
448}
449
450/// A signing key for a Schnorr signature on FROST(secp256k1, KECCAK-256).
451pub type SigningKey = frost_core::SigningKey<S>;
452
453/// A valid verifying key for Schnorr signatures on FROST(secp256k1, KECCAK-256).
454pub type VerifyingKey = frost_core::VerifyingKey<S>;