Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frost-rerandomized: change Randomizer generation #762

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ jobs:
docs:
name: Check Rust doc
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -D warnings

steps:
- uses: actions/[email protected]
Expand Down
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
162 changes: 154 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,65 @@ 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
/// [`Self::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
/// [`Self::new_from_commitments()`]. This 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
/// [`Self::new_from_commitments()`]; it is strongly suggested to not
/// attempt 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 +354,76 @@ 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
/// [`Self::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 [`RandomizedParams`].
///
/// Regenerates a [`RandomizedParams`] generated with
/// [`Self::new_from_commitments()`]. This 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
/// [`Self::new_from_commitments()`]; it is strongly suggested to not
/// attempt 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);
}
Loading