From 4bd0d71406d2fe240bf90cfea8ca68719b29effd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 26 Jul 2024 21:00:05 -0400 Subject: [PATCH] Add initial eVRF DKG test --- Cargo.lock | 1 + crypto/dkg/Cargo.toml | 1 + crypto/dkg/src/evrf/mod.rs | 16 ++++++-- crypto/dkg/src/evrf/proof.rs | 37 ++++++++++++++---- crypto/dkg/src/tests/evrf/mod.rs | 63 ++++++++++++++++++++++++++++++ crypto/dkg/src/tests/evrf/proof.rs | 6 +-- 6 files changed, 111 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3298038..d1d0ed3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,6 +2141,7 @@ dependencies = [ "generic-array 1.1.0", "multiexp", "pasta_curves", + "rand", "rand_chacha", "rand_core", "schnorr-signatures", diff --git a/crypto/dkg/Cargo.toml b/crypto/dkg/Cargo.toml index e5afd311..0e9d4ab0 100644 --- a/crypto/dkg/Cargo.toml +++ b/crypto/dkg/Cargo.toml @@ -48,6 +48,7 @@ generalized-bulletproofs-ec-gadgets = { path = "../evrf/ec-gadgets", optional = [dev-dependencies] rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand = { version = "0.8", default-features = false, features = ["std"] } ciphersuite = { path = "../ciphersuite", default-features = false, features = ["ristretto"] } generalized-bulletproofs = { path = "../evrf/generalized-bulletproofs", features = ["tests"] } ec-divisors = { path = "../evrf/divisors", features = ["pasta"] } diff --git a/crypto/dkg/src/evrf/mod.rs b/crypto/dkg/src/evrf/mod.rs index b27dd303..dd994b0a 100644 --- a/crypto/dkg/src/evrf/mod.rs +++ b/crypto/dkg/src/evrf/mod.rs @@ -90,7 +90,8 @@ use ec_divisors::DivisorCurve; use crate::{Participant, DkgError, ThresholdParams, ThresholdCore}; pub(crate) mod proof; -pub use proof::*; +use proof::*; +pub use proof::EvrfCurve; /// Participation in the DKG. /// @@ -191,7 +192,7 @@ fn share_verification_statements( } /// Struct to perform/verify the DKG with. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct EvrfDkg { t: u16, n: u16, @@ -204,6 +205,11 @@ where <::EmbeddedCurve as Ciphersuite>::G: DivisorCurve::F>, { + /// Sample generators for this ciphersuite. + pub fn generators(max_threshold: u16, max_participants: u16) -> Generators { + Evrf::::generators(max_threshold, max_participants) + } + /// Participate in performing the DKG for the specified parameters. /// /// The context MUST be unique across invocations. Reuse of context will lead to sharing @@ -257,6 +263,10 @@ where evrf_public_keys: &[::G], participations: &HashMap>, ) -> Result> { + if generators.g() != C::generator() { + todo!("TODO"); + } + let Ok(n) = u16::try_from(evrf_public_keys.len()) else { todo!("TODO") }; if (t == 0) || (t > n) { todo!("TODO"); @@ -371,7 +381,7 @@ where } pub fn keys( - self, + &self, evrf_private_key: &Zeroizing<::F>, ) -> Option> { let evrf_public_key = ::generator() * evrf_private_key.deref(); diff --git a/crypto/dkg/src/evrf/proof.rs b/crypto/dkg/src/evrf/proof.rs index ada522bd..2f8cd444 100644 --- a/crypto/dkg/src/evrf/proof.rs +++ b/crypto/dkg/src/evrf/proof.rs @@ -33,6 +33,16 @@ pub trait EvrfCurve: Ciphersuite { type EmbeddedCurveParameters: DiscreteLogParameters; } +fn sample_point(rng: &mut (impl RngCore + CryptoRng)) -> C::G { + let mut repr = ::Repr::default(); + loop { + rng.fill_bytes(repr.as_mut()); + if let Ok(point) = C::read_G(&mut repr.as_ref()) { + return point; + } + } +} + /// The result of proving for an eVRF. pub(crate) struct EvrfProveResult { /// The coefficients for use in the DKG. @@ -44,6 +54,7 @@ pub(crate) struct EvrfProveResult { } /// The result of verifying an eVRF. +#[derive(Clone)] pub(crate) struct EvrfVerifyResult { /// The commitments to the coefficients for use in the DKG. pub(crate) coefficients: Vec, @@ -66,6 +77,22 @@ where <::EmbeddedCurve as Ciphersuite>::G: DivisorCurve::F>, { + // TODO: Wrap these Generators so we can enforce g == C::generator() with type safety + pub(crate) fn generators(max_threshold: u16, max_participants: u16) -> Generators { + let g = C::generator(); + let mut rng = ChaCha20Rng::from_seed(Blake2s256::digest(g.to_bytes()).into()); + let h = sample_point::(&mut rng); + let (_, generators) = + Evrf::::muls_and_generators_to_use(max_threshold.into(), max_participants.into()); + let mut g_bold = vec![]; + let mut h_bold = vec![]; + for _ in 0 .. generators { + g_bold.push(sample_point::(&mut rng)); + h_bold.push(sample_point::(&mut rng)); + } + Generators::new(g, h, g_bold, h_bold).unwrap() + } + // Sample uniform points (via rejection-sampling) on the embedded elliptic curve fn transcript_to_points( seed: [u8; 32], @@ -76,12 +103,8 @@ where let mut rng = ChaCha20Rng::from_seed(seed); let mut res = Vec::with_capacity(quantity); - while res.len() < quantity { - let mut repr = <::G as GroupEncoding>::Repr::default(); - rng.fill_bytes(repr.as_mut()); - if let Ok(point) = C::EmbeddedCurve::read_G(&mut repr.as_ref()) { - res.push(point); - } + for _ in 0 .. quantity { + res.push(sample_point::(&mut rng)); } res } @@ -154,7 +177,7 @@ where const MULS_PER_DH: usize = 7; // 1 DH to prove the discrete logarithm corresponds to the eVRF public key // 2 DHs per generated coefficient - // 2 DHs per generated ECDG + // 2 DHs per generated ECDH let expected_muls = MULS_PER_DH * (1 + (2 * coefficients) + (2 * 2 * ecdhs)); let generators_to_use = { let mut padded_pow_of_2 = 1; diff --git a/crypto/dkg/src/tests/evrf/mod.rs b/crypto/dkg/src/tests/evrf/mod.rs index 5e3cb98b..0649d9ff 100644 --- a/crypto/dkg/src/tests/evrf/mod.rs +++ b/crypto/dkg/src/tests/evrf/mod.rs @@ -1 +1,64 @@ +use std::collections::HashMap; + +use zeroize::Zeroizing; +use rand_core::OsRng; +use rand::seq::SliceRandom; + +use ciphersuite::{group::ff::Field, Ciphersuite}; + +use crate::{ + Participant, ThresholdKeys, + evrf::*, + tests::{THRESHOLD, PARTICIPANTS}, +}; + mod proof; +use proof::{Pallas, Vesta}; + +#[test] +fn evrf_dkg() { + let generators = EvrfDkg::::generators(THRESHOLD, PARTICIPANTS); + + let mut priv_keys = vec![]; + let mut pub_keys = vec![]; + for i in 0 .. PARTICIPANTS { + let priv_key = ::F::random(&mut OsRng); + pub_keys.push(::generator() * priv_key); + priv_keys.push((Participant::new(1 + i).unwrap(), Zeroizing::new(priv_key))); + } + + let mut participations = HashMap::new(); + // Shuffle the private keys so we iterate over a random subset of them + priv_keys.shuffle(&mut OsRng); + for (i, priv_key) in priv_keys.iter().take(usize::from(THRESHOLD)) { + participations.insert( + *i, + EvrfDkg::::participate( + &mut OsRng, + &generators, + [0; 32], + THRESHOLD, + &pub_keys, + priv_key, + ) + .unwrap(), + ); + } + + let dkg = EvrfDkg::::verify( + &mut OsRng, + &generators, + [0; 32], + THRESHOLD, + &pub_keys, + &participations, + ) + .unwrap(); + + for (i, priv_key) in priv_keys { + let keys = ThresholdKeys::from(dkg.keys(&priv_key).unwrap()); + assert_eq!(keys.params().i(), i); + assert_eq!(keys.params().t(), THRESHOLD); + assert_eq!(keys.params().n(), PARTICIPANTS); + } +} diff --git a/crypto/dkg/src/tests/evrf/proof.rs b/crypto/dkg/src/tests/evrf/proof.rs index 9e89f349..e8b586cf 100644 --- a/crypto/dkg/src/tests/evrf/proof.rs +++ b/crypto/dkg/src/tests/evrf/proof.rs @@ -21,7 +21,7 @@ use generalized_bulletproofs_ec_gadgets::DiscreteLogParameters; use crate::evrf::proof::*; #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -struct Pallas; +pub(crate) struct Pallas; impl Ciphersuite for Pallas { type F = Fq; type G = Ep; @@ -38,7 +38,7 @@ impl Ciphersuite for Pallas { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] -struct Vesta; +pub(crate) struct Vesta; impl Ciphersuite for Vesta { type F = Fp; type G = Eq; @@ -54,7 +54,7 @@ impl Ciphersuite for Vesta { } } -struct VestaParams; +pub struct VestaParams; impl DiscreteLogParameters for VestaParams { type ScalarBits = U<{ <::F as PrimeField>::NUM_BITS as usize }>; type XCoefficients = Quot, U2>;