From 00dc3087bd08d65db8a9d8f9c80d0da96a714576 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 25 Jul 2024 02:55:10 -0400 Subject: [PATCH] Update to the new eVRF proof --- Cargo.lock | 26 +- Cargo.toml | 1 - crypto/dkg/Cargo.toml | 8 +- crypto/dkg/src/evrf/mod.rs | 18 +- crypto/dkg/src/evrf/proof.rs | 701 ++++++++++++++++++----------- crypto/dkg/src/tests/evrf/mod.rs | 1 + crypto/dkg/src/tests/evrf/proof.rs | 30 +- crypto/dkg/src/tests/mod.rs | 3 + 8 files changed, 485 insertions(+), 303 deletions(-) create mode 100644 crypto/dkg/src/tests/evrf/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 437c32dd..e3298038 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2128,18 +2128,24 @@ dependencies = [ name = "dkg" version = "0.5.1" dependencies = [ + "blake2", "borsh", "chacha20", "ciphersuite", "dleq", "ec-divisors", - "evrf", "flexible-transcript", "generalized-bulletproofs", + "generalized-bulletproofs-circuit-abstraction", + "generalized-bulletproofs-ec-gadgets", + "generic-array 1.1.0", "multiexp", + "pasta_curves", + "rand_chacha", "rand_core", "schnorr-signatures", "std-shims", + "subtle", "thiserror", "zeroize", ] @@ -2447,24 +2453,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "evrf" -version = "0.1.0" -dependencies = [ - "blake2", - "ciphersuite", - "ec-divisors", - "generalized-bulletproofs", - "generalized-bulletproofs-circuit-abstraction", - "generalized-bulletproofs-ec-gadgets", - "generic-array 1.1.0", - "pasta_curves", - "rand_chacha", - "rand_core", - "subtle", - "zeroize", -] - [[package]] name = "exit-future" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index a98b1c8b..1509c8e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,6 @@ members = [ "crypto/evrf/circuit-abstraction", "crypto/evrf/divisors", "crypto/evrf/ec-gadgets", - "crypto/evrf", "crypto/dkg", "crypto/frost", diff --git a/crypto/dkg/Cargo.toml b/crypto/dkg/Cargo.toml index 6f1b86a2..e5afd311 100644 --- a/crypto/dkg/Cargo.toml +++ b/crypto/dkg/Cargo.toml @@ -43,14 +43,14 @@ blake2 = { version = "0.10", default-features = false, features = ["std"], optio rand_chacha = { version = "0.3", default-features = false, features = ["std"], optional = true } generalized-bulletproofs = { path = "../evrf/generalized-bulletproofs", default-features = false, optional = true } ec-divisors = { path = "../evrf/divisors", default-features = false, optional = true } -generalized-bulletproofs-circuit-abstraction = { path = "./circuit-abstraction", optional = true } -generalized-bulletproofs-ec-gadgets = { path = "./ec-gadgets", optional = true } +generalized-bulletproofs-circuit-abstraction = { path = "../evrf/circuit-abstraction", optional = true } +generalized-bulletproofs-ec-gadgets = { path = "../evrf/ec-gadgets", optional = true } [dev-dependencies] rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } ciphersuite = { path = "../ciphersuite", default-features = false, features = ["ristretto"] } -generalized-bulletproofs = { path = "./generalized-bulletproofs", features = ["tests"] } -ec-divisors = { path = "./divisors", features = ["pasta"] } +generalized-bulletproofs = { path = "../evrf/generalized-bulletproofs", features = ["tests"] } +ec-divisors = { path = "../evrf/divisors", features = ["pasta"] } pasta_curves = "0.5" [features] diff --git a/crypto/dkg/src/evrf/mod.rs b/crypto/dkg/src/evrf/mod.rs index 9ac02327..ce7b8e5d 100644 --- a/crypto/dkg/src/evrf/mod.rs +++ b/crypto/dkg/src/evrf/mod.rs @@ -10,11 +10,17 @@ `a * P_i, b * P_i`. The eVRF proceeds to commit to `A_i.x + B_i.x` in a Pedersen Commitment. Our eVRF uses - [Generalized Bulletproofs](https://repo.getmonero.org/monero-project/ccs-proposals/uploads/a9baa50c38c6312efc0fea5c6a188bb9/gbp.pdf). + [Generalized Bulletproofs]( + https://repo.getmonero.org/monero-project/ccs-proposals + /uploads/a9baa50c38c6312efc0fea5c6a188bb9/gbp.pdf + ). This allows us much larger witnesses without growing the reference string, and enables us to efficiently sample challenges off in-circuit variables (via placing the variables in a vector commitment, then challenging from a transcript of the commitments). We proceed to use - [elliptic curve divisors](https://repo.getmonero.org/-/project/54/uploads/eb1bf5b4d4855a3480c38abf895bd8e8/Veridise_Divisor_Proofs.pdf) + [elliptic curve divisors]( + https://repo.getmonero.org/-/project/54/ + uploads/eb1bf5b4d4855a3480c38abf895bd8e8/Veridise_Divisor_Proofs.pdf + ) (which require the ability to sample a challenge off in-circuit variables) to prove discrete logarithms efficiently. @@ -34,8 +40,8 @@ We have the sender sample two scalars per recipient, denoted `x_i, y_i` (where `i` is the recipient index). They perform the eVRF to prove a Pedersen Commitment commits to - `z_i = (x_i * P_i).x + (y_i * P_i).x`. They then publish the encrypted share `s_i + z_i` and - `X_i = x_i * G, Y_i = y_i * G`. + `z_i = (x_i * P_i).x + (y_i * P_i).x` and `x_i, y_i` are the discrete logarithms of `X_i, Y_i` + over `G`. They then publish the encrypted share `s_i + z_i` and `X_i, Y_i`. The recipient is able to decrypt the share via calculating `s_i - ((p_i * X_i).x + (p_i * Y_i).x)`. @@ -59,6 +65,9 @@ robust to threshold `t`. */ +pub(crate) mod proof; + +/* use core::ops::Deref; use std::{ io::{self, Read, Write}, @@ -443,3 +452,4 @@ where }) } } +*/ diff --git a/crypto/dkg/src/evrf/proof.rs b/crypto/dkg/src/evrf/proof.rs index 6ad0e3a0..7590fe86 100644 --- a/crypto/dkg/src/evrf/proof.rs +++ b/crypto/dkg/src/evrf/proof.rs @@ -1,3 +1,5 @@ +use core::{marker::PhantomData, fmt}; + use subtle::*; use zeroize::{Zeroize, Zeroizing}; @@ -25,17 +27,7 @@ use generalized_bulletproofs_circuit_abstraction::*; use ec_divisors::{DivisorCurve, new_divisor}; use generalized_bulletproofs_ec_gadgets::*; -#[cfg(test)] -mod tests; - -/* - The following circuit has two roles. - - 1) Generating every coefficient used in the DKG, per the eVRF paper, using the fixed eVRF key. - -*/ - -/// A curve to perform the eVRF with. +/// A pair of curves to perform the eVRF with. pub trait EvrfCurve: Ciphersuite { type EmbeddedCurve: Ciphersuite; type EmbeddedCurveParameters: DiscreteLogParameters; @@ -43,92 +35,125 @@ pub trait EvrfCurve: Ciphersuite { /// The result of proving for an eVRF. pub(crate) struct EvrfProveResult { - pub(crate) encrypted_scalars: Vec, + /// The coefficients for use in the DKG. + pub(crate) coefficients: Vec>, + /// The ECDHs to encrypt secret shares with. + pub(crate) ecdhs: Vec>, + /// The proof itself. pub(crate) proof: Vec, } +/// The result of verifying an eVRF. +pub(crate) struct EvrfVerifyResult { + /// The commitments to the coefficients for use in the DKG. + pub(crate) coefficients: Vec, + /// The commitments to the ECDHs used to encrypt secret shares with. + pub(crate) ecdhs: Vec, +} + +impl fmt::Debug for EvrfVerifyResult { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("EvrfVerifyResult").finish_non_exhaustive() + } +} + /// A struct to prove/verify eVRFs with. -pub(crate) struct Evrf; -impl Evrf { - fn transcript_to_points(seed: [u8; 32], quantity: usize) -> Vec { - // We need to do two Diffie-Hellman's per point in order to achieve an unbiased result - let quantity = 2 * quantity; +pub(crate) struct Evrf(PhantomData); +impl Evrf +where + <::EmbeddedCurve as Ciphersuite>::G: + DivisorCurve::F>, +{ + // Sample uniform points (via rejection-sampling) on the embedded elliptic curve + fn transcript_to_points( + seed: [u8; 32], + coefficients: usize, + ) -> Vec<::G> { + // We need to do two Diffie-Hellman's per coefficient in order to achieve an unbiased result + let quantity = 2 * coefficients; let mut rng = ChaCha20Rng::from_seed(seed); let mut res = Vec::with_capacity(quantity); while res.len() < quantity { - let mut repr = ::Repr::default(); + let mut repr = <::G as GroupEncoding>::Repr::default(); rng.fill_bytes(repr.as_mut()); - if let Ok(point) = C::read_G(&mut repr.as_ref()) { + if let Ok(point) = C::EmbeddedCurve::read_G(&mut repr.as_ref()) { res.push(point); } } res } - fn point_with_dlogs( + /// Read a Variable from a theoretical vector commitment tape + fn read_one_from_tape(generators_to_use: usize, start: &mut usize) -> Variable { + // Each commitment has twice as many variables as generators in use + let commitment = *start / (2 * generators_to_use); + // The index will be less than the amount of generators in use, as half are left and half are + // right + let index = *start % generators_to_use; + let res = if (*start / generators_to_use) % 2 == 0 { + Variable::CG { commitment, index } + } else { + Variable::CH { commitment, index } + }; + *start += 1; + res + } + + /// Read a set of variables from a theoretical vector commitment tape + fn read_from_tape( + generators_to_use: usize, + start: &mut usize, + ) -> GenericArray { + let mut buf = Vec::with_capacity(N::USIZE); + for _ in 0 .. N::USIZE { + buf.push(Self::read_one_from_tape(generators_to_use, start)); + } + GenericArray::from_slice(&buf).clone() + } + + /// Read `PointWithDlog`s, which share a discrete logarithm, from the theoretical vector + /// commitment tape. + fn point_with_dlogs( + start: &mut usize, quantity: usize, generators_to_use: usize, - ) -> Vec> { - let quantity = 2 * quantity; - - fn read_one_from_tape(generators_to_use: usize, start: &mut usize) -> Variable { - let commitment = *start / (2 * generators_to_use); - let index = *start % generators_to_use; - let res = if (*start / generators_to_use) % 2 == 0 { - Variable::CG { commitment, index } - } else { - Variable::CH { commitment, index } - }; - *start += 1; - res - } - fn read_from_tape( - generators_to_use: usize, - start: &mut usize, - ) -> GenericArray { - let mut buf = Vec::with_capacity(N::USIZE); - for _ in 0 .. N::USIZE { - buf.push(read_one_from_tape(generators_to_use, start)); - } - GenericArray::from_slice(&buf).clone() - } - - // We define a serialized tape of the discrete logarithm, then for each divisor/point: + ) -> Vec> { + // We define a serialized tape of the discrete logarithm, then for each divisor/point, we push: // zero, x**i, y x**i, y, x_coord, y_coord // We then chunk that into vector commitments // Here, we take the assumed layout and generate the expected `Variable`s for this layout - let mut start = 0; - let dlog = read_from_tape(generators_to_use, &mut start); + let dlog = Self::read_from_tape(generators_to_use, start); - let mut res = Vec::with_capacity(quantity + 1); + let mut res = Vec::with_capacity(quantity); let mut read_point_with_dlog = || { - let zero = read_one_from_tape(generators_to_use, &mut start); - let x_from_power_of_2 = read_from_tape(generators_to_use, &mut start); - let yx = read_from_tape(generators_to_use, &mut start); - let y = read_one_from_tape(generators_to_use, &mut start); + let zero = Self::read_one_from_tape(generators_to_use, start); + let x_from_power_of_2 = Self::read_from_tape(generators_to_use, start); + let yx = Self::read_from_tape(generators_to_use, start); + let y = Self::read_one_from_tape(generators_to_use, start); let divisor = Divisor { zero, x_from_power_of_2, yx, y }; let point = ( - read_one_from_tape(generators_to_use, &mut start), - read_one_from_tape(generators_to_use, &mut start), + Self::read_one_from_tape(generators_to_use, start), + Self::read_one_from_tape(generators_to_use, start), ); res.push(PointWithDlog { dlog: dlog.clone(), divisor, point }); }; for _ in 0 .. quantity { - // One for each DH proven read_point_with_dlog(); } - // And one more for the proof this is the discrete log of the public key - read_point_with_dlog(); res } - fn muls_and_generators_to_use(quantity: usize) -> (usize, usize) { - let expected_muls = 7 * (1 + (2 * quantity)); + fn muls_and_generators_to_use(coefficients: usize, ecdhs: usize) -> (usize, usize) { + 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 + let expected_muls = MULS_PER_DH * (1 + (2 * coefficients) + (2 * 2 * ecdhs)); let generators_to_use = { let mut padded_pow_of_2 = 1; while padded_pow_of_2 < expected_muls { @@ -141,26 +166,84 @@ impl Evrf { (expected_muls, generators_to_use) } - fn circuit( + fn circuit( curve_spec: &CurveSpec, evrf_public_key: (C::F, C::F), - quantity: usize, + coefficients: usize, + ecdh_commitments: &[[(C::F, C::F); 2]], generator_tables: &[GeneratorTable], circuit: &mut Circuit, transcript: &mut impl Transcript, ) { - let (expected_muls, generators_to_use) = Self::muls_and_generators_to_use(quantity); + let (expected_muls, generators_to_use) = + Self::muls_and_generators_to_use(coefficients, ecdh_commitments.len()); let (challenge, challenged_generators) = circuit.discrete_log_challenge(transcript, curve_spec, generator_tables); + debug_assert_eq!(challenged_generators.len(), 1 + (2 * coefficients) + ecdh_commitments.len()); - let mut point_with_dlogs = - Self::point_with_dlogs::(quantity, generators_to_use).into_iter(); + // The generators tables/challenged generators are expected to have the following layouts + // G, coefficients * [A, B], ecdhs * [P] + #[allow(non_snake_case)] + let challenged_G = &challenged_generators[0]; - // Verify the DLog claims for the sampled points - for (i, pair) in challenged_generators.chunks(2).take(quantity).enumerate() { + // Execute the circuit for the coefficients + let mut tape_pos = 0; + { + let mut point_with_dlogs = + Self::point_with_dlogs(&mut tape_pos, 1 + (2 * coefficients), generators_to_use) + .into_iter(); + + // Verify the discrete logarithm is in the fact the discrete logarithm of the eVRF public key + let point = circuit.discrete_log( + curve_spec, + point_with_dlogs.next().unwrap(), + &challenge, + challenged_G, + ); + circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(evrf_public_key.0)); + circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(evrf_public_key.1)); + + // Verify the DLog claims against the sampled points + for (i, pair) in challenged_generators[1 ..].chunks(2).take(coefficients).enumerate() { + let mut lincomb = LinComb::empty(); + debug_assert_eq!(pair.len(), 2); + for challenged_generator in pair { + let point = circuit.discrete_log( + curve_spec, + point_with_dlogs.next().unwrap(), + &challenge, + challenged_generator, + ); + // For each point in this pair, add its x coordinate to a lincomb + lincomb = lincomb.term(C::F::ONE, point.x()); + } + // Constrain the sum of the two x coordinates to be equal to the value in the Pedersen + // commitment + circuit.equality(lincomb, &LinComb::from(Variable::V(i))); + } + debug_assert!(point_with_dlogs.next().is_none()); + } + + // Now execute the circuit for the ECDHs + let mut challenged_generators = challenged_generators.iter().skip(1 + (2 * coefficients)); + for (i, ecdh) in ecdh_commitments.iter().enumerate() { + let challenged_generator = challenged_generators.next().unwrap(); let mut lincomb = LinComb::empty(); - debug_assert_eq!(pair.len(), 2); - for challenged_generator in pair { + for ecdh in ecdh { + let mut point_with_dlogs = + Self::point_with_dlogs(&mut tape_pos, 2, generators_to_use).into_iter(); + + // One proof of the ECDH secret * G for the commitment published + let point = circuit.discrete_log( + curve_spec, + point_with_dlogs.next().unwrap(), + &challenge, + challenged_G, + ); + circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(ecdh.0)); + circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(ecdh.1)); + + // One proof of the ECDH secret * P for the ECDH let point = circuit.discrete_log( curve_spec, point_with_dlogs.next().unwrap(), @@ -170,77 +253,40 @@ impl Evrf { // For each point in this pair, add its x coordinate to a lincomb lincomb = lincomb.term(C::F::ONE, point.x()); } + // Constrain the sum of the two x coordinates to be equal to the value in the Pedersen // commitment - circuit.equality(lincomb, &LinComb::from(Variable::V(i))); + circuit.equality(lincomb, &LinComb::from(Variable::V(coefficients + i))); } - let point = circuit.discrete_log( - curve_spec, - point_with_dlogs.next().unwrap(), - &challenge, - challenged_generators.last().unwrap(), - ); - circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(evrf_public_key.0)); - circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(evrf_public_key.1)); - debug_assert_eq!(expected_muls, circuit.muls()); - debug_assert!(point_with_dlogs.next().is_none()); + debug_assert!(challenged_generators.next().is_none()); } - /// Prove a point on an elliptic curve had its discrete logarithm generated via an eVRF. - pub(crate) fn prove( - rng: &mut (impl RngCore + CryptoRng), - generators: &Generators, - evrf_private_key: Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, - invocation: [u8; 32], - quantity: usize, - ) -> Result, AcError> - where - <::EmbeddedCurve as Ciphersuite>::G: - DivisorCurve::F>, - { - let curve_spec = CurveSpec { - a: <::EmbeddedCurve as Ciphersuite>::G::a(), - b: <::EmbeddedCurve as Ciphersuite>::G::b(), - }; - - // Combine the invocation and the public key into a transcript - let transcript = Blake2s256::digest( - [ - invocation.as_slice(), - (<::EmbeddedCurve as Ciphersuite>::generator() * *evrf_private_key) - .to_bytes() - .as_ref(), - ] - .concat(), - ) - .into(); - - let points = Self::transcript_to_points::(transcript, quantity); - - let num_bits: u32 = <::EmbeddedCurve as Ciphersuite>::F::NUM_BITS; + /// Convert a scalar to a sequence of coefficients for the polynomial 2**i, where the sum of the + /// coefficients is F::NUM_BITS. + /// + /// We'll presumably use this scalar in a discrete log proof. That requires calculating a divisor + /// which is variable time to the sum of the coefficients in the polynomial. This causes all + /// scalars to have a constant sum of their coefficients (instead one variable to the bits set). + /// + /// We achieve this by finding the highest non-0 coefficient, decrementing it, and increasing the + /// immediately less significant coefficient by 2. This increases the sum of the coefficients by + /// 1 (-1+2=1). + // TODO: Support scalars which have a value < F::NUM_BITS + #[allow(clippy::cast_possible_truncation)] + fn scalar_to_bits(scalar: &::F) -> Vec { + let num_bits = <::EmbeddedCurve as Ciphersuite>::F::NUM_BITS; // Obtain the bits of the private key - let mut sum_of_coefficients: u32 = 0; - let mut dlog = vec![::F::ZERO; num_bits as usize]; - for (i, bit) in evrf_private_key.to_le_bits().into_iter().take(num_bits as usize).enumerate() { - let bit = Choice::from(u8::from(bit)); - dlog[i] = - <_>::conditional_select(&::F::ZERO, &::F::ONE, bit); - sum_of_coefficients += u32::conditional_select(&0, &1, bit); + let mut sum_of_coefficients: u64 = 0; + let mut dlog = vec![0; num_bits as usize]; + for (i, bit) in scalar.to_le_bits().into_iter().take(num_bits as usize).enumerate() { + let bit = u64::from(u8::from(bit)); + dlog[i] = bit; + sum_of_coefficients += bit; } - /* - Now that we have the discrete logarithm as the coefficients 0/1 for a polynomial of 2**i, we - want to malleate it such that the sum of its coefficients is NUM_BITS. The divisor - calculcation is a non-trivial amount of work and would be extremely vulnerable to timing - attacks without such efforts. - - We find the highest non-0 coefficient, decrement it, and increase the prior coefficient by 2. - This increase the sum of the coefficients by 1. - */ - let two = ::F::ONE.double(); for _ in 0 .. num_bits { // Find the highest coefficient currently non-zero let mut h = 1u32; @@ -250,140 +296,233 @@ impl Evrf { // TODO: Squash the following two loops by iterating from the top bit to the bottom bit - let mut prior_scalar = dlog[(h as usize) - 1]; - for (i, scalar) in dlog.iter().enumerate().skip(h as usize) { - let is_zero = ::F::ZERO.ct_eq(scalar); + let mut prior_coefficient = dlog[(h as usize) - 1]; + for (i, coefficient) in dlog.iter().enumerate().skip(h as usize) { + let is_zero = 0.ct_eq(coefficient); // Set `h_*` if this value is non-0 h = u32::conditional_select(&h, &(i as u32), !is_zero); - h_value = ::F::conditional_select(&h_value, scalar, !is_zero); - h_prior_value = - ::F::conditional_select(&h_prior_value, &prior_scalar, !is_zero); + h_value = <_>::conditional_select(&h_value, coefficient, !is_zero); + h_prior_value = <_>::conditional_select(&h_prior_value, &prior_coefficient, !is_zero); - // Update prior_scalar - prior_scalar = *scalar; + // Update prior_coefficient + prior_coefficient = *coefficient; } // We should not have selected a value equivalent to 0 // TODO: Ban evrf keys < NUM_BITS and accordingly unable to be so coerced - // TODO: Preprocess this decomposition of the eVRF key? - assert!(!bool::from(h_value.ct_eq(&::F::ZERO))); + assert!(!bool::from(h_value.ct_eq(&0))); // Update h_value, h_prior_value as necessary - h_value -= ::F::ONE; - h_prior_value += two; + h_value -= 1; + h_prior_value += 2; // Now, set these values if we should - let should_set = !sum_of_coefficients.ct_eq(&num_bits); - sum_of_coefficients += u32::conditional_select(&0, &1, should_set); - for (i, scalar) in dlog.iter_mut().enumerate() { + let should_set = !sum_of_coefficients.ct_eq(&u64::from(num_bits)); + sum_of_coefficients += u64::conditional_select(&0, &1, should_set); + for (i, coefficient) in dlog.iter_mut().enumerate() { let this_is_prior = (i as u32).ct_eq(&(h - 1)); let this_is_high = (i as u32).ct_eq(&h); - *scalar = <_>::conditional_select(scalar, &h_prior_value, should_set & this_is_prior); - *scalar = <_>::conditional_select(scalar, &h_value, should_set & this_is_high); + *coefficient = + <_>::conditional_select(coefficient, &h_prior_value, should_set & this_is_prior); + *coefficient = <_>::conditional_select(coefficient, &h_value, should_set & this_is_high); } } - debug_assert!(bool::from( - dlog - .iter() - .sum::<::F>() - .ct_eq(&::F::from(u64::from(num_bits))) - )); + debug_assert!(bool::from(dlog.iter().sum::().ct_eq(&u64::from(num_bits)))); + + dlog + } + + fn transcript( + invocation: [u8; 32], + evrf_public_key: ::G, + ecdh_public_keys: &[::G], + ) -> [u8; 32] { + let mut transcript = Blake2s256::new(); + transcript.update(invocation); + transcript.update(evrf_public_key.to_bytes().as_ref()); + for ecdh in ecdh_public_keys { + transcript.update(ecdh.to_bytes().as_ref()); + } + transcript.finalize().into() + } + + /// Prove a point on an elliptic curve had its discrete logarithm generated via an eVRF. + pub(crate) fn prove( + rng: &mut (impl RngCore + CryptoRng), + generators: &Generators, + evrf_private_key: Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, + invocation: [u8; 32], + coefficients: usize, + ecdh_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], + ) -> Result, AcError> { + let curve_spec = CurveSpec { + a: <::EmbeddedCurve as Ciphersuite>::G::a(), + b: <::EmbeddedCurve as Ciphersuite>::G::b(), + }; + + // Combine the invocation and the public key into a transcript + let transcript = Self::transcript( + invocation, + <::EmbeddedCurve as Ciphersuite>::generator() * *evrf_private_key, + ecdh_public_keys, + ); // A tape of the discrete logarithm, then [zero, x**i, y x**i, y, x_coord, y_coord] let mut vector_commitment_tape = vec![]; - // Start by pushing the discrete logarithm onto the tape - for coefficient in &dlog { - vector_commitment_tape.push(*coefficient); - } - - let mut generator_tables = Vec::with_capacity(1 + (2 * quantity)); + let mut generator_tables = + Vec::with_capacity(1 + (2 * coefficients) + ecdh_public_keys.len()); // A function to calculate a divisor and push it onto the tape // This defines a vec, divisor_points, outside of the fn to reuse its allocation - let mut divisor_points = Vec::with_capacity((num_bits as usize) + 1); - let mut divisor = |mut generator: <::EmbeddedCurve as Ciphersuite>::G| { - { - let (x, y) = ::G::to_xy(generator).unwrap(); - generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); - } - - let dh = generator * *evrf_private_key; - { - for coefficient in &dlog { - let mut coefficient = *coefficient; - while coefficient != ::F::ZERO { - coefficient -= ::F::ONE; - divisor_points.push(generator); - } - generator = generator.double(); + let mut divisor_points = + Vec::with_capacity((::F::NUM_BITS as usize) + 1); + let mut divisor = + |vector_commitment_tape: &mut Vec<_>, + dlog: &[u64], + push_generator: bool, + generator: <::EmbeddedCurve as Ciphersuite>::G, + dh: <::EmbeddedCurve as Ciphersuite>::G| { + if push_generator { + let (x, y) = ::G::to_xy(generator).unwrap(); + generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); } - } - divisor_points.push(-dh); - let mut divisor = new_divisor(&divisor_points).unwrap().normalize_x_coefficient(); - divisor_points.zeroize(); - vector_commitment_tape.push(divisor.zero_coefficient); + { + let mut generator = generator; + for coefficient in dlog { + let mut coefficient = *coefficient; + while coefficient != 0 { + coefficient -= 1; + divisor_points.push(generator); + } + generator = generator.double(); + } + debug_assert_eq!(dlog.iter().sum::(), u64::from(::F::NUM_BITS)); + } + divisor_points.push(-dh); + let mut divisor = new_divisor(&divisor_points).unwrap().normalize_x_coefficient(); + divisor_points.zeroize(); - for coefficient in divisor.x_coefficients.iter().skip(1) { - vector_commitment_tape.push(*coefficient); - } - for _ in divisor.x_coefficients.len() .. - ::XCoefficientsMinusOne::USIZE - { - vector_commitment_tape.push(::F::ZERO); + vector_commitment_tape.push(divisor.zero_coefficient); + + for coefficient in divisor.x_coefficients.iter().skip(1) { + vector_commitment_tape.push(*coefficient); + } + for _ in divisor.x_coefficients.len() .. + ::XCoefficientsMinusOne::USIZE + { + vector_commitment_tape.push(::F::ZERO); + } + + for coefficient in divisor.yx_coefficients.first().unwrap_or(&vec![]) { + vector_commitment_tape.push(*coefficient); + } + for _ in divisor.yx_coefficients.first().unwrap_or(&vec![]).len() .. + ::YxCoefficients::USIZE + { + vector_commitment_tape.push(::F::ZERO); + } + + vector_commitment_tape + .push(divisor.y_coefficients.first().copied().unwrap_or(::F::ZERO)); + + divisor.zeroize(); + drop(divisor); + + let (x, y) = ::G::to_xy(dh).unwrap(); + vector_commitment_tape.push(x); + vector_commitment_tape.push(y); + + (x, y) + }; + + // Start with the coefficients + let evrf_public_key; + let mut actual_coefficients = Vec::with_capacity(coefficients); + { + let mut dlog = Self::scalar_to_bits(&evrf_private_key); + let points = Self::transcript_to_points(transcript, coefficients); + + // Start by pushing the discrete logarithm onto the tape + for coefficient in &dlog { + vector_commitment_tape.push(<_>::from(*coefficient)); } - for coefficient in divisor.yx_coefficients.first().unwrap_or(&vec![]) { - vector_commitment_tape.push(*coefficient); - } - for _ in divisor.yx_coefficients.first().unwrap_or(&vec![]).len() .. - ::YxCoefficients::USIZE - { - vector_commitment_tape.push(::F::ZERO); + // Push a divisor for proving that we're using the correct scalar + evrf_public_key = divisor( + &mut vector_commitment_tape, + &dlog, + true, + <::EmbeddedCurve as Ciphersuite>::generator(), + <::EmbeddedCurve as Ciphersuite>::generator() * *evrf_private_key, + ); + + // Push a divisor for each point we use in the eVRF + for pair in points.chunks(2) { + let mut res = Zeroizing::new(C::F::ZERO); + for point in pair { + let (dh_x, _) = divisor(&mut vector_commitment_tape, &dlog, true, *point, *point * *evrf_private_key); + *res += dh_x; + } + actual_coefficients.push(res); } + debug_assert_eq!(actual_coefficients.len(), coefficients); - vector_commitment_tape - .push(divisor.y_coefficients.first().cloned().unwrap_or(::F::ZERO)); - - divisor.zeroize(); - drop(divisor); - - let (x, y) = ::G::to_xy(dh).unwrap(); - vector_commitment_tape.push(x); - vector_commitment_tape.push(y); - - (x, y) - }; - - // Push a divisor for each point we use in the eVRF - let mut scalars = Vec::with_capacity(quantity); - for pair in points.chunks(2) { - let mut res = Zeroizing::new(C::F::ZERO); - for point in pair { - let (dh_x, _) = divisor(*point); - *res += dh_x; - } - scalars.push(res); + dlog.zeroize(); } - debug_assert_eq!(scalars.len(), quantity); - // Also push a divisor for proving that we're using the correct scalar - let evrf_public_key = divisor(<::EmbeddedCurve as Ciphersuite>::generator()); + // Now do the ECDHs + let mut ecdhs = Vec::with_capacity(ecdh_public_keys.len()); + let mut ecdh_commitments = Vec::with_capacity(2 * ecdh_public_keys.len()); + let mut ecdh_commitments_xy = Vec::with_capacity(ecdh_public_keys.len()); + for ecdh_public_key in ecdh_public_keys { + ecdh_commitments_xy.push([(C::F::ZERO, C::F::ZERO); 2]); - dlog.zeroize(); - drop(dlog); + let mut res = Zeroizing::new(C::F::ZERO); + for j in 0 .. 2 { + let mut ecdh_private_key = ::F::random(&mut *rng); + let mut dlog = Self::scalar_to_bits(&ecdh_private_key); + let ecdh_commitment = ::generator() * ecdh_private_key; + ecdh_commitments.push(ecdh_commitment); + ecdh_commitments_xy.last_mut().unwrap()[j] = + <::G as DivisorCurve>::to_xy(ecdh_commitment).unwrap(); + + // Start by pushing the discrete logarithm onto the tape + for coefficient in &dlog { + vector_commitment_tape.push(<_>::from(*coefficient)); + } + + // Push a divisor for proving that we're using the correct scalar for the commitment + divisor( + &mut vector_commitment_tape, + &dlog, + false, + <::EmbeddedCurve as Ciphersuite>::generator(), + <::EmbeddedCurve as Ciphersuite>::generator() * ecdh_private_key, + ); + // Push a divisor for the key we're performing the ECDH with + let (dh_x, _) = divisor(&mut vector_commitment_tape, &dlog, j == 0, *ecdh_public_key, *ecdh_public_key * ecdh_private_key); + *res += dh_x; + + ecdh_private_key.zeroize(); + dlog.zeroize(); + } + ecdhs.push(res); + } + debug_assert_eq!(ecdhs.len(), ecdh_public_keys.len()); // Now that we have the vector commitment tape, chunk it - let (_, generators_to_use) = Self::muls_and_generators_to_use(quantity); + let (_, generators_to_use) = + Self::muls_and_generators_to_use(coefficients, ecdh_public_keys.len()); let mut vector_commitments = - Vec::with_capacity(vector_commitment_tape.len().div_ceil(generators_to_use)); - for chunk in vector_commitment_tape.chunks(generators_to_use * 2) { - let g_values = chunk[.. generators_to_use].to_vec().into(); - let h_values = chunk[generators_to_use ..].to_vec().into(); + Vec::with_capacity(vector_commitment_tape.len().div_ceil(2 * generators_to_use)); + for chunk in vector_commitment_tape.chunks(2 * generators_to_use) { + let g_values = chunk[.. generators_to_use.min(chunk.len())].to_vec().into(); + let h_values = chunk[generators_to_use.min(chunk.len()) ..].to_vec().into(); vector_commitments.push(PedersenVectorCommitment { g_values, h_values, @@ -394,9 +533,12 @@ impl Evrf { vector_commitment_tape.zeroize(); drop(vector_commitment_tape); - let mut commitments = Vec::with_capacity(quantity); - for scalar in &scalars { - commitments.push(PedersenCommitment { value: **scalar, mask: C::F::random(&mut *rng) }); + let mut commitments = Vec::with_capacity(coefficients + ecdh_public_keys.len()); + for coefficient in &actual_coefficients { + commitments.push(PedersenCommitment { value: **coefficient, mask: C::F::random(&mut *rng) }); + } + for ecdh in &ecdhs { + commitments.push(PedersenCommitment { value: **ecdh, mask: C::F::random(&mut *rng) }); } let mut transcript = ProverTranscript::new(transcript); @@ -414,12 +556,16 @@ impl Evrf { .map(|commitment| commitment.commit(generators.g(), generators.h())) .collect(), ); + for ecdh_commitment in ecdh_commitments { + transcript.push_point(ecdh_commitment); + } let mut circuit = Circuit::prove(vector_commitments, commitments.clone()); - Self::circuit::( + Self::circuit( &curve_spec, evrf_public_key, - quantity, + coefficients, + &ecdh_commitments_xy, &generator_tables, &mut circuit, &mut transcript, @@ -437,14 +583,14 @@ impl Evrf { statement.prove(&mut *rng, &mut transcript, witness).unwrap(); // Push the reveal onto the transcript - for scalar in &scalars { - transcript.push_point(generators.g() * **scalar); + for commitment in &commitments { + transcript.push_point(generators.g() * commitment.value); } // Define a weight to aggregate the commitments with - let mut agg_weights = Vec::with_capacity(quantity); + let mut agg_weights = Vec::with_capacity(commitments.len()); agg_weights.push(C::F::ONE); - while agg_weights.len() < quantity { + while agg_weights.len() < commitments.len() { agg_weights.push(transcript.challenge::()); } let mut x = commitments @@ -461,69 +607,87 @@ impl Evrf { r.zeroize(); x.zeroize(); - Ok(EvrfProveResult { scalars, proof: transcript.complete() }) + Ok(EvrfProveResult { coefficients: actual_coefficients, ecdhs, proof: transcript.complete() }) } // TODO: Dedicated error /// Verify an eVRF proof, returning the commitments output. - pub(crate) fn verify( + pub(crate) fn verify( rng: &mut (impl RngCore + CryptoRng), generators: &Generators, verifier: &mut BatchVerifier, evrf_public_key: <::EmbeddedCurve as Ciphersuite>::G, invocation: [u8; 32], - quantity: usize, + coefficients: usize, + ecdh_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], proof: &[u8], - ) -> Result, ()> - where - <::EmbeddedCurve as Ciphersuite>::G: - DivisorCurve::F>, - { + ) -> Result, ()> { let curve_spec = CurveSpec { a: <::EmbeddedCurve as Ciphersuite>::G::a(), b: <::EmbeddedCurve as Ciphersuite>::G::b(), }; - let transcript = - Blake2s256::digest([invocation.as_slice(), evrf_public_key.to_bytes().as_ref()].concat()) - .into(); + let transcript = Self::transcript(invocation, evrf_public_key, ecdh_public_keys); - let points = Self::transcript_to_points::(transcript, quantity); - let mut generator_tables = Vec::with_capacity(1 + (2 * quantity)); - - for generator in points { - let (x, y) = ::G::to_xy(generator).unwrap(); - generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); - } + let mut generator_tables = + Vec::with_capacity(1 + (2 * coefficients) + ecdh_public_keys.len()); { let (x, y) = ::G::to_xy(::generator()) .unwrap(); generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); } + let points = Self::transcript_to_points(transcript, coefficients); + for generator in points { + let (x, y) = ::G::to_xy(generator).unwrap(); + generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); + } + for generator in ecdh_public_keys { + let (x, y) = ::G::to_xy(*generator).unwrap(); + generator_tables.push(GeneratorTable::new(&curve_spec, x, y)); + } - let (_, generators_to_use) = Self::muls_and_generators_to_use(quantity); + let (_, generators_to_use) = + Self::muls_and_generators_to_use(coefficients, ecdh_public_keys.len()); let mut transcript = VerifierTranscript::new(transcript, proof); + let dlog_len = ::ScalarBits::USIZE; let divisor_len = 1 + ::XCoefficientsMinusOne::USIZE + ::YxCoefficients::USIZE + 1; let dlog_proof_len = divisor_len + 2; - let vcs = (::ScalarBits::USIZE + - ((1 + (2 * quantity)) * dlog_proof_len)) - .div_ceil(2 * generators_to_use); - let all_commitments = transcript.read_commitments(vcs, quantity).map_err(|_| ())?; + let coeffs_vc_variables = dlog_len + ((1 + (2 * coefficients)) * dlog_proof_len); + let ecdhs_vc_variables = ((2 * ecdh_public_keys.len()) * dlog_len) + ((2 * 2 * ecdh_public_keys.len()) * dlog_proof_len); + let vcs = (coeffs_vc_variables + ecdhs_vc_variables).div_ceil(2 * generators_to_use); + + let all_commitments = + transcript.read_commitments(vcs, coefficients + ecdh_public_keys.len()).map_err(|_| ())?; let commitments = all_commitments.V().to_vec(); + let mut ecdh_commitments_xy = Vec::with_capacity(ecdh_public_keys.len()); + for _ in 0 .. ecdh_public_keys.len() { + ecdh_commitments_xy.push([ + <::G as DivisorCurve>::to_xy( + transcript.read_point::().map_err(|_| ())?, + ) + .ok_or(())?, + <::G as DivisorCurve>::to_xy( + transcript.read_point::().map_err(|_| ())?, + ) + .ok_or(())?, + ]); + } + let mut circuit = Circuit::verify(); - Self::circuit::( + Self::circuit( &curve_spec, // TODO: Use a better error here ::G::to_xy(evrf_public_key).ok_or(())?, - quantity, + coefficients, + &ecdh_commitments_xy, &generator_tables, &mut circuit, &mut transcript, @@ -537,20 +701,21 @@ impl Evrf { statement.verify(rng, verifier, &mut transcript).map_err(|_| ())?; - // Read the unblinded public keys - let mut res = Vec::with_capacity(quantity); - for _ in 0 .. quantity { - res.push(transcript.read_point::().map_err(|_| ())?); + // Read the openings for the commitments + let mut openings = Vec::with_capacity(commitments.len()); + for _ in 0 .. commitments.len() { + openings.push(transcript.read_point::().map_err(|_| ())?); } - let mut agg_weights = Vec::with_capacity(quantity); + // Verify the openings of the commitments + let mut agg_weights = Vec::with_capacity(commitments.len()); agg_weights.push(C::F::ONE); - while agg_weights.len() < quantity { + while agg_weights.len() < commitments.len() { agg_weights.push(transcript.challenge::()); } let sum_points = - res.iter().zip(&agg_weights).map(|(point, weight)| *point * *weight).sum::(); + openings.iter().zip(&agg_weights).map(|(point, weight)| *point * *weight).sum::(); let sum_commitments = commitments.into_iter().zip(agg_weights).map(|(point, weight)| point * weight).sum::(); #[allow(non_snake_case)] @@ -570,6 +735,8 @@ impl Evrf { Err(())? }; - Ok(res) + let ecdhs = openings[coefficients ..].to_vec(); + let coefficients = openings[.. coefficients].to_vec(); + Ok(EvrfVerifyResult { coefficients, ecdhs }) } } diff --git a/crypto/dkg/src/tests/evrf/mod.rs b/crypto/dkg/src/tests/evrf/mod.rs new file mode 100644 index 00000000..5e3cb98b --- /dev/null +++ b/crypto/dkg/src/tests/evrf/mod.rs @@ -0,0 +1 @@ +mod proof; diff --git a/crypto/dkg/src/tests/evrf/proof.rs b/crypto/dkg/src/tests/evrf/proof.rs index a4c1c003..c75ff2c4 100644 --- a/crypto/dkg/src/tests/evrf/proof.rs +++ b/crypto/dkg/src/tests/evrf/proof.rs @@ -7,14 +7,18 @@ use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2}; use blake2::{Digest, Blake2b512}; use ciphersuite::{ - group::ff::{FromUniformBytes, PrimeField}, + group::{ + ff::{FromUniformBytes, Field, PrimeField}, + Group, + }, Ciphersuite, }; use pasta_curves::{Ep, Eq, Fp, Fq}; use generalized_bulletproofs::tests::generators; +use generalized_bulletproofs_ec_gadgets::DiscreteLogParameters; -use crate::*; +use crate::evrf::proof::*; #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] struct Pallas; @@ -64,26 +68,36 @@ impl EvrfCurve for Pallas { } #[test] -fn pasta_test() { +fn evrf_proof_pasta_test() { let generators = generators(1024); let vesta_private_key = Zeroizing::new(::F::random(&mut OsRng)); + let ecdh_public_keys = + [::G::random(&mut OsRng), ::G::random(&mut OsRng)]; let time = Instant::now(); - let res = - Evrf::prove::(&mut OsRng, &generators, vesta_private_key.clone(), [0; 32], 1).unwrap(); - println!("Proving time: {:?}", Instant::now() - time); + let res = Evrf::::prove( + &mut OsRng, + &generators, + vesta_private_key.clone(), + [0; 32], + 1, + &ecdh_public_keys, + ) + .unwrap(); + println!("Proving time: {:?}", time.elapsed()); let time = Instant::now(); let mut verifier = generators.batch_verifier(); - dbg!(Evrf::verify::( + dbg!(Evrf::::verify( &mut OsRng, &generators, &mut verifier, Vesta::generator() * *vesta_private_key, [0; 32], 1, + &ecdh_public_keys, &res.proof, ) .unwrap()); assert!(generators.verify(verifier)); - println!("Verifying time: {:?}", Instant::now() - time); + println!("Verifying time: {:?}", time.elapsed()); } diff --git a/crypto/dkg/src/tests/mod.rs b/crypto/dkg/src/tests/mod.rs index f21d7254..99d68b3d 100644 --- a/crypto/dkg/src/tests/mod.rs +++ b/crypto/dkg/src/tests/mod.rs @@ -19,6 +19,9 @@ use pedpop::pedpop_gen; mod promote; use promote::test_generator_promotion; +#[cfg(all(test, feature = "evrf"))] +mod evrf; + /// Constant amount of participants to use when testing. pub const PARTICIPANTS: u16 = 5; /// Constant threshold of participants to use when testing.