frost_ed448/
lib.rs

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