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>;