Skip to main content

frost_core/
round1.rs

1//! FROST Round 1 functionality and types
2// Remove after https://github.com/rust-lang/rust/issues/147648 is fixed
3#![allow(unused_assignments)]
4
5use alloc::{
6    collections::BTreeMap,
7    fmt::{self, Debug},
8    string::ToString,
9    vec::Vec,
10};
11
12use derive_getters::Getters;
13#[cfg(any(test, feature = "test-impl"))]
14use hex::FromHex;
15
16use rand_core::{CryptoRng, RngCore};
17use zeroize::{Zeroize, ZeroizeOnDrop};
18
19use crate::{
20    serialization::{SerializableElement, SerializableScalar},
21    Ciphersuite, Element, Error, Field, Group, Header,
22};
23
24#[cfg(feature = "serialization")]
25use crate::serialization::{Deserialize, Serialize};
26
27use super::{keys::SigningShare, Identifier};
28
29/// A scalar that is a signing nonce.
30#[derive(Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
33#[cfg_attr(feature = "serde", serde(transparent))]
34pub struct Nonce<C: Ciphersuite>(pub(super) SerializableScalar<C>);
35
36impl<C> Nonce<C>
37where
38    C: Ciphersuite,
39{
40    /// Generates a new uniformly random signing nonce by sourcing fresh randomness and combining
41    /// with the secret signing share, to hedge against a bad RNG.
42    ///
43    /// Each participant generates signing nonces before performing a signing
44    /// operation.
45    ///
46    /// An implementation of `nonce_generate(secret)` from the [spec].
47    ///
48    /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#name-nonce-generation
49    pub fn new<R>(secret: &SigningShare<C>, rng: &mut R) -> Self
50    where
51        R: CryptoRng + RngCore,
52    {
53        let mut random_bytes = [0; 32];
54        rng.fill_bytes(&mut random_bytes[..]);
55
56        Self::nonce_generate_from_random_bytes(secret, random_bytes)
57    }
58
59    /// Create a nonce from a scalar.
60    #[cfg_attr(feature = "internals", visibility::make(pub))]
61    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
62    fn from_scalar(scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar) -> Self {
63        Self(SerializableScalar(scalar))
64    }
65
66    /// Convert a nonce into a scalar.
67    #[cfg_attr(feature = "internals", visibility::make(pub))]
68    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
69    pub(crate) fn to_scalar(
70        self,
71    ) -> <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
72        self.0 .0
73    }
74
75    /// Generates a nonce from the given random bytes.
76    /// This function allows testing and MUST NOT be made public.
77    pub(crate) fn nonce_generate_from_random_bytes(
78        secret: &SigningShare<C>,
79        random_bytes: [u8; 32],
80    ) -> Self {
81        let secret_enc = secret.0.serialize();
82
83        let input: Vec<u8> = random_bytes
84            .iter()
85            .chain(secret_enc.iter())
86            .cloned()
87            .collect();
88
89        Self::from_scalar(C::H3(input.as_slice()))
90    }
91
92    /// Deserialize [`Nonce`] from bytes
93    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
94        Ok(Self(SerializableScalar::deserialize(bytes)?))
95    }
96
97    /// Serialize [`Nonce`] to bytes
98    pub fn serialize(&self) -> Vec<u8> {
99        self.0.serialize()
100    }
101}
102
103impl<C> Zeroize for Nonce<C>
104where
105    C: Ciphersuite,
106{
107    fn zeroize(&mut self) {
108        *self = Nonce::from_scalar(<<C::Group as Group>::Field>::zero());
109    }
110}
111
112#[cfg(any(test, feature = "test-impl"))]
113impl<C> FromHex for Nonce<C>
114where
115    C: Ciphersuite,
116{
117    type Error = &'static str;
118
119    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
120        let v: Vec<u8> = FromHex::from_hex(hex).map_err(|_| "invalid hex")?;
121        Self::deserialize(&v).map_err(|_| "malformed nonce encoding")
122    }
123}
124
125/// A group element that is a commitment to a signing nonce share.
126#[derive(Clone, Copy, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
129pub struct NonceCommitment<C: Ciphersuite>(pub(super) SerializableElement<C>);
130
131impl<C> NonceCommitment<C>
132where
133    C: Ciphersuite,
134{
135    /// Create a new [`NonceCommitment`] from an [`Element`]
136    #[cfg_attr(feature = "internals", visibility::make(pub))]
137    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
138    pub(crate) fn new(value: Element<C>) -> Self {
139        Self(SerializableElement(value))
140    }
141
142    /// Get the inner [`Element`] of the [`NonceCommitment`]
143    #[cfg_attr(feature = "internals", visibility::make(pub))]
144    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
145    pub(crate) fn value(&self) -> Element<C> {
146        self.0 .0
147    }
148
149    /// Deserialize [`NonceCommitment`] from bytes
150    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
151        Ok(Self(SerializableElement::deserialize(bytes)?))
152    }
153
154    /// Serialize [`NonceCommitment`] to bytes
155    pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
156        self.0.serialize()
157    }
158}
159
160impl<C> Debug for NonceCommitment<C>
161where
162    C: Ciphersuite,
163{
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        f.debug_tuple("NonceCommitment")
166            .field(
167                &self
168                    .serialize()
169                    .map(hex::encode)
170                    .unwrap_or("<invalid>".to_string()),
171            )
172            .finish()
173    }
174}
175
176impl<C> From<Nonce<C>> for NonceCommitment<C>
177where
178    C: Ciphersuite,
179{
180    fn from(nonce: Nonce<C>) -> Self {
181        From::from(&nonce)
182    }
183}
184
185impl<C> From<&Nonce<C>> for NonceCommitment<C>
186where
187    C: Ciphersuite,
188{
189    fn from(nonce: &Nonce<C>) -> Self {
190        Self::new(<C::Group>::generator() * nonce.to_scalar())
191    }
192}
193
194#[cfg(any(test, feature = "test-impl"))]
195impl<C> FromHex for NonceCommitment<C>
196where
197    C: Ciphersuite,
198{
199    type Error = &'static str;
200
201    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
202        let v: Vec<u8> = FromHex::from_hex(hex).map_err(|_| "invalid hex")?;
203        Self::deserialize(&v).map_err(|_| "malformed nonce commitment encoding")
204    }
205}
206
207/// Comprised of hiding and binding nonces.
208///
209/// Note that [`SigningNonces`] must be used *only once* for a signing
210/// operation; re-using nonces will result in leakage of a signer's long-lived
211/// signing key.
212#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq, Getters)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
215#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
216pub struct SigningNonces<C: Ciphersuite> {
217    /// Serialization header
218    #[getter(skip)]
219    pub(crate) header: Header<C>,
220    /// The hiding [`Nonce`].
221    pub(crate) hiding: Nonce<C>,
222    /// The binding [`Nonce`].
223    pub(crate) binding: Nonce<C>,
224    /// The commitments to the nonces. This is precomputed to improve
225    /// sign() performance, since it needs to check if the commitments
226    /// to the participant's nonces are included in the commitments sent
227    /// by the Coordinator, and this prevents having to recompute them.
228    #[zeroize(skip)]
229    pub(crate) commitments: SigningCommitments<C>,
230}
231
232impl<C> SigningNonces<C>
233where
234    C: Ciphersuite,
235{
236    /// Generates a new signing nonce.
237    ///
238    /// Each participant generates signing nonces before performing a signing
239    /// operation.
240    pub fn new<R>(secret: &SigningShare<C>, rng: &mut R) -> Self
241    where
242        R: CryptoRng + RngCore,
243    {
244        let hiding = Nonce::<C>::new(secret, rng);
245        let binding = Nonce::<C>::new(secret, rng);
246
247        Self::from_nonces(hiding, binding)
248    }
249
250    /// Generates a new [`SigningNonces`] from a pair of [`Nonce`].
251    ///
252    /// # Security
253    ///
254    /// SigningNonces MUST NOT be repeated in different FROST signings.
255    /// Thus, if you're using this method (because e.g. you're writing it
256    /// to disk between rounds), be careful so that does not happen.
257    pub fn from_nonces(hiding: Nonce<C>, binding: Nonce<C>) -> Self {
258        let hiding_commitment = (&hiding).into();
259        let binding_commitment = (&binding).into();
260        let commitments = SigningCommitments::new(hiding_commitment, binding_commitment);
261
262        Self {
263            header: Header::default(),
264            hiding,
265            binding,
266            commitments,
267        }
268    }
269}
270
271impl<C> Debug for SigningNonces<C>
272where
273    C: Ciphersuite,
274{
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        f.debug_struct("SigningNonces")
277            .field("hiding", &"<redacted>")
278            .field("binding", &"<redacted>")
279            .finish()
280    }
281}
282
283#[cfg(feature = "serialization")]
284impl<C> SigningNonces<C>
285where
286    C: Ciphersuite,
287{
288    /// Serialize the struct into a Vec.
289    pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
290        Serialize::serialize(&self)
291    }
292
293    /// Deserialize the struct from a slice of bytes.
294    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
295        Deserialize::deserialize(bytes)
296    }
297}
298
299/// Published by each participant in the first round of the signing protocol.
300///
301/// This step can be batched if desired by the implementation. Each
302/// SigningCommitment can be used for exactly *one* signature.
303#[derive(Copy, Clone, Debug, Eq, PartialEq, Getters)]
304#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
305#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
306#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
307pub struct SigningCommitments<C: Ciphersuite> {
308    /// Serialization header
309    #[getter(skip)]
310    pub(crate) header: Header<C>,
311    /// Commitment to the hiding [`Nonce`].
312    pub(crate) hiding: NonceCommitment<C>,
313    /// Commitment to the binding [`Nonce`].
314    pub(crate) binding: NonceCommitment<C>,
315}
316
317impl<C> SigningCommitments<C>
318where
319    C: Ciphersuite,
320{
321    /// Create new SigningCommitments
322    pub fn new(hiding: NonceCommitment<C>, binding: NonceCommitment<C>) -> Self {
323        Self {
324            header: Header::default(),
325            hiding,
326            binding,
327        }
328    }
329
330    /// Computes the [commitment share] from these round one signing commitments.
331    ///
332    /// [commitment share]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation
333    #[cfg_attr(feature = "internals", visibility::make(pub))]
334    #[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
335    pub(super) fn to_group_commitment_share(
336        self,
337        binding_factor: &crate::BindingFactor<C>,
338    ) -> GroupCommitmentShare<C> {
339        GroupCommitmentShare::<C>(self.hiding.value() + (self.binding.value() * binding_factor.0))
340    }
341}
342
343#[cfg(feature = "serialization")]
344impl<C> SigningCommitments<C>
345where
346    C: Ciphersuite,
347{
348    /// Serialize the struct into a Vec.
349    pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
350        Serialize::serialize(&self)
351    }
352
353    /// Deserialize the struct from a slice of bytes.
354    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
355        Deserialize::deserialize(bytes)
356    }
357}
358
359impl<C> From<&SigningNonces<C>> for SigningCommitments<C>
360where
361    C: Ciphersuite,
362{
363    fn from(nonces: &SigningNonces<C>) -> Self {
364        nonces.commitments
365    }
366}
367
368/// One signer's share of the group commitment, derived from their individual signing commitments
369/// and the binding factor _rho_.
370#[derive(Clone, Copy, PartialEq)]
371pub struct GroupCommitmentShare<C: Ciphersuite>(pub(super) Element<C>);
372
373impl<C: Ciphersuite> GroupCommitmentShare<C> {
374    /// Create from an element.
375    #[cfg_attr(feature = "internals", visibility::make(pub))]
376    #[allow(unused)]
377    pub(crate) fn from_element(element: Element<C>) -> Self {
378        Self(element)
379    }
380
381    /// Return the underlying element.
382    #[cfg_attr(feature = "internals", visibility::make(pub))]
383    pub(crate) fn to_element(self) -> Element<C> {
384        self.0
385    }
386}
387
388/// Encode the list of group signing commitments.
389///
390/// Implements [`encode_group_commitment_list()`] from the spec.
391///
392/// `signing_commitments` must contain the sorted map of participants
393/// identifiers to the signing commitments they issued.
394///
395/// Returns a byte string containing the serialized representation of the
396/// commitment list.
397///
398/// [`encode_group_commitment_list()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-list-operations
399#[cfg_attr(feature = "internals", visibility::make(pub))]
400#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
401pub(super) fn encode_group_commitments<C: Ciphersuite>(
402    signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
403) -> Result<Vec<u8>, Error<C>> {
404    let mut bytes = vec![];
405
406    for (item_identifier, item) in signing_commitments {
407        bytes.extend_from_slice(item_identifier.serialize().as_ref());
408        bytes.extend_from_slice(<C::Group>::serialize(&item.hiding.value())?.as_ref());
409        bytes.extend_from_slice(<C::Group>::serialize(&item.binding.value())?.as_ref());
410    }
411
412    Ok(bytes)
413}
414
415/// Done once by each participant, to generate _their_ nonces and commitments
416/// that are then used during signing.
417///
418/// This is only needed if pre-processing is needed (for 1-round FROST). For
419/// regular 2-round FROST, use [`commit`].
420///
421/// When performing signing using two rounds, num_nonces would equal 1, to
422/// perform the first round. Batching entails generating more than one
423/// nonce/commitment pair at a time.  Nonces should be stored in secret storage
424/// for later use, whereas the commitments are published.
425pub fn preprocess<C, R>(
426    num_nonces: u8,
427    secret: &SigningShare<C>,
428    rng: &mut R,
429) -> (Vec<SigningNonces<C>>, Vec<SigningCommitments<C>>)
430where
431    C: Ciphersuite,
432    R: CryptoRng + RngCore,
433{
434    let mut signing_nonces: Vec<SigningNonces<C>> = Vec::with_capacity(num_nonces as usize);
435    let mut signing_commitments: Vec<SigningCommitments<C>> =
436        Vec::with_capacity(num_nonces as usize);
437
438    for _ in 0..num_nonces {
439        let nonces = SigningNonces::new(secret, rng);
440        signing_commitments.push(SigningCommitments::from(&nonces));
441        signing_nonces.push(nonces);
442    }
443
444    (signing_nonces, signing_commitments)
445}
446
447/// Performed once by each participant selected for the signing operation.
448///
449/// Implements [`commit`] from the spec.
450///
451/// Generates the signing nonces and commitments to be used in the signing
452/// operation.
453///
454/// [`commit`]: https://datatracker.ietf.org/doc/html/rfc9591#name-round-one-commitment
455pub fn commit<C, R>(
456    secret: &SigningShare<C>,
457    rng: &mut R,
458) -> (SigningNonces<C>, SigningCommitments<C>)
459where
460    C: Ciphersuite,
461    R: CryptoRng + RngCore,
462{
463    let (mut vec_signing_nonces, mut vec_signing_commitments) = preprocess(1, secret, rng);
464    (
465        vec_signing_nonces.pop().expect("must have 1 element"),
466        vec_signing_commitments.pop().expect("must have 1 element"),
467    )
468}