diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 27e1c229..be81cae0 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -369,6 +369,9 @@ pub struct GroupCommitmentShare(pub(super) Element); /// 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( signing_commitments: &BTreeMap, SigningCommitments>, ) -> Result, Error> { diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index a309d6f1..d2991572 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -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)] @@ -27,8 +30,10 @@ 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")] @@ -36,7 +41,6 @@ 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, @@ -122,6 +126,9 @@ impl Randomize for PublicKeyPackage { /// 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( signing_package: &frost::SigningPackage, signer_nonces: &frost::round1::SigningNonces, @@ -134,6 +141,25 @@ pub fn sign( 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( + signing_package: &frost::SigningPackage, + signer_nonces: &frost::round1::SigningNonces, + key_package: &frost::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result, Error> { + 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`]. @@ -177,12 +203,15 @@ impl Randomizer 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( mut rng: R, signing_package: &SigningPackage, @@ -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()`]. + pub fn new_from_commitments( + mut rng: R, + signing_commitments: &BTreeMap, SigningCommitments>, + ) -> Result<(Self, Vec), Error> { + // Generate a dummy scalar to get its encoded size + let one = <::Field as Field>::zero(); + let ns = <::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 + /// 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 + /// 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, SigningCommitments>, + ) -> Result, Error> + 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 Randomizer @@ -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( group_verifying_key: &VerifyingKey, signing_package: &SigningPackage, rng: R, ) -> Result> { + #[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()`]. + pub fn new_from_commitments( + group_verifying_key: &VerifyingKey, + signing_commitments: &BTreeMap, SigningCommitments>, + rng: R, + ) -> Result<(Self, Vec), Error> { + 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`]. + /// + /// Regenerates a [`RandomizedParams`] generated with + /// [`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 + /// [`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, + randomizer_seed: &[u8], + signing_commitments: &BTreeMap, SigningCommitments>, + ) -> Result> { + let randomizer = + Randomizer::regenerate_from_seed_and_commitments(randomizer_seed, signing_commitments)?; + Ok(Self::from_randomizer(group_verifying_key, randomizer)) + } } impl RandomizedParams diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index f29c91c9..0b526498 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -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. @@ -70,9 +72,12 @@ pub fn check_randomized_sign_with_dealer( 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( @@ -138,6 +150,7 @@ fn check_from_randomizer( signing_package: &SigningPackage, pubkeys: &frost::keys::PublicKeyPackage, ) { + #[allow(deprecated)] let randomizer = Randomizer::new(rng, signing_package).unwrap(); let randomizer_params = RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer); @@ -176,3 +189,39 @@ fn check_from_randomizer_and_signing_package( + mut rng: &mut R, + signing_commitments: &BTreeMap, SigningCommitments>, +) { + // 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); +}