Skip to main content

frost_ed448/
lib.rs

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