Skip to main content

frost_rerandomized/
lib.rs

1//! FROST implementation supporting re-randomizable keys.
2//!
3//! To sign with re-randomized FROST:
4//!
5//! - Do Round 1 the same way as regular FROST;
6//! - The Coordinator should call [`RandomizedParams::new_from_commitments()`]
7//!   and send the generate randomizer seed (the second returned value) to all
8//!   participants, using a confidential channel, along with the regular
9//!   [`frost::SigningPackage`];
10//! - Each participant should regenerate the RandomizerParams by calling
11//!   [`RandomizedParams::regenerate_from_seed_and_commitments()`], which they
12//!   should pass to [`sign_with_randomizer_seed()`] and send the resulting
13//!   [`frost::round2::SignatureShare`] back to the Coordinator;
14//! - The Coordinator should then call [`aggregate`].
15#![no_std]
16#![allow(non_snake_case)]
17
18extern crate alloc;
19
20#[cfg(any(test, feature = "test-impl"))]
21pub mod tests;
22
23use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
24
25use derive_getters::Getters;
26pub use frost_core;
27
28#[cfg(feature = "serialization")]
29use frost_core::SigningPackage;
30use frost_core::{
31    self as frost,
32    keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
33    round1::{encode_group_commitments, SigningCommitments},
34    serialization::SerializableScalar,
35    CheaterDetection, Ciphersuite, Error, Field, Group, Identifier, Scalar, VerifyingKey,
36};
37
38#[cfg(feature = "serde")]
39use frost_core::serde;
40
41// When pulled into `reddsa`, that has its own sibling `rand_core` import.
42// For the time being, we do not re-export this `rand_core`.
43use rand_core::{CryptoRng, RngCore};
44
45/// Randomize the given key type for usage in a FROST signing with re-randomized keys,
46/// using the given [`RandomizedParams`].
47trait Randomize<C> {
48    fn randomize(&self, params: &RandomizedParams<C>) -> Result<Self, Error<C>>
49    where
50        Self: Sized,
51        C: Ciphersuite;
52}
53
54/// A Ciphersuite that supports rerandomization.
55pub trait RandomizedCiphersuite: Ciphersuite {
56    /// A hash function that hashes into a randomizer scalar.
57    fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar>;
58}
59
60impl<C: Ciphersuite> Randomize<C> for KeyPackage<C> {
61    /// Randomize the given [`KeyPackage`] for usage in a re-randomized FROST signing,
62    /// using the given [`RandomizedParams`].
63    ///
64    /// It's recommended to use [`sign`] directly which already handles
65    /// the key package randomization.
66    ///
67    /// You MUST NOT reuse the randomized key package for more than one signing.
68    fn randomize(&self, randomized_params: &RandomizedParams<C>) -> Result<Self, Error<C>>
69    where
70        Self: Sized,
71        C: Ciphersuite,
72    {
73        let verifying_share = self.verifying_share();
74        let randomized_verifying_share = VerifyingShare::<C>::new(
75            verifying_share.to_element() + randomized_params.randomizer_element,
76        );
77
78        let signing_share = self.signing_share();
79        let randomized_signing_share =
80            SigningShare::new(signing_share.to_scalar() + randomized_params.randomizer.to_scalar());
81
82        let randomized_key_package = KeyPackage::new(
83            *self.identifier(),
84            randomized_signing_share,
85            randomized_verifying_share,
86            randomized_params.randomized_verifying_key,
87            *self.min_signers(),
88        );
89        Ok(randomized_key_package)
90    }
91}
92
93impl<C: Ciphersuite> Randomize<C> for PublicKeyPackage<C> {
94    /// Randomized the given [`PublicKeyPackage`] for usage in a re-randomized FROST
95    /// aggregation, using the given [`RandomizedParams`].
96    ///
97    /// It's recommended to use [`aggregate`] directly which already handles
98    /// the public key package randomization.
99    fn randomize(&self, randomized_params: &RandomizedParams<C>) -> Result<Self, Error<C>>
100    where
101        Self: Sized,
102        C: Ciphersuite,
103    {
104        let verifying_shares = self.verifying_shares().clone();
105        let randomized_verifying_shares = verifying_shares
106            .iter()
107            .map(|(identifier, verifying_share)| {
108                (
109                    *identifier,
110                    VerifyingShare::<C>::new(
111                        verifying_share.to_element() + randomized_params.randomizer_element,
112                    ),
113                )
114            })
115            .collect();
116
117        Ok(PublicKeyPackage::new_internal(
118            randomized_verifying_shares,
119            randomized_params.randomized_verifying_key,
120            self.min_signers(),
121        ))
122    }
123}
124
125/// Re-randomized FROST signing using the given `randomizer`, which should
126/// be sent from the Coordinator using a confidential channel.
127///
128/// See [`frost::round2::sign`] for documentation on the other parameters.
129#[deprecated(
130    note = "switch to sign_with_randomizer_seed(), passing a seed generated with RandomizedParams::new_from_commitments()"
131)]
132pub fn sign<C: RandomizedCiphersuite>(
133    signing_package: &frost::SigningPackage<C>,
134    signer_nonces: &frost::round1::SigningNonces<C>,
135    key_package: &frost::keys::KeyPackage<C>,
136    randomizer: Randomizer<C>,
137) -> Result<frost::round2::SignatureShare<C>, Error<C>> {
138    let randomized_params =
139        RandomizedParams::from_randomizer(key_package.verifying_key(), randomizer);
140    let randomized_key_package = key_package.randomize(&randomized_params)?;
141    frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
142}
143
144/// Re-randomized FROST signing using the given `randomizer_seed`, which should
145/// be sent from the Coordinator using a confidential channel.
146///
147/// See [`frost::round2::sign`] for documentation on the other parameters.
148pub fn sign_with_randomizer_seed<C: RandomizedCiphersuite>(
149    signing_package: &frost::SigningPackage<C>,
150    signer_nonces: &frost::round1::SigningNonces<C>,
151    key_package: &frost::keys::KeyPackage<C>,
152    randomizer_seed: &[u8],
153) -> Result<frost::round2::SignatureShare<C>, Error<C>> {
154    let randomized_params = RandomizedParams::regenerate_from_seed_and_commitments(
155        key_package.verifying_key(),
156        randomizer_seed,
157        signing_package.signing_commitments(),
158    )?;
159    let randomized_key_package = key_package.randomize(&randomized_params)?;
160    frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
161}
162
163/// Re-randomized FROST signature share aggregation with the given
164/// [`RandomizedParams`].
165///
166/// See [`frost::aggregate`] for documentation on the other parameters.
167pub fn aggregate<C>(
168    signing_package: &frost::SigningPackage<C>,
169    signature_shares: &BTreeMap<frost::Identifier<C>, frost::round2::SignatureShare<C>>,
170    pubkeys: &frost::keys::PublicKeyPackage<C>,
171    randomized_params: &RandomizedParams<C>,
172) -> Result<frost_core::Signature<C>, Error<C>>
173where
174    C: Ciphersuite,
175{
176    let randomized_public_key_package = pubkeys.randomize(randomized_params)?;
177    frost::aggregate(
178        signing_package,
179        signature_shares,
180        &randomized_public_key_package,
181    )
182}
183
184/// Re-randomized FROST signature share aggregation with the given
185/// [`RandomizedParams`] using the given cheater detection strategy.
186///
187/// See [`frost::aggregate_custom`] for documentation on the other parameters.
188pub fn aggregate_custom<C>(
189    signing_package: &frost::SigningPackage<C>,
190    signature_shares: &BTreeMap<frost::Identifier<C>, frost::round2::SignatureShare<C>>,
191    pubkeys: &frost::keys::PublicKeyPackage<C>,
192    cheater_detection: CheaterDetection,
193    randomized_params: &RandomizedParams<C>,
194) -> Result<frost_core::Signature<C>, Error<C>>
195where
196    C: Ciphersuite,
197{
198    let randomized_public_key_package = pubkeys.randomize(randomized_params)?;
199    frost::aggregate_custom(
200        signing_package,
201        signature_shares,
202        &randomized_public_key_package,
203        cheater_detection,
204    )
205}
206
207/// A randomizer. A random scalar which is used to randomize the key.
208#[derive(Copy, Clone, PartialEq, Eq)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
211#[cfg_attr(feature = "serde", serde(transparent))]
212#[cfg_attr(feature = "serde", serde(crate = "self::serde"))]
213pub struct Randomizer<C: Ciphersuite>(SerializableScalar<C>);
214
215impl<C> Randomizer<C>
216where
217    C: Ciphersuite,
218{
219    pub(crate) fn to_scalar(self) -> Scalar<C> {
220        self.0 .0
221    }
222}
223
224impl<C> Randomizer<C>
225where
226    C: RandomizedCiphersuite,
227{
228    /// Create a new random Randomizer using a SigningPackage for randomness.
229    ///
230    /// The [`SigningPackage`] must be the signing package being used in the
231    /// current FROST signing run. It is hashed into the randomizer calculation,
232    /// which binds it to that specific package.
233    #[cfg(feature = "serialization")]
234    #[deprecated(
235        note = "switch to new_from_commitments(), passing the commitments from SigningPackage"
236    )]
237    pub fn new<R: RngCore + CryptoRng>(
238        mut rng: R,
239        signing_package: &SigningPackage<C>,
240    ) -> Result<Self, Error<C>> {
241        let rng_randomizer = <<C::Group as Group>::Field as Field>::random(&mut rng);
242        Self::from_randomizer_and_signing_package(rng_randomizer, signing_package)
243    }
244
245    /// Create a final Randomizer from a random Randomizer and a SigningPackage.
246    /// Function refactored out for testing, should always be private.
247    #[cfg(feature = "serialization")]
248    fn from_randomizer_and_signing_package(
249        rng_randomizer: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
250        signing_package: &SigningPackage<C>,
251    ) -> Result<Randomizer<C>, Error<C>>
252    where
253        C: RandomizedCiphersuite,
254    {
255        let randomizer = C::hash_randomizer(
256            &[
257                <<C::Group as Group>::Field>::serialize(&rng_randomizer).as_ref(),
258                &signing_package.serialize()?,
259            ]
260            .concat(),
261        )
262        .ok_or(Error::SerializationError)?;
263        Ok(Self(SerializableScalar(randomizer)))
264    }
265
266    /// Create a new random Randomizer using SigningCommitments for randomness.
267    ///
268    /// The [`SigningCommitments`] map must be the one being used in the current
269    /// FROST signing run (built by the Coordinator after receiving from
270    /// Participants). It is hashed into the randomizer calculation, which binds
271    /// it to that specific commitments.
272    ///
273    /// Returns the Randomizer and the generate randomizer seed. Both can be
274    /// used to regenerate the Randomizer with
275    /// [`Self::regenerate_from_seed_and_commitments()`].
276    pub fn new_from_commitments<R: RngCore + CryptoRng>(
277        mut rng: R,
278        signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
279    ) -> Result<(Self, Vec<u8>), Error<C>> {
280        // Generate a dummy scalar to get its encoded size
281        let zero = <<C::Group as Group>::Field as Field>::zero();
282        let ns = <<C::Group as Group>::Field as Field>::serialize(&zero)
283            .as_ref()
284            .len();
285        let mut randomizer_seed = alloc::vec![0; ns];
286        rng.fill_bytes(&mut randomizer_seed);
287        Ok((
288            Self::regenerate_from_seed_and_commitments(&randomizer_seed, signing_commitments)?,
289            randomizer_seed,
290        ))
291    }
292
293    /// Regenerates a Randomizer generated with
294    /// [`Self::new_from_commitments()`]. This can be used by Participants after
295    /// receiving the randomizer seed and commitments in Round 2. This is better
296    /// than the Coordinator simply generating a Randomizer and sending it to
297    /// Participants, because in this approach the participants don't need to
298    /// fully trust the Coordinator's random number generator (i.e. even if the
299    /// randomizer seed was not randomly generated the randomizer will still
300    /// be).
301    ///
302    /// This should be used exclusively with the output of
303    /// [`Self::new_from_commitments()`]; it is strongly suggested to not
304    /// attempt generating the randomizer seed yourself (even if the point of
305    /// this approach is to hedge against issues in the randomizer seed
306    /// generation).
307    pub fn regenerate_from_seed_and_commitments(
308        randomizer_seed: &[u8],
309        signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
310    ) -> Result<Randomizer<C>, Error<C>>
311    where
312        C: RandomizedCiphersuite,
313    {
314        let randomizer = C::hash_randomizer(
315            &[
316                randomizer_seed,
317                &encode_group_commitments(signing_commitments)?,
318            ]
319            .concat(),
320        )
321        .ok_or(Error::SerializationError)?;
322        Ok(Self(SerializableScalar(randomizer)))
323    }
324}
325
326impl<C> Randomizer<C>
327where
328    C: Ciphersuite,
329{
330    /// Create a new Randomizer from the given scalar. It MUST be randomly
331    /// generated.
332    ///
333    /// It is not recommended to use this method unless for compatibility
334    /// reasons with specifications on how the randomizer must be generated. Use
335    /// [`Randomizer::new()`] instead.
336    pub fn from_scalar(scalar: Scalar<C>) -> Self {
337        Self(SerializableScalar(scalar))
338    }
339
340    /// Serialize the identifier using the ciphersuite encoding.
341    pub fn serialize(&self) -> Vec<u8> {
342        self.0.serialize()
343    }
344
345    /// Deserialize an Identifier from a serialized buffer.
346    /// Returns an error if it attempts to deserialize zero.
347    pub fn deserialize(buf: &[u8]) -> Result<Self, Error<C>> {
348        Ok(Self(SerializableScalar::deserialize(buf)?))
349    }
350}
351
352impl<C> core::fmt::Debug for Randomizer<C>
353where
354    C: Ciphersuite,
355{
356    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
357        f.debug_tuple("Randomizer")
358            .field(&hex::encode(self.0.serialize()))
359            .finish()
360    }
361}
362
363/// Randomized parameters for a signing instance of randomized FROST.
364#[derive(Clone, PartialEq, Eq, Getters)]
365pub struct RandomizedParams<C: Ciphersuite> {
366    /// The randomizer, also called α
367    randomizer: Randomizer<C>,
368    /// The generator multiplied by the randomizer.
369    randomizer_element: <C::Group as Group>::Element,
370    /// The randomized group public key. The group public key added to the randomizer element.
371    randomized_verifying_key: frost_core::VerifyingKey<C>,
372}
373
374impl<C> RandomizedParams<C>
375where
376    C: RandomizedCiphersuite,
377{
378    /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and
379    /// the given [`SigningPackage`].
380    #[cfg(feature = "serialization")]
381    #[deprecated(
382        note = "switch to new_from_commitments(), passing the commitments from SigningPackage"
383    )]
384    pub fn new<R: RngCore + CryptoRng>(
385        group_verifying_key: &VerifyingKey<C>,
386        signing_package: &SigningPackage<C>,
387        rng: R,
388    ) -> Result<Self, Error<C>> {
389        #[allow(deprecated)]
390        Ok(Self::from_randomizer(
391            group_verifying_key,
392            Randomizer::new(rng, signing_package)?,
393        ))
394    }
395
396    /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and the
397    /// given signing commitments.
398    ///
399    /// The [`SigningCommitments`] map must be the one being used in the current
400    /// FROST signing run (built by the Coordinator after receiving from
401    /// Participants). It is hashed into the randomizer calculation, which binds
402    /// it to that specific commitments.
403    ///
404    /// Returns the generated [`RandomizedParams`] and a randomizer seed. Both
405    /// can be used to regenerate the [`RandomizedParams`] with
406    /// [`Self::regenerate_from_seed_and_commitments()`].
407    pub fn new_from_commitments<R: RngCore + CryptoRng>(
408        group_verifying_key: &VerifyingKey<C>,
409        signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
410        rng: R,
411    ) -> Result<(Self, Vec<u8>), Error<C>> {
412        let (randomizer, randomizer_seed) =
413            Randomizer::new_from_commitments(rng, signing_commitments)?;
414        Ok((
415            Self::from_randomizer(group_verifying_key, randomizer),
416            randomizer_seed,
417        ))
418    }
419
420    /// Regenerate a [`RandomizedParams`] with the given [`VerifyingKey`] from
421    /// the given given signing commitments.
422    ///
423    /// Returns the generated [`RandomizedParams`] and a randomizer seed, which
424    /// can be used to regenerate the [`RandomizedParams`].
425    ///
426    /// Regenerates a [`RandomizedParams`] generated with
427    /// [`Self::new_from_commitments()`]. This can be used by Participants after
428    /// receiving the randomizer seed and commitments in Round 2. This is better
429    /// than the Coordinator simply generating a [`Randomizer`] and sending it
430    /// to Participants, because in this approach the participants don't need to
431    /// fully trust the Coordinator's random number generator (i.e. even if the
432    /// randomizer seed was not randomly generated the randomizer will still
433    /// be).
434    ///
435    /// This should be used exclusively with the output of
436    /// [`Self::new_from_commitments()`]; it is strongly suggested to not
437    /// attempt generating the randomizer seed yourself (even if the point of
438    /// this approach is to hedge against issues in the randomizer seed
439    /// generation).
440    pub fn regenerate_from_seed_and_commitments(
441        group_verifying_key: &VerifyingKey<C>,
442        randomizer_seed: &[u8],
443        signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
444    ) -> Result<Self, Error<C>> {
445        let randomizer =
446            Randomizer::regenerate_from_seed_and_commitments(randomizer_seed, signing_commitments)?;
447        Ok(Self::from_randomizer(group_verifying_key, randomizer))
448    }
449}
450
451impl<C> RandomizedParams<C>
452where
453    C: Ciphersuite,
454{
455    /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and the
456    /// given `participants` for the  given `randomizer`. The `randomizer` MUST
457    /// be generated uniformly at random! Use [`RandomizedParams::new()`] which
458    /// generates a fresh randomizer, unless your application requires generating
459    /// a randomizer outside.
460    pub fn from_randomizer(
461        group_verifying_key: &VerifyingKey<C>,
462        randomizer: Randomizer<C>,
463    ) -> Self {
464        let randomizer_element = <C::Group as Group>::generator() * randomizer.to_scalar();
465        let verifying_key_element = group_verifying_key.to_element();
466        let randomized_verifying_key_element = verifying_key_element + randomizer_element;
467        let randomized_verifying_key = VerifyingKey::<C>::new(randomized_verifying_key_element);
468
469        Self {
470            randomizer,
471            randomizer_element,
472            randomized_verifying_key,
473        }
474    }
475}
476
477impl<C> core::fmt::Debug for RandomizedParams<C>
478where
479    C: Ciphersuite,
480{
481    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
482        f.debug_struct("RandomizedParams")
483            .field("randomizer", &self.randomizer)
484            .field(
485                "randomizer_element",
486                &<C::Group as Group>::serialize(&self.randomizer_element)
487                    .map(hex::encode)
488                    .unwrap_or("<invalid>".to_string()),
489            )
490            .field("randomized_verifying_key", &self.randomized_verifying_key)
491            .finish()
492    }
493}