From c0c57f4b4ba6a61e95401900768d9d6b58d1f0dc Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 22 Sep 2023 11:20:11 -0300 Subject: [PATCH] generate Randomizer by hashing SigningPackage (#542) --- frost-ed25519/Cargo.toml | 1 + frost-ed25519/src/lib.rs | 11 +++++ frost-ed448/Cargo.toml | 1 + frost-ed448/src/lib.rs | 11 +++++ frost-p256/Cargo.toml | 1 + frost-p256/src/lib.rs | 10 +++++ frost-rerandomized/src/lib.rs | 76 ++++++++++++++++++++++++++++----- frost-rerandomized/src/tests.rs | 64 ++++++++++++++++++++++----- frost-ristretto255/Cargo.toml | 1 + frost-ristretto255/src/lib.rs | 11 +++++ frost-secp256k1/Cargo.toml | 1 + frost-secp256k1/src/lib.rs | 10 +++++ 12 files changed, 178 insertions(+), 20 deletions(-) diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index d670561..b01f4cc 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -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" diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 4de4640..b152a8c 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -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<<::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. diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 24f74b9..0b524c8 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -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" diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index ccd1d45..edd53e3 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -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<<::Field as Field>::Scalar> { + Some(hash_to_scalar(&[ + CONTEXT_STRING.as_bytes(), + b"randomizer", + m, + ])) + } +} + type E = Ed448Shake256; /// A FROST(Ed448, SHAKE256) participant identifier. diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index cbac72e..5f50f91 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -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" diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 85c4742..7fa2fd8 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -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<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), + m, + )) + } +} + // Shorthand alias for the ciphersuite type P = P256Sha256; diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 1a8276b..38dca60 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -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: 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<<::Field as Field>::Scalar>; +} + impl Randomize for KeyPackage { /// Randomize the given [`KeyPackage`] for usage in a re-randomized FROST signing, /// using the given [`RandomizedParams`]. @@ -113,7 +120,7 @@ impl Randomize for PublicKeyPackage { /// be sent from the Coordinator using a confidential channel. /// /// See [`frost::round2::sign`] for documentation on the other parameters. -pub fn sign( +pub fn sign( signing_package: &frost::SigningPackage, signer_nonces: &frost::round1::SigningNonces, key_package: &frost::keys::KeyPackage, @@ -158,15 +165,52 @@ pub struct Randomizer(Scalar); impl Randomizer where - C: Ciphersuite, + C: RandomizedCiphersuite, { /// Create a new random Randomizer. - pub fn new(mut rng: R) -> Self { - let randomizer = <::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( + mut rng: R, + signing_package: &SigningPackage, + ) -> Result> { + let rng_randomizer = <::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: <<::Group as Group>::Field as Field>::Scalar, + signing_package: &SigningPackage, + ) -> Result, Error> + where + C: RandomizedCiphersuite, + { + let randomizer = C::hash_randomizer( + &[ + <::Field>::serialize(&rng_randomizer).as_ref(), + &signing_package.serialize()?, + ] + .concat(), + ) + .ok_or(Error::SerializationError)?; + Ok(Self(randomizer)) + } +} + +impl Randomizer +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) -> Self { Self(scalar) } @@ -221,14 +265,26 @@ pub struct RandomizedParams { impl RandomizedParams where - C: Ciphersuite, + C: RandomizedCiphersuite, { /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and /// the given `participants`. - pub fn new(group_verifying_key: &VerifyingKey, rng: R) -> Self { - Self::from_randomizer(group_verifying_key, Randomizer::new(rng)) + pub fn new( + group_verifying_key: &VerifyingKey, + signing_package: &SigningPackage, + rng: R, + ) -> Result> { + Ok(Self::from_randomizer( + group_verifying_key, + Randomizer::new(rng, signing_package)?, + )) } +} +impl RandomizedParams +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 diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index d29fe90..6966d32 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -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( +pub fn check_randomized_sign_with_dealer( mut rng: R, ) -> (Vec, Signature, VerifyingKey) { //////////////////////////////////////////////////////////////////////////// @@ -39,10 +39,6 @@ pub fn check_randomized_sign_with_dealer let mut commitments: BTreeMap, frost::round1::SigningCommitments> = 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 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 ) } -fn check_from_randomizer( +fn check_randomizer( pubkeys: &frost::keys::PublicKeyPackage, - rng: &mut R, + signing_package: &frost::SigningPackage, + 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( + rng: &mut R, + signing_package: &SigningPackage, + pubkeys: &frost::keys::PublicKeyPackage, +) { + 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( + mut rng: &mut R, + signing_package: &SigningPackage, +) { + let rng_randomizer1 = <::Field as Field>::random(&mut rng); + let rng_randomizer2 = <::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); +} diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 2d885af..e85407d 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -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" diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 986b584..548eb66 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -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<<::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. diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 8970205..068cfe2 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -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" diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index e8a5b5b..a150b4c 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -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<<::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.