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