generate Randomizer by hashing SigningPackage (#542)

This commit is contained in:
Conrado Gouvea 2023-09-22 11:20:11 -03:00 committed by GitHub
parent ba3ef7dbb8
commit c0c57f4b4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 178 additions and 20 deletions

View File

@ -26,6 +26,7 @@ rustdoc-args = ["--cfg", "docsrs"]
curve25519-dalek = { version = "=4.1.0", features = ["rand_core"] }
document-features = "0.2.7"
frost-core = { path = "../frost-core", version = "0.7.0" }
frost-rerandomized = { path = "../frost-rerandomized", version = "0.7.0" }
rand_core = "0.6"
sha2 = "0.10.2"

View File

@ -13,6 +13,7 @@ use curve25519_dalek::{
scalar::Scalar,
traits::Identity,
};
use frost_rerandomized::RandomizedCiphersuite;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
@ -209,6 +210,16 @@ impl Ciphersuite for Ed25519Sha512 {
}
}
impl RandomizedCiphersuite for Ed25519Sha512 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[
CONTEXT_STRING.as_bytes(),
b"randomizer",
m,
]))
}
}
type E = Ed25519Sha512;
/// A FROST(Ed25519, SHA-512) participant identifier.

View File

@ -25,6 +25,7 @@ rustdoc-args = ["--cfg", "docsrs"]
document-features = "0.2.7"
ed448-goldilocks = { version = "0.9.0" }
frost-core = { path = "../frost-core", version = "0.7.0" }
frost-rerandomized = { path = "../frost-rerandomized", version = "0.7.0" }
rand_core = "0.6"
sha3 = "0.10.6"

View File

@ -11,6 +11,7 @@ use ed448_goldilocks::{
curve::{edwards::CompressedEdwardsY, ExtendedPoint},
Scalar,
};
use frost_rerandomized::RandomizedCiphersuite;
use rand_core::{CryptoRng, RngCore};
use sha3::{
digest::{ExtendableOutput, Update, XofReader},
@ -204,6 +205,16 @@ impl Ciphersuite for Ed448Shake256 {
}
}
impl RandomizedCiphersuite for Ed448Shake256 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[
CONTEXT_STRING.as_bytes(),
b"randomizer",
m,
]))
}
}
type E = Ed448Shake256;
/// A FROST(Ed448, SHAKE256) participant identifier.

View File

@ -26,6 +26,7 @@ rustdoc-args = ["--cfg", "docsrs"]
document-features = "0.2.7"
p256 = { version = "0.13.0", features = ["hash2curve"] }
frost-core = { path = "../frost-core", version = "0.7.0" }
frost-rerandomized = { path = "../frost-rerandomized", version = "0.7.0" }
rand_core = "0.6"
sha2 = "0.10.2"

View File

@ -7,6 +7,7 @@
use std::collections::BTreeMap;
use frost_rerandomized::RandomizedCiphersuite;
use p256::{
elliptic_curve::{
hash2curve::{hash_to_field, ExpandMsgXmd},
@ -235,6 +236,15 @@ impl Ciphersuite for P256Sha256 {
}
}
impl RandomizedCiphersuite for P256Sha256 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(
(CONTEXT_STRING.to_owned() + "randomizer").as_bytes(),
m,
))
}
}
// Shorthand alias for the ciphersuite
type P = P256Sha256;

View File

@ -3,7 +3,7 @@
//! To sign with re-randomized FROST:
//!
//! - Do Round 1 the same way as regular FROST;
//! - The Coordinator should generate a [`RandomizedParams`] and send
//! - 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
@ -23,6 +23,7 @@ use frost_core::{
frost::{
self,
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
SigningPackage,
},
Ciphersuite, Error, Field, Group, Scalar, VerifyingKey,
};
@ -45,6 +46,12 @@ trait Randomize<C> {
C: Ciphersuite;
}
/// A Ciphersuite that supports rerandomization.
pub trait RandomizedCiphersuite: Ciphersuite {
/// A hash function that hashes into a randomizer scalar.
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar>;
}
impl<C: Ciphersuite> Randomize<C> for KeyPackage<C> {
/// Randomize the given [`KeyPackage`] for usage in a re-randomized FROST signing,
/// using the given [`RandomizedParams`].
@ -113,7 +120,7 @@ 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.
pub fn sign<C: Ciphersuite>(
pub fn sign<C: RandomizedCiphersuite>(
signing_package: &frost::SigningPackage<C>,
signer_nonces: &frost::round1::SigningNonces<C>,
key_package: &frost::keys::KeyPackage<C>,
@ -158,15 +165,52 @@ pub struct Randomizer<C: Ciphersuite>(Scalar<C>);
impl<C> Randomizer<C>
where
C: Ciphersuite,
C: RandomizedCiphersuite,
{
/// 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)
///
/// 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.
pub fn new<R: RngCore + CryptoRng>(
mut rng: R,
signing_package: &SigningPackage<C>,
) -> Result<Self, Error<C>> {
let rng_randomizer = <<C::Group as Group>::Field as Field>::random(&mut rng);
Self::from_randomizer_and_signing_package(rng_randomizer, signing_package)
}
/// Create a new Randomizer from the given scalar. It MUST be randomly generated.
/// Create a final Randomizer from a random Randomizer and a SigningPackage.
/// Function refactored out for testing, should always be private.
fn from_randomizer_and_signing_package(
rng_randomizer: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
signing_package: &SigningPackage<C>,
) -> Result<Randomizer<C>, Error<C>>
where
C: RandomizedCiphersuite,
{
let randomizer = C::hash_randomizer(
&[
<<C::Group as Group>::Field>::serialize(&rng_randomizer).as_ref(),
&signing_package.serialize()?,
]
.concat(),
)
.ok_or(Error::SerializationError)?;
Ok(Self(randomizer))
}
}
impl<C> Randomizer<C>
where
C: Ciphersuite,
{
/// Create a new Randomizer from the given scalar. It MUST be randomly
/// generated.
///
/// It is not recommended to use this method unless for compatibility
/// reasons with specifications on how the randomizer must be generated. Use
/// [`Randomizer::new()`] instead.
pub fn from_scalar(scalar: Scalar<C>) -> Self {
Self(scalar)
}
@ -221,14 +265,26 @@ pub struct RandomizedParams<C: Ciphersuite> {
impl<C> RandomizedParams<C>
where
C: Ciphersuite,
C: RandomizedCiphersuite,
{
/// 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))
pub fn new<R: RngCore + CryptoRng>(
group_verifying_key: &VerifyingKey<C>,
signing_package: &SigningPackage<C>,
rng: R,
) -> Result<Self, Error<C>> {
Ok(Self::from_randomizer(
group_verifying_key,
Randomizer::new(rng, signing_package)?,
))
}
}
impl<C> RandomizedParams<C>
where
C: Ciphersuite,
{
/// 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

View File

@ -2,14 +2,14 @@
use std::collections::BTreeMap;
use crate::{frost_core::frost, frost_core::Ciphersuite, RandomizedParams, Randomizer};
use frost_core::{Signature, VerifyingKey};
use crate::{frost_core::frost, RandomizedCiphersuite, RandomizedParams, Randomizer};
use frost_core::{frost::SigningPackage, Field, Group, Signature, VerifyingKey};
use rand_core::{CryptoRng, RngCore};
/// Test re-randomized FROST signing with trusted dealer with a Ciphersuite.
/// Returns the signed message, generated signature, and the randomized public key
/// so that the caller can verify the signature with their own implementation.
pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
pub fn check_randomized_sign_with_dealer<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
mut rng: R,
) -> (Vec<u8>, Signature<C>, VerifyingKey<C>) {
////////////////////////////////////////////////////////////////////////////
@ -39,10 +39,6 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
let mut commitments: BTreeMap<frost::Identifier<C>, frost::round1::SigningCommitments<C>> =
BTreeMap::new();
check_from_randomizer(&pubkeys, &mut rng);
let randomizer_params = RandomizedParams::new(pubkeys.verifying_key(), &mut rng);
let randomizer = randomizer_params.randomizer();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////
@ -70,6 +66,11 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
let message = "message to sign".as_bytes();
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();
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
@ -119,13 +120,56 @@ pub fn check_randomized_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>
)
}
fn check_from_randomizer<C: Ciphersuite, R: RngCore + CryptoRng>(
fn check_randomizer<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
pubkeys: &frost::keys::PublicKeyPackage<C>,
rng: &mut R,
signing_package: &frost::SigningPackage<C>,
mut rng: &mut R,
) {
let randomizer = Randomizer::new(rng);
check_from_randomizer(&mut rng, signing_package, pubkeys);
check_from_randomizer_and_signing_package(&mut rng, signing_package);
}
fn check_from_randomizer<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
rng: &mut R,
signing_package: &SigningPackage<C>,
pubkeys: &frost::keys::PublicKeyPackage<C>,
) {
let randomizer = Randomizer::new(rng, signing_package).unwrap();
let randomizer_params = RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer);
assert!(*randomizer_params.randomizer() == randomizer);
}
fn check_from_randomizer_and_signing_package<C: RandomizedCiphersuite, R: RngCore + CryptoRng>(
mut rng: &mut R,
signing_package: &SigningPackage<C>,
) {
let rng_randomizer1 = <<C::Group as Group>::Field as Field>::random(&mut rng);
let rng_randomizer2 = <<C::Group as Group>::Field as Field>::random(&mut rng);
let randomizer1 =
Randomizer::from_randomizer_and_signing_package(rng_randomizer1, signing_package);
let randomizer2 =
Randomizer::from_randomizer_and_signing_package(rng_randomizer1, signing_package);
// Make sure same inputs lead to same randomizer (and that equality works)
assert!(randomizer1 == randomizer2);
let randomizer2 =
Randomizer::from_randomizer_and_signing_package(rng_randomizer2, signing_package);
// Make sure that different rng_randomizers lead to different randomizers
assert!(randomizer1 != randomizer2);
let signing_package2 = SigningPackage::new(
signing_package.signing_commitments().clone(),
"fresh new message".as_bytes(),
);
let randomizer2 =
Randomizer::from_randomizer_and_signing_package(rng_randomizer1, &signing_package2);
// Make sure that different packages lead to different randomizers
assert!(randomizer1 != randomizer2);
}

View File

@ -22,6 +22,7 @@ rustdoc-args = ["--cfg", "docsrs"]
curve25519-dalek = { version = "=4.1.0", features = ["serde", "rand_core"] }
document-features = "0.2.7"
frost-core = { path = "../frost-core", version = "0.7.0" }
frost-rerandomized = { path = "../frost-rerandomized", version = "0.7.0" }
rand_core = "0.6"
sha2 = "0.10.2"

View File

@ -10,6 +10,7 @@ use curve25519_dalek::{
scalar::Scalar,
traits::Identity,
};
use frost_rerandomized::RandomizedCiphersuite;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
@ -195,6 +196,16 @@ impl Ciphersuite for Ristretto255Sha512 {
}
}
impl RandomizedCiphersuite for Ristretto255Sha512 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[
CONTEXT_STRING.as_bytes(),
b"randomizer",
m,
]))
}
}
type R = Ristretto255Sha512;
/// A FROST(ristretto255, SHA-512) participant identifier.

View File

@ -24,6 +24,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
document-features = "0.2.7"
frost-core = { path = "../frost-core", version = "0.7.0" }
frost-rerandomized = { path = "../frost-rerandomized", version = "0.7.0" }
k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"] }
rand_core = "0.6"
sha2 = "0.10.2"

View File

@ -7,6 +7,7 @@
use std::collections::BTreeMap;
use frost_rerandomized::RandomizedCiphersuite;
use k256::{
elliptic_curve::{
group::prime::PrimeCurveAffine,
@ -235,6 +236,15 @@ impl Ciphersuite for Secp256K1Sha256 {
}
}
impl RandomizedCiphersuite for Secp256K1Sha256 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(
(CONTEXT_STRING.to_owned() + "randomizer").as_bytes(),
m,
))
}
}
type S = Secp256K1Sha256;
/// A FROST(secp256k1, SHA-256) participant identifier.