Skip to content

Commit

Permalink
frost-rerandomized: change Randomizer generation to take SigningCommi…
Browse files Browse the repository at this point in the history
…tments and not depend on serialization feature
  • Loading branch information
conradoplg committed Oct 29, 2024
1 parent ae6d2e8 commit ab8e2ef
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 14 deletions.
3 changes: 3 additions & 0 deletions frost-core/src/round1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ pub struct GroupCommitmentShare<C: Ciphersuite>(pub(super) Element<C>);
/// commitment list.
///
/// [`encode_group_commitment_list()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-list-operations
#[cfg(feature = "internals")]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(super) fn encode_group_commitments<C: Ciphersuite>(
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
) -> Result<Vec<u8>, Error<C>> {
Expand Down
159 changes: 151 additions & 8 deletions frost-rerandomized/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
//! To sign with re-randomized FROST:
//!
//! - Do Round 1 the same way as regular FROST;
//! - The Coordinator should call [`RandomizedParams::new()`] and send
//! the [`RandomizedParams::randomizer`] to all participants, using a
//! confidential channel, along with the regular [`frost::SigningPackage`];
//! - Each participant should call [`sign`] and send the resulting
//! - The Coordinator should call [`RandomizedParams::new_from_commitments()`]
//! and send the generate randomizer seed (the second returned value) to all
//! participants, using a confidential channel, along with the regular
//! [`frost::SigningPackage`];
//! - Each participant should regenerate the RandomizerParams by calling
//! [`RandomizedParams::regenerate_from_seed_and_commitments()`], which they
//! should pass to [`sign_with_randomizer_seed()`] and send the resulting
//! [`frost::round2::SignatureShare`] back to the Coordinator;
//! - The Coordinator should then call [`aggregate`].
#![cfg_attr(not(feature = "std"), no_std)]
Expand All @@ -27,16 +30,17 @@ use frost_core::SigningPackage;
use frost_core::{
self as frost,
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
round1::encode_group_commitments,
round1::SigningCommitments,
serialization::SerializableScalar,
Ciphersuite, Error, Field, Group, Scalar, VerifyingKey,
Ciphersuite, Error, Field, Group, Identifier, Scalar, VerifyingKey,
};

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

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

/// Randomize the given key type for usage in a FROST signing with re-randomized keys,
Expand Down Expand Up @@ -122,6 +126,9 @@ impl<C: Ciphersuite> Randomize<C> for PublicKeyPackage<C> {
/// be sent from the Coordinator using a confidential channel.
///
/// See [`frost::round2::sign`] for documentation on the other parameters.
#[deprecated(
note = "switch to sign_with_randomizer_seed(), passing a seed generated with RandomizedParams::new_from_commitments()"
)]
pub fn sign<C: RandomizedCiphersuite>(
signing_package: &frost::SigningPackage<C>,
signer_nonces: &frost::round1::SigningNonces<C>,
Expand All @@ -134,6 +141,25 @@ pub fn sign<C: RandomizedCiphersuite>(
frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
}

/// Re-randomized FROST signing using the given `randomizer_seed`, which should
/// be sent from the Coordinator using a confidential channel.
///
/// See [`frost::round2::sign`] for documentation on the other parameters.
pub fn sign_with_randomizer_seed<C: RandomizedCiphersuite>(
signing_package: &frost::SigningPackage<C>,
signer_nonces: &frost::round1::SigningNonces<C>,
key_package: &frost::keys::KeyPackage<C>,
randomizer_seed: &[u8],
) -> Result<frost::round2::SignatureShare<C>, Error<C>> {
let randomized_params = RandomizedParams::regenerate_from_seed_and_commitments(
key_package.verifying_key(),
randomizer_seed,
signing_package.signing_commitments(),
)?;
let randomized_key_package = key_package.randomize(&randomized_params)?;
frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
}

/// Re-randomized FROST signature share aggregation with the given [`RandomizedParams`],
/// which can be computed from the previously generated randomizer using
/// [`RandomizedParams::from_randomizer`].
Expand Down Expand Up @@ -177,12 +203,15 @@ impl<C> Randomizer<C>
where
C: RandomizedCiphersuite,
{
/// Create a new random Randomizer.
/// Create a new random Randomizer using a SigningPackage for randomness.
///
/// The [`SigningPackage`] must be the signing package being used in the
/// current FROST signing run. It is hashed into the randomizer calculation,
/// which binds it to that specific package.
#[cfg(feature = "serialization")]
#[deprecated(
note = "switch to new_from_commitments(), passing the commitments from SigningPackage"
)]
pub fn new<R: RngCore + CryptoRng>(
mut rng: R,
signing_package: &SigningPackage<C>,
Expand Down Expand Up @@ -211,6 +240,63 @@ where
.ok_or(Error::SerializationError)?;
Ok(Self(SerializableScalar(randomizer)))
}

/// Create a new random Randomizer using SigningCommitments for randomness.
///
/// The [`SigningCommitments`] map must be the one being used in the current
/// FROST signing run (built by the Coordinator after receiving from
/// Participants). It is hashed into the randomizer calculation, which binds
/// it to that specific commitments.
///
/// Returns the Randomizer and the generate randomizer seed. Both can be
/// used to regenerate the Randomizer with
/// [`regenerate_from_seed_and_commitments()`].

Check warning on line 253 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `regenerate_from_seed_and_commitments`
pub fn new_from_commitments<R: RngCore + CryptoRng>(
mut rng: R,
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
) -> Result<(Self, Vec<u8>), Error<C>> {
// Generate a dummy scalar to get its encoded size
let one = <<C::Group as Group>::Field as Field>::zero();
let ns = <<C::Group as Group>::Field as Field>::serialize(&one)
.as_ref()
.len();
let mut randomizer_seed = alloc::vec![0; ns];
rng.fill_bytes(&mut randomizer_seed);
Ok((
Self::regenerate_from_seed_and_commitments(&randomizer_seed, signing_commitments)?,
randomizer_seed,
))
}

/// Regenerates a Randomizer generated with [`new_from_commitments()`]. This

Check warning on line 271 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `new_from_commitments`
/// can be used by Participants after receiving the randomizer seed and
/// commitments in Round 2. This is better than the Coordinator simply
/// generating a Randomizer and sending it to Participants, because in this
/// approach the participants don't need to fully trust the Coordinator's
/// random number generator (i.e. even if the randomizer seed was not
/// randomly generated the randomizer will still be).
///
/// This should be used exclusively with the output of
/// [`new_from_commitments()`]; it is strongly suggested to not attempt

Check warning on line 280 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `new_from_commitments`
/// generating the randomizer seed yourself (even if the point of this
/// approach is to hedge against issues in the randomizer seed generation).
pub fn regenerate_from_seed_and_commitments(
randomizer_seed: &[u8],
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
) -> Result<Randomizer<C>, Error<C>>
where
C: RandomizedCiphersuite,
{
let randomizer = C::hash_randomizer(
&[
randomizer_seed,
&encode_group_commitments(signing_commitments)?,
]
.concat(),
)
.ok_or(Error::SerializationError)?;
Ok(Self(SerializableScalar(randomizer)))
}
}

impl<C> Randomizer<C>
Expand Down Expand Up @@ -266,18 +352,75 @@ where
C: RandomizedCiphersuite,
{
/// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and
/// the given `participants`.
/// the given [`SigningPackage`].
#[cfg(feature = "serialization")]
#[deprecated(
note = "switch to new_from_commitments(), passing the commitments from SigningPackage"
)]
pub fn new<R: RngCore + CryptoRng>(
group_verifying_key: &VerifyingKey<C>,
signing_package: &SigningPackage<C>,
rng: R,
) -> Result<Self, Error<C>> {
#[allow(deprecated)]
Ok(Self::from_randomizer(
group_verifying_key,
Randomizer::new(rng, signing_package)?,
))
}

/// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and the
/// given signing commitments.
///
/// The [`SigningCommitments`] map must be the one being used in the current
/// FROST signing run (built by the Coordinator after receiving from
/// Participants). It is hashed into the randomizer calculation, which binds
/// it to that specific commitments.
///
/// Returns the generated [`RandomizedParams`] and a randomizer seed. Both
/// can be used to regenerate the [`RandomizedParams`] with
/// [`regenerate_from_seed_and_commitments()`].

Check warning on line 382 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `regenerate_from_seed_and_commitments`
pub fn new_from_commitments<R: RngCore + CryptoRng>(
group_verifying_key: &VerifyingKey<C>,
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
rng: R,
) -> Result<(Self, Vec<u8>), Error<C>> {
let (randomizer, randomizer_seed) =
Randomizer::new_from_commitments(rng, signing_commitments)?;
Ok((
Self::from_randomizer(group_verifying_key, randomizer),
randomizer_seed,
))
}

/// Regenerate a [`RandomizedParams`] with the given [`VerifyingKey`] from
/// the given given signing commitments.
///
/// Returns the generated [`RandomizedParams`] and a randomizer seed, which
/// can be used to regenerate the [`RandomizerParams`].

Check warning on line 400 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `RandomizerParams`
///
/// Regenerates a [`RandomizedParams`] generated with
/// [`new_from_commitments()`]. This can be used by Participants after

Check warning on line 403 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `new_from_commitments`
/// receiving the randomizer seed and commitments in Round 2. This is better
/// than the Coordinator simply generating a [`Randomizer`] and sending it
/// to Participants, because in this approach the participants don't need to
/// fully trust the Coordinator's random number generator (i.e. even if the
/// randomizer seed was not randomly generated the randomizer will still
/// be).
///
/// This should be used exclusively with the output of
/// [`new_from_commitments()`]; it is strongly suggested to not attempt

Check warning on line 412 in frost-rerandomized/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unresolved link to `new_from_commitments`
/// generating the randomizer seed yourself (even if the point of this
/// approach is to hedge against issues in the randomizer seed generation).
pub fn regenerate_from_seed_and_commitments(
group_verifying_key: &VerifyingKey<C>,
randomizer_seed: &[u8],
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
) -> Result<Self, Error<C>> {
let randomizer =
Randomizer::regenerate_from_seed_and_commitments(randomizer_seed, signing_commitments)?;
Ok(Self::from_randomizer(group_verifying_key, randomizer))
}
}

impl<C> RandomizedParams<C>
Expand Down
61 changes: 55 additions & 6 deletions frost-rerandomized/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use alloc::collections::BTreeMap;
use alloc::vec::Vec;

use crate::{frost_core as frost, RandomizedCiphersuite, RandomizedParams, Randomizer};
use frost_core::{Field, Group, Signature, SigningPackage, VerifyingKey};
use frost_core::{
round1::SigningCommitments, Field, Group, Identifier, Signature, SigningPackage, VerifyingKey,
};
use rand_core::{CryptoRng, RngCore};

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

check_randomizer(&pubkeys, &signing_package, &mut rng);
let randomizer_params =
RandomizedParams::new(pubkeys.verifying_key(), &signing_package, &mut rng).unwrap();
let randomizer = randomizer_params.randomizer();
let (randomizer_params, randomizer_seed) = RandomizedParams::new_from_commitments(
pubkeys.verifying_key(),
signing_package.signing_commitments(),
&mut rng,
)
.unwrap();

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

// Each participant generates their signature share.
let signature_share =
crate::sign(&signing_package, nonces_to_use, key_package, *randomizer).unwrap();
let signature_share = crate::sign_with_randomizer_seed(
&signing_package,
nonces_to_use,
key_package,
&randomizer_seed,
)
.unwrap();
signature_shares.insert(*participant_identifier, signature_share);
}

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

check_from_randomizer_and_signing_package(&mut rng, signing_package);

check_from_seed_and_signing_commitments(&mut rng, signing_package.signing_commitments());
}

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

let randomizer_params = RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer);
Expand Down Expand Up @@ -176,3 +189,39 @@ fn check_from_randomizer_and_signing_package<C: RandomizedCiphersuite, R: RngCor
// Make sure that different packages lead to different randomizers
assert!(randomizer1 != randomizer2);
}

fn check_from_seed_and_signing_commitments<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
mut rng: &mut R,
signing_commitments: &BTreeMap<Identifier<C>, SigningCommitments<C>>,
) {
// Make sure regeneration returns the same Randomizer.
let (randomizer1, randomizer_seed1) =
Randomizer::new_from_commitments(&mut rng, signing_commitments).unwrap();
let randomizer2 =
Randomizer::regenerate_from_seed_and_commitments(&randomizer_seed1, signing_commitments)
.unwrap();
assert!(randomizer1 == randomizer2);

let (randomizer2, randomizer_seed2) =
Randomizer::new_from_commitments(&mut rng, signing_commitments).unwrap();

// Make sure that different rng_randomizers lead to different randomizers
assert!(randomizer1 != randomizer2);
assert!(randomizer_seed1 != randomizer_seed2);

// Modify the commitments map, by overwriting the first entry with the value
// of the last entry.
let mut modified_signing_commitments = signing_commitments.clone();
modified_signing_commitments
.first_entry()
.unwrap()
.insert(*signing_commitments.last_key_value().unwrap().1);
let randomizer2 = Randomizer::regenerate_from_seed_and_commitments(
&randomizer_seed1,
&modified_signing_commitments,
)
.unwrap();

// Make sure that different packages lead to different randomizers
assert!(randomizer1 != randomizer2);
}

0 comments on commit ab8e2ef

Please sign in to comment.