use core::{marker::PhantomData, ops::Deref, fmt}; use subtle::*; use zeroize::{Zeroize, Zeroizing}; use rand_core::{RngCore, CryptoRng, SeedableRng}; use rand_chacha::ChaCha20Rng; use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; use blake2::{Digest, Blake2s256}; use ciphersuite::{ group::{ ff::{Field, PrimeField, PrimeFieldBits}, Group, GroupEncoding, }, Ciphersuite, }; use generalized_bulletproofs::{ *, transcript::{Transcript as ProverTranscript, VerifierTranscript}, arithmetic_circuit_proof::*, }; use generalized_bulletproofs_circuit_abstraction::*; use ec_divisors::{DivisorCurve, new_divisor}; use generalized_bulletproofs_ec_gadgets::*; /// A pair of curves to perform the eVRF with. pub trait EvrfCurve: Ciphersuite { type EmbeddedCurve: Ciphersuite::F>>; type EmbeddedCurveParameters: DiscreteLogParameters; } #[cfg(feature = "evrf-secp256k1")] impl EvrfCurve for ciphersuite::Secp256k1 { type EmbeddedCurve = secq256k1::Secq256k1; type EmbeddedCurveParameters = secq256k1::Secq256k1; } #[cfg(feature = "evrf-ed25519")] impl EvrfCurve for ciphersuite::Ed25519 { type EmbeddedCurve = embedwards25519::Embedwards25519; type EmbeddedCurveParameters = embedwards25519::Embedwards25519; } #[cfg(feature = "evrf-ristretto")] impl EvrfCurve for ciphersuite::Ristretto { type EmbeddedCurve = embedwards25519::Embedwards25519; type EmbeddedCurveParameters = embedwards25519::Embedwards25519; } 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()) { if bool::from(!point.is_identity()) { return point; } } } } /// Generators for eVRF proof. #[derive(Clone, Debug)] pub struct EvrfGenerators(pub(crate) Generators); impl EvrfGenerators { /// Create a new set of generators. pub fn new(max_threshold: u16, max_participants: u16) -> EvrfGenerators { 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)); } Self(Generators::new(g, h, g_bold, h_bold).unwrap()) } } /// The result of proving for an eVRF. pub(crate) struct EvrfProveResult { /// The coefficients for use in the DKG. pub(crate) coefficients: Vec>, /// The masks to encrypt secret shares with. pub(crate) encryption_masks: 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 ephemeral public keys to perform ECDHs with pub(crate) ecdh_keys: Vec<[::G; 2]>, /// The commitments to the masks used to encrypt secret shares with. pub(crate) encryption_commitments: 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(PhantomData); impl Evrf { // 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); for _ in 0 .. quantity { res.push(sample_point::(&mut rng)); } res } /// 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> { // 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 dlog = Self::read_from_tape(generators_to_use, start); let mut res = Vec::with_capacity(quantity); let mut read_point_with_dlog = || { 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 = ( 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 { read_point_with_dlog(); } res } 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 ECDH 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 { padded_pow_of_2 <<= 1; } // This may as small as 16, which would create an excessive amount of vector commitments // We set a floor of 1024 rows for bandwidth reasons padded_pow_of_2.max(1024) }; (expected_muls, generators_to_use) } fn circuit( curve_spec: &CurveSpec, evrf_public_key: (C::F, C::F), 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(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()); // 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]; // 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(); 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(), &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(coefficients + i))); } debug_assert_eq!(expected_muls, circuit.muls()); debug_assert!(challenged_generators.next().is_none()); } /// Convert a scalar to a sequence of coefficients for the polynomial 2**i, where the sum of the /// coefficients is F::NUM_BITS. /// /// Despite the name, the returned coefficients are not guaranteed to be bits (0 or 1). /// /// This scalar will presumably be used in a discrete log proof. That requires calculating a /// divisor which is variable time to the amount of points interpolated. Since the amount of /// points interpolated is equal to the sum of the coefficients in the polynomial, we need all /// scalars to have a constant sum of their coefficients (instead of one variable to its bits). /// /// 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). fn scalar_to_bits(scalar: &::F) -> Vec { let num_bits = u64::from(<::EmbeddedCurve as Ciphersuite>::F::NUM_BITS); // Obtain the bits of the private key let num_bits_usize = usize::try_from(num_bits).unwrap(); let mut decomposition = vec![0; num_bits_usize]; for (i, bit) in scalar.to_le_bits().into_iter().take(num_bits_usize).enumerate() { let bit = u64::from(u8::from(bit)); decomposition[i] = bit; } // The following algorithm only works if the value of the scalar exceeds num_bits // If it isn't, we increase it by the modulus such that it does exceed num_bits { let mut less_than_num_bits = Choice::from(0); for i in 0 .. num_bits { less_than_num_bits |= scalar.ct_eq(&::F::from(i)); } let mut decomposition_of_modulus = vec![0; num_bits_usize]; // Decompose negative one for (i, bit) in (-::F::ONE) .to_le_bits() .into_iter() .take(num_bits_usize) .enumerate() { let bit = u64::from(u8::from(bit)); decomposition_of_modulus[i] = bit; } // Increment it by one decomposition_of_modulus[0] += 1; // Add the decomposition onto the decomposition of the modulus for i in 0 .. num_bits_usize { let new_decomposition = <_>::conditional_select( &decomposition[i], &(decomposition[i] + decomposition_of_modulus[i]), less_than_num_bits, ); decomposition[i] = new_decomposition; } } // Calculcate the sum of the coefficients let mut sum_of_coefficients: u64 = 0; for decomposition in &decomposition { sum_of_coefficients += *decomposition; } /* Now, because we added a log2(k)-bit number to a k-bit number, we may have our sum of coefficients be *too high*. We attempt to reduce the sum of the coefficients accordingly. This algorithm is guaranteed to complete as expected. Take the sequence `222`. `222` becomes `032` becomes `013`. Even if the next coefficient in the sequence is `2`, the third coefficient will be reduced once and the next coefficient (`2`, increased to `3`) will only be eligible for reduction once. This demonstrates, even for a worst case of log2(k) `2`s followed by `1`s (as possible if the modulus is a Mersenne prime), the log2(k) `2`s can be reduced as necessary so long as there is a single coefficient after (requiring the entire sequence be at least of length log2(k) + 1). For a 2-bit number, log2(k) + 1 == 2, so this holds for any odd prime field. To fully type out the demonstration for the Mersenne prime 3, with scalar to encode 1 (the highest value less than the number of bits): 10 - Little-endian bits of 1 21 - Little-endian bits of 1, plus the modulus 02 - After one reduction, where the sum of the coefficients does in fact equal 2 (the target) */ { let mut log2_num_bits = 0; while (1 << log2_num_bits) < num_bits { log2_num_bits += 1; } for _ in 0 .. log2_num_bits { // If the sum of coefficients is the amount of bits, we're done let mut done = sum_of_coefficients.ct_eq(&num_bits); for i in 0 .. (num_bits_usize - 1) { let should_act = (!done) & decomposition[i].ct_gt(&1); // Subtract 2 from this coefficient let amount_to_sub = <_>::conditional_select(&0, &2, should_act); decomposition[i] -= amount_to_sub; // Add 1 to the next coefficient let amount_to_add = <_>::conditional_select(&0, &1, should_act); decomposition[i + 1] += amount_to_add; // Also update the sum of coefficients sum_of_coefficients -= <_>::conditional_select(&0, &1, should_act); // If we updated the coefficients this loop iter, we're done for this loop iter done |= should_act; } } } for _ in 0 .. num_bits { // If the sum of coefficients is the amount of bits, we're done let mut done = sum_of_coefficients.ct_eq(&num_bits); // Find the highest coefficient currently non-zero for i in (1 .. decomposition.len()).rev() { // If this is non-zero, we should decrement this coefficient if we haven't already // decremented a coefficient this round let is_non_zero = !(0.ct_eq(&decomposition[i])); let should_act = (!done) & is_non_zero; // Update this coefficient and the prior coefficient let amount_to_sub = <_>::conditional_select(&0, &1, should_act); decomposition[i] -= amount_to_sub; let amount_to_add = <_>::conditional_select(&0, &2, should_act); // i must be at least 1, so i - 1 will be at least 0 (meaning it's safe to index with) decomposition[i - 1] += amount_to_add; // Also update the sum of coefficients sum_of_coefficients += <_>::conditional_select(&0, &1, should_act); // If we updated the coefficients this loop iter, we're done for this loop iter done |= should_act; } } debug_assert!(bool::from(decomposition.iter().sum::().ct_eq(&num_bits))); decomposition } /// 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, transcript: [u8; 32], coefficients: usize, ecdh_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], evrf_private_key: &Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, ) -> Result, AcError> { let curve_spec = CurveSpec { a: <::EmbeddedCurve as Ciphersuite>::G::a(), b: <::EmbeddedCurve as Ciphersuite>::G::b(), }; // A tape of the discrete logarithm, then [zero, x**i, y x**i, y, x_coord, y_coord] let mut vector_commitment_tape = vec![]; 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((::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)); } { 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(); 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)); } // 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.deref(), ); // 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.deref(), ); *res += dh_x; } actual_coefficients.push(res); } debug_assert_eq!(actual_coefficients.len(), coefficients); dlog.zeroize(); } // Now do the ECDHs for the encryption let mut encryption_masks = 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]); let mut res = Zeroizing::new(C::F::ZERO); for j in 0 .. 2 { let mut ecdh_private_key; loop { ecdh_private_key = ::F::random(&mut *rng); // Generate a non-0 ECDH private key, as necessary to not produce an identity output // Identity isn't representable with the divisors, hence the explicit effort if bool::from(!ecdh_private_key.is_zero()) { break; } } 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(); } encryption_masks.push(res); } debug_assert_eq!(encryption_masks.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(coefficients, ecdh_public_keys.len()); let mut vector_commitments = 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, mask: C::F::random(&mut *rng), }); } vector_commitment_tape.zeroize(); drop(vector_commitment_tape); 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 enc_mask in &encryption_masks { commitments.push(PedersenCommitment { value: **enc_mask, mask: C::F::random(&mut *rng) }); } let mut transcript = ProverTranscript::new(transcript); let commited_commitments = transcript.write_commitments( vector_commitments .iter() .map(|commitment| { commitment .commit(generators.g_bold_slice(), generators.h_bold_slice(), generators.h()) .ok_or(AcError::NotEnoughGenerators) }) .collect::>()?, commitments .iter() .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( &curve_spec, evrf_public_key, coefficients, &ecdh_commitments_xy, &generator_tables, &mut circuit, &mut transcript, ); let (statement, Some(witness)) = circuit .statement( generators.reduce(generators_to_use).ok_or(AcError::NotEnoughGenerators)?, commited_commitments, ) .unwrap() else { panic!("proving yet wasn't yielded the witness"); }; statement.prove(&mut *rng, &mut transcript, witness).unwrap(); // Push the reveal onto the transcript 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(commitments.len()); agg_weights.push(C::F::ONE); while agg_weights.len() < commitments.len() { agg_weights.push(transcript.challenge::()); } let mut x = commitments .iter() .zip(&agg_weights) .map(|(commitment, weight)| commitment.mask * *weight) .sum::(); // Do a Schnorr PoK for the randomness of the aggregated Pedersen commitment let mut r = C::F::random(&mut *rng); transcript.push_point(generators.h() * r); let c = transcript.challenge::(); transcript.push_scalar(r + (c * x)); r.zeroize(); x.zeroize(); Ok(EvrfProveResult { coefficients: actual_coefficients, encryption_masks, proof: transcript.complete(), }) } /// Verify an eVRF proof, returning the commitments output. #[allow(clippy::too_many_arguments)] pub(crate) fn verify( rng: &mut (impl RngCore + CryptoRng), generators: &Generators, verifier: &mut BatchVerifier, transcript: [u8; 32], coefficients: usize, ecdh_public_keys: &[<::EmbeddedCurve as Ciphersuite>::G], evrf_public_key: <::EmbeddedCurve as Ciphersuite>::G, proof: &[u8], ) -> Result, ()> { let curve_spec = CurveSpec { a: <::EmbeddedCurve as Ciphersuite>::G::a(), b: <::EmbeddedCurve as Ciphersuite>::G::b(), }; 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(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 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_keys = Vec::with_capacity(ecdh_public_keys.len()); let mut ecdh_keys_xy = Vec::with_capacity(ecdh_public_keys.len()); for _ in 0 .. ecdh_public_keys.len() { let ecdh_keys_i = [ transcript.read_point::().map_err(|_| ())?, transcript.read_point::().map_err(|_| ())?, ]; ecdh_keys.push(ecdh_keys_i); // This bans zero ECDH keys ecdh_keys_xy.push([ <::G as DivisorCurve>::to_xy(ecdh_keys_i[0]).ok_or(())?, <::G as DivisorCurve>::to_xy(ecdh_keys_i[1]).ok_or(())?, ]); } let mut circuit = Circuit::verify(); Self::circuit( &curve_spec, ::G::to_xy(evrf_public_key).ok_or(())?, coefficients, &ecdh_keys_xy, &generator_tables, &mut circuit, &mut transcript, ); let (statement, None) = circuit.statement(generators.reduce(generators_to_use).ok_or(())?, all_commitments).unwrap() else { panic!("verifying yet was yielded a witness"); }; statement.verify(rng, verifier, &mut transcript).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(|_| ())?); } // 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() < commitments.len() { agg_weights.push(transcript.challenge::()); } let sum_points = 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)] let A = sum_commitments - sum_points; #[allow(non_snake_case)] let R = transcript.read_point::().map_err(|_| ())?; let c = transcript.challenge::(); let s = transcript.read_scalar::().map_err(|_| ())?; // Doesn't batch verify this as we can't access the internals of the GBP batch verifier if (R + (A * c)) != (generators.h() * s) { Err(())?; } if !transcript.complete().is_empty() { Err(())? }; let encryption_commitments = openings[coefficients ..].to_vec(); let coefficients = openings[.. coefficients].to_vec(); Ok(EvrfVerifyResult { coefficients, ecdh_keys, encryption_commitments }) } }