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