Simplify rerandomized FROST (#437)
* refactor Lagrange coefficient computation * simplified rerandomized FROST * switch to a Randomize trait, remove unaccurate comment * remove manual rerandomization test * improve comments * removed unneeded alpha_share * Apply suggestions from code review Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com> * frost-rerandomized: add serde feature * add Randomizer type * revert DuplicatedIdentifiers back to DuplicatedIdentifier --------- Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>
This commit is contained in:
parent
4bf92b7a2c
commit
ba8086db5c
|
@ -24,8 +24,8 @@ pub enum Error<C: Ciphersuite> {
|
|||
#[error("Malformed identifier is unserializable.")]
|
||||
MalformedIdentifier,
|
||||
/// This identifier is duplicated.
|
||||
#[error("Duplicated identifiers.")]
|
||||
DuplicatedIdentifiers,
|
||||
#[error("Duplicated identifier.")]
|
||||
DuplicatedIdentifier,
|
||||
/// This identifier does not belong to a participant in the signing process.
|
||||
#[error("Unknown identifier.")]
|
||||
UnknownIdentifier,
|
||||
|
@ -140,7 +140,7 @@ where
|
|||
| Error::DKGNotSupported
|
||||
| Error::FieldError(_)
|
||||
| Error::GroupError(_)
|
||||
| Error::DuplicatedIdentifiers
|
||||
| Error::DuplicatedIdentifier
|
||||
| Error::InvalidCoefficient
|
||||
| Error::UnknownIdentifier
|
||||
| Error::IncorrectNumberOfIdentifiers
|
||||
|
|
|
@ -192,8 +192,10 @@ fn compute_lagrange_coefficient<C: Ciphersuite>(
|
|||
return Err(Error::UnknownIdentifier);
|
||||
}
|
||||
|
||||
Ok(num
|
||||
* <<C::Group as Group>::Field>::invert(&den).map_err(|_| Error::DuplicatedIdentifiers)?)
|
||||
Ok(
|
||||
num * <<C::Group as Group>::Field>::invert(&den)
|
||||
.map_err(|_| Error::DuplicatedIdentifier)?,
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates the lagrange coefficient for the i'th participant (for `signer_id`).
|
||||
|
|
|
@ -56,6 +56,18 @@ impl<C> SigningShare<C>
|
|||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
/// Create a new [`SigningShare`] from a scalar.
|
||||
#[cfg(feature = "internals")]
|
||||
pub fn new(scalar: Scalar<C>) -> Self {
|
||||
Self(scalar)
|
||||
}
|
||||
|
||||
/// Get the inner scalar.
|
||||
#[cfg(feature = "internals")]
|
||||
pub fn to_scalar(&self) -> Scalar<C> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Deserialize from bytes
|
||||
pub fn deserialize(
|
||||
bytes: <<C::Group as Group>::Field as Field>::Serialization,
|
||||
|
@ -143,6 +155,18 @@ impl<C> VerifyingShare<C>
|
|||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
/// Create a new [`VerifyingShare`] from a element.
|
||||
#[cfg(feature = "internals")]
|
||||
pub fn new(element: Element<C>) -> Self {
|
||||
Self(element)
|
||||
}
|
||||
|
||||
/// Get the inner element.
|
||||
#[cfg(feature = "internals")]
|
||||
pub fn to_element(&self) -> Element<C> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Deserialize from bytes
|
||||
pub fn deserialize(bytes: <C::Group as Group>::Serialization) -> Result<Self, Error<C>> {
|
||||
<C::Group as Group>::deserialize(&bytes)
|
||||
|
@ -718,7 +742,7 @@ pub(crate) fn generate_secret_shares<C: Ciphersuite>(
|
|||
|
||||
let identifiers_set: HashSet<_> = identifiers.iter().collect();
|
||||
if identifiers_set.len() != identifiers.len() {
|
||||
return Err(Error::DuplicatedIdentifiers);
|
||||
return Err(Error::DuplicatedIdentifier);
|
||||
}
|
||||
|
||||
for id in identifiers {
|
||||
|
@ -763,7 +787,7 @@ pub fn reconstruct<C: Ciphersuite>(
|
|||
.collect();
|
||||
|
||||
if identifiers.len() != secret_shares.len() {
|
||||
return Err(Error::DuplicatedIdentifiers);
|
||||
return Err(Error::DuplicatedIdentifier);
|
||||
}
|
||||
|
||||
// Compute the Lagrange coefficients
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn repair_share_step_1<C: Ciphersuite, R: RngCore + CryptoRng>(
|
|||
}
|
||||
let xset: BTreeSet<_> = helpers.iter().cloned().collect();
|
||||
if xset.len() != helpers.len() {
|
||||
return Err(Error::DuplicatedIdentifiers);
|
||||
return Err(Error::DuplicatedIdentifier);
|
||||
}
|
||||
|
||||
let rand_val: Vec<Scalar<C>> = generate_coefficients::<C, R>(helpers.len() - 1, rng);
|
||||
|
|
|
@ -96,8 +96,10 @@ pub trait Field: Copy + Clone {
|
|||
pub type Scalar<C> = <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(feature = "internals", visibility::make(pub))]
|
||||
/// Helper struct to serialize a Scalar.
|
||||
pub(crate) struct ScalarSerialization<C: Ciphersuite>(
|
||||
<<<C as Ciphersuite>::Group as Group>::Field as Field>::Serialization,
|
||||
pub <<<C as Ciphersuite>::Group as Group>::Field as Field>::Serialization,
|
||||
);
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
|
@ -51,6 +51,20 @@ where
|
|||
|
||||
Signature { R, z }
|
||||
}
|
||||
|
||||
/// Creates a SigningKey from a scalar.
|
||||
#[cfg(feature = "internals")]
|
||||
pub fn from_scalar(
|
||||
scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
|
||||
) -> Self {
|
||||
Self { scalar }
|
||||
}
|
||||
|
||||
/// Return the underlying scalar.
|
||||
#[cfg(feature = "internals")]
|
||||
pub fn to_scalar(self) -> <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
|
||||
self.scalar
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::fmt::Debug for SigningKey<C>
|
||||
|
|
|
@ -55,7 +55,7 @@ pub fn check_share_generation<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R
|
|||
|
||||
assert_eq!(
|
||||
frost::keys::reconstruct::<C>(&secret_shares).unwrap_err(),
|
||||
Error::DuplicatedIdentifiers
|
||||
Error::DuplicatedIdentifier
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,8 @@ pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
|
|||
check_sign(min_signers, key_packages, rng, pubkeys)
|
||||
}
|
||||
|
||||
fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
|
||||
/// Test FROST signing with the given shares.
|
||||
pub fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
|
||||
min_signers: u16,
|
||||
key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>>,
|
||||
mut rng: R,
|
||||
|
@ -372,7 +373,7 @@ pub fn check_sign_with_dealer_and_identifiers<C: Ciphersuite, R: RngCore + Crypt
|
|||
&mut rng,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, Error::DuplicatedIdentifiers);
|
||||
assert_eq!(err, Error::DuplicatedIdentifier);
|
||||
|
||||
// Check correct case
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ sha2 = "0.10.2"
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
frost-rerandomized = { path = "../frost-rerandomized", version = "0.6.0", features = ["test-impl"] }
|
||||
ed25519-dalek = "1.0.1"
|
||||
hex = "0.4.3"
|
||||
lazy_static = "1.4"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use frost_ed25519::Ed25519Sha512;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_randomized_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
let (_msg, _group_signature, _group_pubkey) =
|
||||
frost_rerandomized::tests::check_randomized_sign_with_dealer::<Ed25519Sha512, _>(rng);
|
||||
}
|
|
@ -28,6 +28,7 @@ sha3 = "0.10.6"
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
frost-rerandomized = { path = "../frost-rerandomized", version = "0.6.0", features = ["test-impl"] }
|
||||
lazy_static = "1.4"
|
||||
hex = "0.4.3"
|
||||
proptest = "1.0"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use frost_ed448::Ed448Shake256;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_randomized_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
let (_msg, _group_signature, _group_pubkey) =
|
||||
frost_rerandomized::tests::check_randomized_sign_with_dealer::<Ed448Shake256, _>(rng);
|
||||
}
|
|
@ -29,6 +29,7 @@ sha2 = "0.10.2"
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
frost-rerandomized = { path = "../frost-rerandomized", version = "0.6.0", features = ["test-impl"] }
|
||||
hex = "0.4.3"
|
||||
lazy_static = "1.4"
|
||||
proptest = "1.0"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use frost_p256::P256Sha256;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_randomized_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
let (_msg, _group_signature, _group_pubkey) =
|
||||
frost_rerandomized::tests::check_randomized_sign_with_dealer::<P256Sha256, _>(rng);
|
||||
}
|
|
@ -19,6 +19,7 @@ description = "Types and traits to support implementing a re-randomized variant
|
|||
features = ["nightly"]
|
||||
|
||||
[dependencies]
|
||||
derive-getters = "0.3.0"
|
||||
frost-core = { path = "../frost-core", version = "0.6.0", features = ["internals"] }
|
||||
rand_core = "0.6"
|
||||
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
//! Randomized FROST support.
|
||||
//! FROST implementation supporting re-randomizable keys.
|
||||
//!
|
||||
//! To sign with re-randomized FROST:
|
||||
//!
|
||||
//! - Do Round 1 the same way as regular FROST;
|
||||
//! - The Coordinator should generate a [`RandomizedParams`] and send
|
||||
//! the [`RandomizedParams::randomizer`] to all participants, using a
|
||||
//! confidential channel, along with the regular [`SigningPackage`];
|
||||
//! - Each participant should call [`sign`] and send the resulting
|
||||
//! [`SignatureShare`] back to the Coordinator;
|
||||
//! - The Coordinator should then call [`aggregate`].
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
#[cfg(any(test, feature = "test-impl"))]
|
||||
|
@ -7,91 +16,119 @@ pub mod tests;
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use derive_getters::Getters;
|
||||
pub use frost_core;
|
||||
|
||||
use frost_core::{
|
||||
frost::{self, keys::PublicKeyPackage},
|
||||
frost::{
|
||||
self,
|
||||
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
|
||||
},
|
||||
Ciphersuite, Error, Field, Group, Scalar, VerifyingKey,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use frost_core::serde;
|
||||
#[cfg(feature = "serde")]
|
||||
use frost_core::ScalarSerialization;
|
||||
|
||||
// When pulled into `reddsa`, that has its own sibling `rand_core` import.
|
||||
// For the time being, we do not re-export this `rand_core`.
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// Performed once by each participant selected for the signing operation.
|
||||
/// Randomize the given key type for usage in a FROST signing with re-randomized keys,
|
||||
/// using the given [`RandomizedParams`].
|
||||
trait Randomize<C> {
|
||||
fn randomize(&self, params: &RandomizedParams<C>) -> Result<Self, Error<C>>
|
||||
where
|
||||
Self: Sized,
|
||||
C: Ciphersuite;
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Randomize<C> for KeyPackage<C> {
|
||||
/// Randomize the given [`KeyPackage`] for usage in a re-randomized FROST signing,
|
||||
/// using the given [`RandomizedParams`].
|
||||
///
|
||||
/// It's recommended to use [`sign`] directly which already handles
|
||||
/// the key package randomization.
|
||||
///
|
||||
/// You MUST NOT reuse the randomized key package for more than one signing.
|
||||
fn randomize(&self, randomized_params: &RandomizedParams<C>) -> Result<Self, Error<C>>
|
||||
where
|
||||
Self: Sized,
|
||||
C: Ciphersuite,
|
||||
{
|
||||
let verifying_share = self.public();
|
||||
let randomized_verifying_share = VerifyingShare::<C>::new(
|
||||
verifying_share.to_element() + randomized_params.randomizer_element,
|
||||
);
|
||||
|
||||
let signing_share = self.secret_share();
|
||||
let randomized_signing_share =
|
||||
SigningShare::new(signing_share.to_scalar() + randomized_params.randomizer.0);
|
||||
|
||||
let randomized_key_package = KeyPackage::new(
|
||||
*self.identifier(),
|
||||
randomized_signing_share,
|
||||
randomized_verifying_share,
|
||||
randomized_params.randomized_verifying_key,
|
||||
);
|
||||
Ok(randomized_key_package)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Randomize<C> for PublicKeyPackage<C> {
|
||||
/// Randomized the given [`PublicKeyPackage`] for usage in a re-randomized FROST
|
||||
/// aggregation, using the given [`RandomizedParams`].
|
||||
///
|
||||
/// It's recommended to use [`aggregate`] directly which already handles
|
||||
/// the public key package randomization.
|
||||
fn randomize(&self, randomized_params: &RandomizedParams<C>) -> Result<Self, Error<C>>
|
||||
where
|
||||
Self: Sized,
|
||||
C: Ciphersuite,
|
||||
{
|
||||
let verifying_shares = self.signer_pubkeys().clone();
|
||||
let randomized_verifying_shares = verifying_shares
|
||||
.iter()
|
||||
.map(|(identifier, verifying_share)| {
|
||||
(
|
||||
*identifier,
|
||||
VerifyingShare::<C>::new(
|
||||
verifying_share.to_element() + randomized_params.randomizer_element,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(PublicKeyPackage::new(
|
||||
randomized_verifying_shares,
|
||||
randomized_params.randomized_verifying_key,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-randomized FROST signing using the given `randomizer`, which should
|
||||
/// be sent from the Coordinator using a confidential channel.
|
||||
///
|
||||
/// Implements [`sign`] from the spec.
|
||||
///
|
||||
/// Receives the message to be signed and a set of signing commitments and a set
|
||||
/// of randomizing commitments to be used in that signing operation, including
|
||||
/// that for this participant.
|
||||
///
|
||||
/// Assumes the participant has already determined which nonce corresponds with
|
||||
/// the commitment that was assigned by the coordinator in the SigningPackage.
|
||||
///
|
||||
/// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-10.html#name-round-two-signature-share-g
|
||||
/// See [`frost::round2::sign`] for documentation on the other parameters.
|
||||
pub fn sign<C: Ciphersuite>(
|
||||
signing_package: &frost::SigningPackage<C>,
|
||||
signer_nonces: &frost::round1::SigningNonces<C>,
|
||||
key_package: &frost::keys::KeyPackage<C>,
|
||||
randomizer_point: &<C::Group as Group>::Element,
|
||||
randomizer: Randomizer<C>,
|
||||
) -> Result<frost::round2::SignatureShare<C>, Error<C>> {
|
||||
let public_key = key_package.group_public().to_element() + *randomizer_point;
|
||||
|
||||
// Encodes the signing commitment list produced in round one as part of generating [`Rho`], the
|
||||
// binding factor.
|
||||
let binding_factor_list = frost::compute_binding_factor_list(
|
||||
signing_package,
|
||||
key_package.group_public(),
|
||||
<C::Group as Group>::serialize(randomizer_point).as_ref(),
|
||||
);
|
||||
|
||||
let rho: frost::BindingFactor<C> = binding_factor_list[*key_package.identifier()].clone();
|
||||
|
||||
// Compute the group commitment from signing commitments produced in round one.
|
||||
let group_commitment = frost::compute_group_commitment(signing_package, &binding_factor_list)?;
|
||||
|
||||
// Compute Lagrange coefficient.
|
||||
let lambda_i = frost::derive_interpolating_value(key_package.identifier(), signing_package)?;
|
||||
|
||||
// Compute the per-message challenge.
|
||||
let challenge = frost_core::challenge::<C>(
|
||||
&group_commitment.to_element(),
|
||||
&public_key,
|
||||
signing_package.message().as_slice(),
|
||||
);
|
||||
|
||||
// Compute the Schnorr signature share.
|
||||
let signature_share = frost::round2::compute_signature_share(
|
||||
signer_nonces,
|
||||
rho,
|
||||
lambda_i,
|
||||
key_package,
|
||||
challenge,
|
||||
);
|
||||
|
||||
Ok(signature_share)
|
||||
let randomized_params =
|
||||
RandomizedParams::from_randomizer(key_package.group_public(), randomizer);
|
||||
let randomized_key_package = key_package.randomize(&randomized_params)?;
|
||||
frost::round2::sign(signing_package, signer_nonces, &randomized_key_package)
|
||||
}
|
||||
|
||||
/// Aggregates the shares into a verified signature to publish.
|
||||
/// Re-randomized FROST signature share aggregation with the given [`RandomizedParams`],
|
||||
/// which can be computed from the previously generated randomizer using
|
||||
/// [`RandomizedParams::from_randomizer`].
|
||||
///
|
||||
/// Resulting signature is compatible with verification of a plain SpendAuth
|
||||
/// signature.
|
||||
///
|
||||
/// If the aggegated signature does not verify, each participant's signature share
|
||||
/// is validated, to find the cheater(s). This approach is more efficient and secure
|
||||
/// as we don't need to verify all shares if the aggregate signature is verifiable
|
||||
/// under the public group key and message (which should be the common case).
|
||||
///
|
||||
/// This operation is performed by a coordinator that can communicate with all
|
||||
/// the signing participants before publishing the final signature. The
|
||||
/// coordinator can be one of the participants or a semi-trusted third party
|
||||
/// (who is trusted to not perform denial of service attacks, but does not learn
|
||||
/// any secret information). Note that because the coordinator is trusted to
|
||||
/// report misbehaving parties in order to avoid publishing an invalid
|
||||
/// signature, if the coordinator themselves is a signer and misbehaves, they
|
||||
/// can avoid that step. However, at worst, this results in a denial of
|
||||
/// service attack due to publishing an invalid signature.
|
||||
/// See [`frost::aggregate`] for documentation on the other parameters.
|
||||
pub fn aggregate<C>(
|
||||
signing_package: &frost::SigningPackage<C>,
|
||||
signature_shares: &HashMap<frost::Identifier<C>, frost::round2::SignatureShare<C>>,
|
||||
|
@ -101,151 +138,113 @@ pub fn aggregate<C>(
|
|||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
let public_key = randomized_params.randomized_group_public_key();
|
||||
|
||||
// Encodes the signing commitment list produced in round one as part of generating [`Rho`], the
|
||||
// binding factor.
|
||||
let binding_factor_list = frost::compute_binding_factor_list(
|
||||
let randomized_public_key_package = pubkeys.randomize(randomized_params)?;
|
||||
frost::aggregate(
|
||||
signing_package,
|
||||
pubkeys.group_public(),
|
||||
<C::Group as Group>::serialize(randomized_params.randomizer_point()).as_ref(),
|
||||
);
|
||||
|
||||
// Compute the group commitment from signing commitments produced in round one.
|
||||
let group_commitment = frost::compute_group_commitment(signing_package, &binding_factor_list)?;
|
||||
|
||||
// Compute the per-message challenge.
|
||||
let challenge = frost_core::challenge::<C>(
|
||||
&group_commitment.clone().to_element(),
|
||||
&public_key.to_element(),
|
||||
signing_package.message().as_slice(),
|
||||
);
|
||||
|
||||
// The aggregation of the signature shares by summing them up, resulting in
|
||||
// a plain Schnorr signature.
|
||||
//
|
||||
// Implements [`aggregate`] from the spec.
|
||||
//
|
||||
// [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-12.html#section-5.3
|
||||
let mut z = <<C::Group as Group>::Field as Field>::zero();
|
||||
|
||||
for signature_share in signature_shares.values() {
|
||||
z = z + *signature_share.share();
|
||||
}
|
||||
|
||||
z = z + challenge.clone().to_scalar() * randomized_params.randomizer;
|
||||
|
||||
let signature = frost_core::Signature::new(group_commitment.to_element(), z);
|
||||
|
||||
// Verify the aggregate signature
|
||||
let verification_result = public_key.verify(signing_package.message(), &signature);
|
||||
|
||||
// Only if the verification of the aggregate signature failed; verify each share to find the cheater.
|
||||
// This approach is more efficient since we don't need to verify all shares
|
||||
// if the aggregate signature is valid (which should be the common case).
|
||||
if let Err(err) = verification_result {
|
||||
// Verify the signature shares.
|
||||
for (signature_share_identifier, signature_share) in signature_shares {
|
||||
// Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_,
|
||||
// and where s[i] is a secret share of the constant term of _f_, the secret polynomial.
|
||||
let signer_pubkey = pubkeys
|
||||
.signer_pubkeys()
|
||||
.get(signature_share_identifier)
|
||||
.unwrap();
|
||||
|
||||
// Compute Lagrange coefficient.
|
||||
let lambda_i =
|
||||
frost::derive_interpolating_value(signature_share_identifier, signing_package)?;
|
||||
|
||||
let binding_factor = binding_factor_list[*signature_share_identifier].clone();
|
||||
|
||||
// Compute the commitment share.
|
||||
let R_share = signing_package
|
||||
.signing_commitment(signature_share_identifier)
|
||||
.to_group_commitment_share(&binding_factor);
|
||||
|
||||
// Compute relation values to verify this signature share.
|
||||
signature_share.verify(
|
||||
*signature_share_identifier,
|
||||
&R_share,
|
||||
signer_pubkey,
|
||||
lambda_i,
|
||||
&challenge,
|
||||
)?;
|
||||
}
|
||||
|
||||
// We should never reach here; but we return the verification error to be safe.
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(signature)
|
||||
signature_shares,
|
||||
&randomized_public_key_package,
|
||||
)
|
||||
}
|
||||
|
||||
/// Randomized params for a signing instance of randomized FROST.
|
||||
/// A randomizer. A random scalar which is used to randomize the key.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization<C>"))]
|
||||
#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization<C>"))]
|
||||
#[cfg_attr(feature = "serde", serde(crate = "self::serde"))]
|
||||
pub struct Randomizer<C: Ciphersuite>(Scalar<C>);
|
||||
|
||||
impl<C> Randomizer<C>
|
||||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
/// Create a new random Randomizer.
|
||||
pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> Self {
|
||||
let randomizer = <<C::Group as Group>::Field as Field>::random(&mut rng);
|
||||
Self(randomizer)
|
||||
}
|
||||
|
||||
/// Create a new Randomizer from the given scalar. It MUST be randomly generated.
|
||||
pub fn from_scalar(scalar: Scalar<C>) -> Self {
|
||||
Self(scalar)
|
||||
}
|
||||
|
||||
/// Serialize the identifier using the ciphersuite encoding.
|
||||
pub fn serialize(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
|
||||
<<C::Group as Group>::Field>::serialize(&self.0)
|
||||
}
|
||||
|
||||
/// Deserialize an Identifier from a serialized buffer.
|
||||
/// Returns an error if it attempts to deserialize zero.
|
||||
pub fn deserialize(
|
||||
buf: &<<C::Group as Group>::Field as Field>::Serialization,
|
||||
) -> Result<Self, Error<C>> {
|
||||
let scalar = <<C::Group as Group>::Field>::deserialize(buf)?;
|
||||
Ok(Self(scalar))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<C> TryFrom<ScalarSerialization<C>> for Randomizer<C>
|
||||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
type Error = Error<C>;
|
||||
|
||||
fn try_from(value: ScalarSerialization<C>) -> Result<Self, Self::Error> {
|
||||
Self::deserialize(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<C> From<Randomizer<C>> for ScalarSerialization<C>
|
||||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
fn from(value: Randomizer<C>) -> Self {
|
||||
Self(value.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
/// Randomized parameters for a signing instance of randomized FROST.
|
||||
#[derive(Clone, PartialEq, Eq, Getters)]
|
||||
pub struct RandomizedParams<C: Ciphersuite> {
|
||||
/// The randomizer, also called `alpha`
|
||||
randomizer: frost_core::Scalar<C>,
|
||||
/// The randomizer, also called α
|
||||
randomizer: Randomizer<C>,
|
||||
/// The generator multiplied by the randomizer.
|
||||
randomizer_point: <C::Group as Group>::Element,
|
||||
/// The randomized group public key. The group public key added to the randomizer point.
|
||||
randomized_group_public_key: frost_core::VerifyingKey<C>,
|
||||
randomizer_element: <C::Group as Group>::Element,
|
||||
/// The randomized group public key. The group public key added to the randomizer element.
|
||||
randomized_verifying_key: frost_core::VerifyingKey<C>,
|
||||
}
|
||||
|
||||
impl<C> RandomizedParams<C>
|
||||
where
|
||||
C: Ciphersuite,
|
||||
{
|
||||
/// Create a new RandomizedParams for the given [`PublicKeyPackage`]
|
||||
pub fn new<R: RngCore + CryptoRng>(
|
||||
public_key_package: &PublicKeyPackage<C>,
|
||||
mut rng: R,
|
||||
) -> Self {
|
||||
let randomizer = <<C::Group as Group>::Field as Field>::random(&mut rng);
|
||||
Self::from_randomizer(public_key_package, randomizer)
|
||||
/// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and
|
||||
/// the given `participants`.
|
||||
pub fn new<R: RngCore + CryptoRng>(group_verifying_key: &VerifyingKey<C>, rng: R) -> Self {
|
||||
Self::from_randomizer(group_verifying_key, Randomizer::new(rng))
|
||||
}
|
||||
|
||||
/// Create a new RandomizedParams for the given [`PublicKeyPackage`]
|
||||
/// with the given `randomizer`. The `randomizer` MUST be generated uniformly
|
||||
/// at random! Use [`RandomizedParams::new()`] which generates a fresh
|
||||
/// randomizer, unless your application requires generating a randomizer
|
||||
/// outside.
|
||||
/// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and the
|
||||
/// given `participants` for the given `randomizer`. The `randomizer` MUST
|
||||
/// be generated uniformly at random! Use [`RandomizedParams::new()`] which
|
||||
/// generates a fresh randomizer, unless your application requires generating
|
||||
/// a randomizer outside.
|
||||
pub fn from_randomizer(
|
||||
public_key_package: &PublicKeyPackage<C>,
|
||||
randomizer: Scalar<C>,
|
||||
group_verifying_key: &VerifyingKey<C>,
|
||||
randomizer: Randomizer<C>,
|
||||
) -> Self {
|
||||
let randomizer_point = <C::Group as Group>::generator() * randomizer;
|
||||
|
||||
let group_public_point = public_key_package.group_public().to_element();
|
||||
|
||||
let randomized_group_public_point = group_public_point + randomizer_point;
|
||||
let randomized_group_public_key = VerifyingKey::new(randomized_group_public_point);
|
||||
let randomizer_element = <C::Group as Group>::generator() * randomizer.0;
|
||||
let group_public_element = group_verifying_key.to_element();
|
||||
let randomized_group_public_element = group_public_element + randomizer_element;
|
||||
let randomized_verifying_key = VerifyingKey::<C>::new(randomized_group_public_element);
|
||||
|
||||
Self {
|
||||
randomizer,
|
||||
randomizer_point,
|
||||
randomized_group_public_key,
|
||||
randomizer_element,
|
||||
randomized_verifying_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the randomizer.
|
||||
///
|
||||
/// It can be useful to the coordinator, e.g. to generate the ZK proof
|
||||
/// in Zcash. It MUST NOT be sent to other parties.
|
||||
pub fn randomizer(&self) -> &frost_core::Scalar<C> {
|
||||
&self.randomizer
|
||||
}
|
||||
|
||||
/// Return the randomizer point.
|
||||
///
|
||||
/// It must be sent by the coordinator to each participant when signing.
|
||||
pub fn randomizer_point(&self) -> &<C::Group as Group>::Element {
|
||||
&self.randomizer_point
|
||||
}
|
||||
|
||||
/// Return the randomized group public key.
|
||||
///
|
||||
/// It can be used to verify the final signature.
|
||||
pub fn randomized_group_public_key(&self) -> &frost_core::VerifyingKey<C> {
|
||||
&self.randomized_group_public_key
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use crate::{frost_core::frost, frost_core::Ciphersuite, RandomizedParams};
|
||||
use frost_core::{Field, Group, Signature, VerifyingKey};
|
||||
use crate::{frost_core::frost, frost_core::Ciphersuite, RandomizedParams, Randomizer};
|
||||
use frost_core::{Signature, VerifyingKey};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// Test re-randomized FROST signing with trusted dealer with a Ciphersuite.
|
||||
|
@ -39,7 +39,8 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
|
|||
BTreeMap::new();
|
||||
|
||||
check_from_randomizer(&pubkeys, &mut rng);
|
||||
let randomizer_params = RandomizedParams::new(&pubkeys, &mut rng);
|
||||
let randomizer_params = RandomizedParams::new(pubkeys.group_public(), &mut rng);
|
||||
let randomizer = randomizer_params.randomizer();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Round 1: generating nonces and signing commitments for each participant
|
||||
|
@ -78,13 +79,8 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
|
|||
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_params.randomizer_point(),
|
||||
)
|
||||
.unwrap();
|
||||
let signature_share =
|
||||
crate::sign(&signing_package, nonces_to_use, key_package, *randomizer).unwrap();
|
||||
signature_shares.insert(*participant_identifier, signature_share);
|
||||
}
|
||||
|
||||
|
@ -108,7 +104,7 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
|
|||
// Check that the threshold signature can be verified by the randomized group public
|
||||
// key (the verification key).
|
||||
assert!(randomizer_params
|
||||
.randomized_group_public_key()
|
||||
.randomized_verifying_key()
|
||||
.verify(message, &group_signature)
|
||||
.is_ok());
|
||||
|
||||
|
@ -118,17 +114,17 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
|
|||
(
|
||||
message.to_owned(),
|
||||
group_signature,
|
||||
*randomizer_params.randomized_group_public_key(),
|
||||
*randomizer_params.randomized_verifying_key(),
|
||||
)
|
||||
}
|
||||
|
||||
fn check_from_randomizer<C: Ciphersuite, R: RngCore + CryptoRng>(
|
||||
pubkeys: &frost::keys::PublicKeyPackage<C>,
|
||||
mut rng: &mut R,
|
||||
rng: &mut R,
|
||||
) {
|
||||
let randomizer = <<C::Group as Group>::Field as Field>::random(&mut rng);
|
||||
let randomizer = Randomizer::new(rng);
|
||||
|
||||
let randomizer_params = RandomizedParams::from_randomizer(pubkeys, randomizer);
|
||||
let randomizer_params = RandomizedParams::from_randomizer(pubkeys.group_public(), randomizer);
|
||||
|
||||
assert!(*randomizer_params.randomizer() == randomizer);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ sha2 = "0.10.2"
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
frost-rerandomized = { path = "../frost-rerandomized", version = "0.6.0", features = ["test-impl"] }
|
||||
hex = "0.4.3"
|
||||
lazy_static = "1.4"
|
||||
proptest = "1.0"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use frost_ristretto255::Ristretto255Sha512;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_randomized_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
let (_msg, _group_signature, _group_pubkey) =
|
||||
frost_rerandomized::tests::check_randomized_sign_with_dealer::<Ristretto255Sha512, _>(rng);
|
||||
}
|
|
@ -28,6 +28,7 @@ sha2 = "0.10.2"
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
frost-rerandomized = { path = "../frost-rerandomized", version = "0.6.0", features = ["test-impl"] }
|
||||
hex = "0.4.3"
|
||||
lazy_static = "1.4"
|
||||
proptest = "1.0"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
use frost_secp256k1::Secp256K1Sha256;
|
||||
use rand::thread_rng;
|
||||
|
||||
#[test]
|
||||
fn check_randomized_sign_with_dealer() {
|
||||
let rng = thread_rng();
|
||||
|
||||
let (_msg, _group_signature, _group_pubkey) =
|
||||
frost_rerandomized::tests::check_randomized_sign_with_dealer::<Secp256K1Sha256, _>(rng);
|
||||
}
|
|
@ -324,6 +324,7 @@ fn main() -> ExitCode {
|
|||
"tests/common_traits_tests.rs",
|
||||
"tests/integration_tests.rs",
|
||||
"tests/recreation_tests.rs",
|
||||
"tests/rerandomized_tests.rs",
|
||||
"tests/serde_tests.rs",
|
||||
"tests/helpers/samples.rs",
|
||||
] {
|
||||
|
|
Loading…
Reference in New Issue