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