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