Skip to content

Commit bac3297

Browse files
authored
frost-rerandomized: change Randomizer generation (#762)
* frost-rerandomized: change Randomizer generation to take SigningCommitments and not depend on serialization feature * ci: change doc warnings to errors * fix doc warnings * fix `one` variable naming
1 parent e6a57b3 commit bac3297

File tree

3 files changed

+212
-14
lines changed

3 files changed

+212
-14
lines changed

frost-core/src/round1.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,9 @@ impl<C: Ciphersuite> GroupCommitmentShare<C> {
396396
/// commitment list.
397397
///
398398
/// [`encode_group_commitment_list()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-list-operations
399+
#[cfg(feature = "internals")]
400+
#[cfg_attr(feature = "internals", visibility::make(pub))]
401+
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
399402
pub(super) fn encode_group_commitments<C: Ciphersuite>(
400403
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
401404
) -> Result<Vec<u8>, Error<C>> {

frost-rerandomized/src/lib.rs

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
//! To sign with re-randomized FROST:
44
//!
55
//! - Do Round 1 the same way as regular FROST;
6-
//! - The Coordinator should call [`RandomizedParams::new()`] and send
7-
//! the [`RandomizedParams::randomizer`] to all participants, using a
8-
//! confidential channel, along with the regular [`frost::SigningPackage`];
9-
//! - Each participant should call [`sign`] and send the resulting
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
1013
//! [`frost::round2::SignatureShare`] back to the Coordinator;
1114
//! - The Coordinator should then call [`aggregate`].
1215
#![no_std]
@@ -27,16 +30,17 @@ use frost_core::SigningPackage;
2730
use frost_core::{
2831
self as frost,
2932
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
33+
round1::encode_group_commitments,
34+
round1::SigningCommitments,
3035
serialization::SerializableScalar,
31-
Ciphersuite, Error, Field, Group, Scalar, VerifyingKey,
36+
Ciphersuite, Error, Field, Group, Identifier, Scalar, VerifyingKey,
3237
};
3338

3439
#[cfg(feature = "serde")]
3540
use frost_core::serde;
3641

3742
// When pulled into `reddsa`, that has its own sibling `rand_core` import.
3843
// For the time being, we do not re-export this `rand_core`.
39-
#[cfg(feature = "serialization")]
4044
use rand_core::{CryptoRng, RngCore};
4145

4246
/// Randomize the given key type for usage in a FROST signing with re-randomized keys,
@@ -123,6 +127,9 @@ impl<C: Ciphersuite> Randomize<C> for PublicKeyPackage<C> {
123127
/// be sent from the Coordinator using a confidential channel.
124128
///
125129
/// See [`frost::round2::sign`] for documentation on the other parameters.
130+
#[deprecated(
131+
note = "switch to sign_with_randomizer_seed(), passing a seed generated with RandomizedParams::new_from_commitments()"
132+
)]
126133
pub fn sign<C: RandomizedCiphersuite>(
127134
signing_package: &frost::SigningPackage<C>,
128135
signer_nonces: &frost::round1::SigningNonces<C>,
@@ -135,6 +142,25 @@ pub fn sign<C: RandomizedCiphersuite>(
135142
frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
136143
}
137144

145+
/// Re-randomized FROST signing using the given `randomizer_seed`, which should
146+
/// be sent from the Coordinator using a confidential channel.
147+
///
148+
/// See [`frost::round2::sign`] for documentation on the other parameters.
149+
pub fn sign_with_randomizer_seed<C: RandomizedCiphersuite>(
150+
signing_package: &frost::SigningPackage<C>,
151+
signer_nonces: &frost::round1::SigningNonces<C>,
152+
key_package: &frost::keys::KeyPackage<C>,
153+
randomizer_seed: &[u8],
154+
) -> Result<frost::round2::SignatureShare<C>, Error<C>> {
155+
let randomized_params = RandomizedParams::regenerate_from_seed_and_commitments(
156+
key_package.verifying_key(),
157+
randomizer_seed,
158+
signing_package.signing_commitments(),
159+
)?;
160+
let randomized_key_package = key_package.randomize(&randomized_params)?;
161+
frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
162+
}
163+
138164
/// Re-randomized FROST signature share aggregation with the given [`RandomizedParams`],
139165
/// which can be computed from the previously generated randomizer using
140166
/// [`RandomizedParams::from_randomizer`].
@@ -178,12 +204,15 @@ impl<C> Randomizer<C>
178204
where
179205
C: RandomizedCiphersuite,
180206
{
181-
/// Create a new random Randomizer.
207+
/// Create a new random Randomizer using a SigningPackage for randomness.
182208
///
183209
/// The [`SigningPackage`] must be the signing package being used in the
184210
/// current FROST signing run. It is hashed into the randomizer calculation,
185211
/// which binds it to that specific package.
186212
#[cfg(feature = "serialization")]
213+
#[deprecated(
214+
note = "switch to new_from_commitments(), passing the commitments from SigningPackage"
215+
)]
187216
pub fn new<R: RngCore + CryptoRng>(
188217
mut rng: R,
189218
signing_package: &SigningPackage<C>,
@@ -212,6 +241,65 @@ where
212241
.ok_or(Error::SerializationError)?;
213242
Ok(Self(SerializableScalar(randomizer)))
214243
}
244+
245+
/// Create a new random Randomizer using SigningCommitments for randomness.
246+
///
247+
/// The [`SigningCommitments`] map must be the one being used in the current
248+
/// FROST signing run (built by the Coordinator after receiving from
249+
/// Participants). It is hashed into the randomizer calculation, which binds
250+
/// it to that specific commitments.
251+
///
252+
/// Returns the Randomizer and the generate randomizer seed. Both can be
253+
/// used to regenerate the Randomizer with
254+
/// [`Self::regenerate_from_seed_and_commitments()`].
255+
pub fn new_from_commitments<R: RngCore + CryptoRng>(
256+
mut rng: R,
257+
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
258+
) -> Result<(Self, Vec<u8>), Error<C>> {
259+
// Generate a dummy scalar to get its encoded size
260+
let zero = <<C::Group as Group>::Field as Field>::zero();
261+
let ns = <<C::Group as Group>::Field as Field>::serialize(&zero)
262+
.as_ref()
263+
.len();
264+
let mut randomizer_seed = alloc::vec![0; ns];
265+
rng.fill_bytes(&mut randomizer_seed);
266+
Ok((
267+
Self::regenerate_from_seed_and_commitments(&randomizer_seed, signing_commitments)?,
268+
randomizer_seed,
269+
))
270+
}
271+
272+
/// Regenerates a Randomizer generated with
273+
/// [`Self::new_from_commitments()`]. This can be used by Participants after
274+
/// receiving the randomizer seed and commitments in Round 2. This is better
275+
/// than the Coordinator simply generating a Randomizer and sending it to
276+
/// Participants, because in this approach the participants don't need to
277+
/// fully trust the Coordinator's random number generator (i.e. even if the
278+
/// randomizer seed was not randomly generated the randomizer will still
279+
/// be).
280+
///
281+
/// This should be used exclusively with the output of
282+
/// [`Self::new_from_commitments()`]; it is strongly suggested to not
283+
/// attempt generating the randomizer seed yourself (even if the point of
284+
/// this approach is to hedge against issues in the randomizer seed
285+
/// generation).
286+
pub fn regenerate_from_seed_and_commitments(
287+
randomizer_seed: &[u8],
288+
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
289+
) -> Result<Randomizer<C>, Error<C>>
290+
where
291+
C: RandomizedCiphersuite,
292+
{
293+
let randomizer = C::hash_randomizer(
294+
&[
295+
randomizer_seed,
296+
&encode_group_commitments(signing_commitments)?,
297+
]
298+
.concat(),
299+
)
300+
.ok_or(Error::SerializationError)?;
301+
Ok(Self(SerializableScalar(randomizer)))
302+
}
215303
}
216304

217305
impl<C> Randomizer<C>
@@ -267,18 +355,76 @@ where
267355
C: RandomizedCiphersuite,
268356
{
269357
/// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and
270-
/// the given `participants`.
358+
/// the given [`SigningPackage`].
271359
#[cfg(feature = "serialization")]
360+
#[deprecated(
361+
note = "switch to new_from_commitments(), passing the commitments from SigningPackage"
362+
)]
272363
pub fn new<R: RngCore + CryptoRng>(
273364
group_verifying_key: &VerifyingKey<C>,
274365
signing_package: &SigningPackage<C>,
275366
rng: R,
276367
) -> Result<Self, Error<C>> {
368+
#[allow(deprecated)]
277369
Ok(Self::from_randomizer(
278370
group_verifying_key,
279371
Randomizer::new(rng, signing_package)?,
280372
))
281373
}
374+
375+
/// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and the
376+
/// given signing commitments.
377+
///
378+
/// The [`SigningCommitments`] map must be the one being used in the current
379+
/// FROST signing run (built by the Coordinator after receiving from
380+
/// Participants). It is hashed into the randomizer calculation, which binds
381+
/// it to that specific commitments.
382+
///
383+
/// Returns the generated [`RandomizedParams`] and a randomizer seed. Both
384+
/// can be used to regenerate the [`RandomizedParams`] with
385+
/// [`Self::regenerate_from_seed_and_commitments()`].
386+
pub fn new_from_commitments<R: RngCore + CryptoRng>(
387+
group_verifying_key: &VerifyingKey<C>,
388+
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
389+
rng: R,
390+
) -> Result<(Self, Vec<u8>), Error<C>> {
391+
let (randomizer, randomizer_seed) =
392+
Randomizer::new_from_commitments(rng, signing_commitments)?;
393+
Ok((
394+
Self::from_randomizer(group_verifying_key, randomizer),
395+
randomizer_seed,
396+
))
397+
}
398+
399+
/// Regenerate a [`RandomizedParams`] with the given [`VerifyingKey`] from
400+
/// the given given signing commitments.
401+
///
402+
/// Returns the generated [`RandomizedParams`] and a randomizer seed, which
403+
/// can be used to regenerate the [`RandomizedParams`].
404+
///
405+
/// Regenerates a [`RandomizedParams`] generated with
406+
/// [`Self::new_from_commitments()`]. This can be used by Participants after
407+
/// receiving the randomizer seed and commitments in Round 2. This is better
408+
/// than the Coordinator simply generating a [`Randomizer`] and sending it
409+
/// to Participants, because in this approach the participants don't need to
410+
/// fully trust the Coordinator's random number generator (i.e. even if the
411+
/// randomizer seed was not randomly generated the randomizer will still
412+
/// be).
413+
///
414+
/// This should be used exclusively with the output of
415+
/// [`Self::new_from_commitments()`]; it is strongly suggested to not
416+
/// attempt generating the randomizer seed yourself (even if the point of
417+
/// this approach is to hedge against issues in the randomizer seed
418+
/// generation).
419+
pub fn regenerate_from_seed_and_commitments(
420+
group_verifying_key: &VerifyingKey<C>,
421+
randomizer_seed: &[u8],
422+
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
423+
) -> Result<Self, Error<C>> {
424+
let randomizer =
425+
Randomizer::regenerate_from_seed_and_commitments(randomizer_seed, signing_commitments)?;
426+
Ok(Self::from_randomizer(group_verifying_key, randomizer))
427+
}
282428
}
283429

284430
impl<C> RandomizedParams<C>

frost-rerandomized/src/tests.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use alloc::collections::BTreeMap;
66
use alloc::vec::Vec;
77

88
use crate::{frost_core as frost, RandomizedCiphersuite, RandomizedParams, Randomizer};
9-
use frost_core::{Field, Group, Signature, SigningPackage, VerifyingKey};
9+
use frost_core::{
10+
round1::SigningCommitments, Field, Group, Identifier, Signature, SigningPackage, VerifyingKey,
11+
};
1012
use rand_core::{CryptoRng, RngCore};
1113

1214
/// Test re-randomized FROST signing with trusted dealer with a Ciphersuite.
@@ -70,9 +72,12 @@ pub fn check_randomized_sign_with_dealer<C: RandomizedCiphersuite, R: RngCore +
7072
let signing_package = frost::SigningPackage::new(commitments, message);
7173

7274
check_randomizer(&pubkeys, &signing_package, &mut rng);
73-
let randomizer_params =
74-
RandomizedParams::new(pubkeys.verifying_key(), &signing_package, &mut rng).unwrap();
75-
let randomizer = randomizer_params.randomizer();
75+
let (randomizer_params, randomizer_seed) = RandomizedParams::new_from_commitments(
76+
pubkeys.verifying_key(),
77+
signing_package.signing_commitments(),
78+
&mut rng,
79+
)
80+
.unwrap();
7681

7782
////////////////////////////////////////////////////////////////////////////
7883
// Round 2: each participant generates their signature share
@@ -84,8 +89,13 @@ pub fn check_randomized_sign_with_dealer<C: RandomizedCiphersuite, R: RngCore +
8489
let nonces_to_use = &nonces.get(participant_identifier).unwrap();
8590

8691
// Each participant generates their signature share.
87-
let signature_share =
88-
crate::sign(&signing_package, nonces_to_use, key_package, *randomizer).unwrap();
92+
let signature_share = crate::sign_with_randomizer_seed(
93+
&signing_package,
94+
nonces_to_use,
95+
key_package,
96+
&randomizer_seed,
97+
)
98+
.unwrap();
8999
signature_shares.insert(*participant_identifier, signature_share);
90100
}
91101

@@ -131,13 +141,16 @@ fn check_randomizer<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
131141
check_from_randomizer(&mut rng, signing_package, pubkeys);
132142

133143
check_from_randomizer_and_signing_package(&mut rng, signing_package);
144+
145+
check_from_seed_and_signing_commitments(&mut rng, signing_package.signing_commitments());
134146
}
135147

136148
fn check_from_randomizer<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
137149
rng: &mut R,
138150
signing_package: &SigningPackage<C>,
139151
pubkeys: &frost::keys::PublicKeyPackage<C>,
140152
) {
153+
#[allow(deprecated)]
141154
let randomizer = Randomizer::new(rng, signing_package).unwrap();
142155

143156
let randomizer_params = RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer);
@@ -176,3 +189,39 @@ fn check_from_randomizer_and_signing_package<C: RandomizedCiphersuite, R: RngCor
176189
// Make sure that different packages lead to different randomizers
177190
assert!(randomizer1 != randomizer2);
178191
}
192+
193+
fn check_from_seed_and_signing_commitments<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
194+
mut rng: &mut R,
195+
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
196+
) {
197+
// Make sure regeneration returns the same Randomizer.
198+
let (randomizer1, randomizer_seed1) =
199+
Randomizer::new_from_commitments(&mut rng, signing_commitments).unwrap();
200+
let randomizer2 =
201+
Randomizer::regenerate_from_seed_and_commitments(&randomizer_seed1, signing_commitments)
202+
.unwrap();
203+
assert!(randomizer1 == randomizer2);
204+
205+
let (randomizer2, randomizer_seed2) =
206+
Randomizer::new_from_commitments(&mut rng, signing_commitments).unwrap();
207+
208+
// Make sure that different rng_randomizers lead to different randomizers
209+
assert!(randomizer1 != randomizer2);
210+
assert!(randomizer_seed1 != randomizer_seed2);
211+
212+
// Modify the commitments map, by overwriting the first entry with the value
213+
// of the last entry.
214+
let mut modified_signing_commitments = signing_commitments.clone();
215+
modified_signing_commitments
216+
.first_entry()
217+
.unwrap()
218+
.insert(*signing_commitments.last_key_value().unwrap().1);
219+
let randomizer2 = Randomizer::regenerate_from_seed_and_commitments(
220+
&randomizer_seed1,
221+
&modified_signing_commitments,
222+
)
223+
.unwrap();
224+
225+
// Make sure that different packages lead to different randomizers
226+
assert!(randomizer1 != randomizer2);
227+
}

0 commit comments

Comments
 (0)