Update to the new eVRF proof

This commit is contained in:
Luke Parker
2024-07-25 02:55:10 -04:00
parent eca82f3f7b
commit 00dc3087bd
8 changed files with 485 additions and 303 deletions

26
Cargo.lock generated
View File

@@ -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"

View File

@@ -39,7 +39,6 @@ members = [
"crypto/evrf/circuit-abstraction",
"crypto/evrf/divisors",
"crypto/evrf/ec-gadgets",
"crypto/evrf",
"crypto/dkg",
"crypto/frost",

View File

@@ -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]

View File

@@ -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
})
}
}
*/

View File

@@ -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<C: Ciphersuite> {
pub(crate) encrypted_scalars: Vec<C::F>,
/// The coefficients for use in the DKG.
pub(crate) coefficients: Vec<Zeroizing<C::F>>,
/// The ECDHs to encrypt secret shares with.
pub(crate) ecdhs: Vec<Zeroizing<C::F>>,
/// The proof itself.
pub(crate) proof: Vec<u8>,
}
/// The result of verifying an eVRF.
pub(crate) struct EvrfVerifyResult<C: Ciphersuite> {
/// The commitments to the coefficients for use in the DKG.
pub(crate) coefficients: Vec<C::G>,
/// The commitments to the ECDHs used to encrypt secret shares with.
pub(crate) ecdhs: Vec<C::G>,
}
impl<C: Ciphersuite> fmt::Debug for EvrfVerifyResult<C> {
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<C: Ciphersuite>(seed: [u8; 32], quantity: usize) -> Vec<C::G> {
// 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<C: EvrfCurve>(PhantomData<C>);
impl<C: EvrfCurve> Evrf<C>
where
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G:
DivisorCurve<FieldElement = <C as Ciphersuite>::F>,
{
// Sample uniform points (via rejection-sampling) on the embedded elliptic curve
fn transcript_to_points(
seed: [u8; 32],
coefficients: usize,
) -> Vec<<C::EmbeddedCurve as Ciphersuite>::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 = <C::G as GroupEncoding>::Repr::default();
let mut repr = <<C::EmbeddedCurve as Ciphersuite>::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<Parameters: DiscreteLogParameters>(
/// 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<N: ArrayLength>(
generators_to_use: usize,
start: &mut usize,
) -> GenericArray<Variable, N> {
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<PointWithDlog<Parameters>> {
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<N: ArrayLength>(
generators_to_use: usize,
start: &mut usize,
) -> GenericArray<Variable, N> {
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<PointWithDlog<C::EmbeddedCurveParameters>> {
// 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<C: EvrfCurve>(
fn circuit(
curve_spec: &CurveSpec<C::F>,
evrf_public_key: (C::F, C::F),
quantity: usize,
coefficients: usize,
ecdh_commitments: &[[(C::F, C::F); 2]],
generator_tables: &[GeneratorTable<C::F, C::EmbeddedCurveParameters>],
circuit: &mut Circuit<C>,
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::<C::EmbeddedCurveParameters>(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<C: EvrfCurve>(
rng: &mut (impl RngCore + CryptoRng),
generators: &Generators<C>,
evrf_private_key: Zeroizing<<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
invocation: [u8; 32],
quantity: usize,
) -> Result<EvrfProveResult<C>, AcError>
where
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G:
DivisorCurve<FieldElement = <C as Ciphersuite>::F>,
{
let curve_spec = CurveSpec {
a: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::b(),
};
// Combine the invocation and the public key into a transcript
let transcript = Blake2s256::digest(
[
invocation.as_slice(),
(<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator() * *evrf_private_key)
.to_bytes()
.as_ref(),
]
.concat(),
)
.into();
let points = Self::transcript_to_points::<C::EmbeddedCurve>(transcript, quantity);
let num_bits: u32 = <<C as EvrfCurve>::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: &<C::EmbeddedCurve as Ciphersuite>::F) -> Vec<u64> {
let num_bits = <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::NUM_BITS;
// Obtain the bits of the private key
let mut sum_of_coefficients: u32 = 0;
let mut dlog = vec![<C as Ciphersuite>::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(&<C as Ciphersuite>::F::ZERO, &<C as Ciphersuite>::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 = <C as Ciphersuite>::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 = <C as Ciphersuite>::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 = <C as Ciphersuite>::F::conditional_select(&h_value, scalar, !is_zero);
h_prior_value =
<C as Ciphersuite>::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(&<C as Ciphersuite>::F::ZERO)));
assert!(!bool::from(h_value.ct_eq(&0)));
// Update h_value, h_prior_value as necessary
h_value -= <C as Ciphersuite>::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::<<C as Ciphersuite>::F>()
.ct_eq(&<C as Ciphersuite>::F::from(u64::from(num_bits)))
));
debug_assert!(bool::from(dlog.iter().sum::<u64>().ct_eq(&u64::from(num_bits))));
dlog
}
fn transcript(
invocation: [u8; 32],
evrf_public_key: <C::EmbeddedCurve as Ciphersuite>::G,
ecdh_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::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<C>,
evrf_private_key: Zeroizing<<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
invocation: [u8; 32],
coefficients: usize,
ecdh_public_keys: &[<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G],
) -> Result<EvrfProveResult<C>, AcError> {
let curve_spec = CurveSpec {
a: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::b(),
};
// Combine the invocation and the public key into a transcript
let transcript = Self::transcript(
invocation,
<<C as EvrfCurve>::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: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G| {
{
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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 != <C as Ciphersuite>::F::ZERO {
coefficient -= <C as Ciphersuite>::F::ONE;
divisor_points.push(generator);
}
generator = generator.double();
let mut divisor_points =
Vec::with_capacity((<C::EmbeddedCurve as Ciphersuite>::F::NUM_BITS as usize) + 1);
let mut divisor =
|vector_commitment_tape: &mut Vec<_>,
dlog: &[u64],
push_generator: bool,
generator: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G,
dh: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G| {
if push_generator {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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>(), u64::from(<C::EmbeddedCurve as Ciphersuite>::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() ..
<C::EmbeddedCurveParameters as DiscreteLogParameters>::XCoefficientsMinusOne::USIZE
{
vector_commitment_tape.push(<C as Ciphersuite>::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() ..
<C::EmbeddedCurveParameters as DiscreteLogParameters>::XCoefficientsMinusOne::USIZE
{
vector_commitment_tape.push(<C as Ciphersuite>::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() ..
<C::EmbeddedCurveParameters as DiscreteLogParameters>::YxCoefficients::USIZE
{
vector_commitment_tape.push(<C as Ciphersuite>::F::ZERO);
}
vector_commitment_tape
.push(divisor.y_coefficients.first().copied().unwrap_or(<C as Ciphersuite>::F::ZERO));
divisor.zeroize();
drop(divisor);
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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() ..
<C::EmbeddedCurveParameters as DiscreteLogParameters>::YxCoefficients::USIZE
{
vector_commitment_tape.push(<C as Ciphersuite>::F::ZERO);
// Push a divisor for proving that we're using the correct scalar
evrf_public_key = divisor(
&mut vector_commitment_tape,
&dlog,
true,
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator(),
<<C as EvrfCurve>::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(<C as Ciphersuite>::F::ZERO));
divisor.zeroize();
drop(divisor);
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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(<<C as EvrfCurve>::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 = <C::EmbeddedCurve as Ciphersuite>::F::random(&mut *rng);
let mut dlog = Self::scalar_to_bits(&ecdh_private_key);
let ecdh_commitment = <C::EmbeddedCurve as Ciphersuite>::generator() * ecdh_private_key;
ecdh_commitments.push(ecdh_commitment);
ecdh_commitments_xy.last_mut().unwrap()[j] =
<<C::EmbeddedCurve as Ciphersuite>::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,
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator(),
<<C as EvrfCurve>::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::<C>(
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::<C::F>());
}
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<C: EvrfCurve>(
pub(crate) fn verify(
rng: &mut (impl RngCore + CryptoRng),
generators: &Generators<C>,
verifier: &mut BatchVerifier<C>,
evrf_public_key: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G,
invocation: [u8; 32],
quantity: usize,
coefficients: usize,
ecdh_public_keys: &[<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G],
proof: &[u8],
) -> Result<Vec<C::G>, ()>
where
<<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G:
DivisorCurve<FieldElement = <C as Ciphersuite>::F>,
{
) -> Result<EvrfVerifyResult<C>, ()> {
let curve_spec = CurveSpec {
a: <<C as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as EvrfCurve>::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::<C::EmbeddedCurve>(transcript, quantity);
let mut generator_tables = Vec::with_capacity(1 + (2 * quantity));
for generator in points {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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) =
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(<C::EmbeddedCurve as Ciphersuite>::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) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(generator).unwrap();
generator_tables.push(GeneratorTable::new(&curve_spec, x, y));
}
for generator in ecdh_public_keys {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::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 = <C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits::USIZE;
let divisor_len = 1 +
<C::EmbeddedCurveParameters as DiscreteLogParameters>::XCoefficientsMinusOne::USIZE +
<C::EmbeddedCurveParameters as DiscreteLogParameters>::YxCoefficients::USIZE +
1;
let dlog_proof_len = divisor_len + 2;
let vcs = (<C::EmbeddedCurveParameters as DiscreteLogParameters>::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([
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(
transcript.read_point::<C::EmbeddedCurve>().map_err(|_| ())?,
)
.ok_or(())?,
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(
transcript.read_point::<C::EmbeddedCurve>().map_err(|_| ())?,
)
.ok_or(())?,
]);
}
let mut circuit = Circuit::verify();
Self::circuit::<C>(
Self::circuit(
&curve_spec,
// TODO: Use a better error here
<C::EmbeddedCurve as Ciphersuite>::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::<C>().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::<C>().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::<C::F>());
}
let sum_points =
res.iter().zip(&agg_weights).map(|(point, weight)| *point * *weight).sum::<C::G>();
openings.iter().zip(&agg_weights).map(|(point, weight)| *point * *weight).sum::<C::G>();
let sum_commitments =
commitments.into_iter().zip(agg_weights).map(|(point, weight)| point * weight).sum::<C::G>();
#[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 })
}
}

View File

@@ -0,0 +1 @@
mod proof;

View File

@@ -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(<Vesta as Ciphersuite>::F::random(&mut OsRng));
let ecdh_public_keys =
[<Vesta as Ciphersuite>::G::random(&mut OsRng), <Vesta as Ciphersuite>::G::random(&mut OsRng)];
let time = Instant::now();
let res =
Evrf::prove::<Pallas>(&mut OsRng, &generators, vesta_private_key.clone(), [0; 32], 1).unwrap();
println!("Proving time: {:?}", Instant::now() - time);
let res = Evrf::<Pallas>::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::<Pallas>(
dbg!(Evrf::<Pallas>::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());
}

View File

@@ -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.