frost_ristretto255/
lib.rs

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