mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
dkg-evrf crate
monero-oxide relies on ciphersuite, which is in-tree, yet we've made breaking changes since. This commit adds a patch so monero-oxide -> patches/ciphersuite -> crypto/ciphersuite, with patches/ciphersuite resolving the breaking changes.
This commit is contained in:
684
crypto/dkg/evrf/src/proof/mod.rs
Normal file
684
crypto/dkg/evrf/src/proof/mod.rs
Normal file
@@ -0,0 +1,684 @@
|
||||
use core::{marker::PhantomData, ops::Deref, fmt};
|
||||
#[allow(unused_imports)]
|
||||
use std_shims::prelude::*;
|
||||
use std_shims::{vec, vec::Vec};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite};
|
||||
|
||||
use generalized_bulletproofs::{
|
||||
Generators, BatchVerifier, PedersenCommitment, PedersenVectorCommitment,
|
||||
transcript::{Transcript as ProverTranscript, VerifierTranscript},
|
||||
arithmetic_circuit_proof::*,
|
||||
};
|
||||
use generalized_bulletproofs_circuit_abstraction::{Transcript, Circuit as BpCircuit};
|
||||
|
||||
use ec_divisors::{DivisorCurve, ScalarDecomposition};
|
||||
use generalized_bulletproofs_ec_gadgets::{
|
||||
CurveSpec, DiscreteLogChallenge, ChallengedGenerator, EcDlogGadgets,
|
||||
};
|
||||
|
||||
use crate::Curves;
|
||||
|
||||
mod tape;
|
||||
use tape::*;
|
||||
|
||||
type EmbeddedPoint<C> = (
|
||||
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
|
||||
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
|
||||
);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
struct Circuit<
|
||||
'a,
|
||||
C: Curves,
|
||||
CG: Iterator<
|
||||
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
>,
|
||||
> {
|
||||
curve_spec: &'a CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
|
||||
circuit: &'a mut BpCircuit<C::ToweringCurve>,
|
||||
challenge: DiscreteLogChallenge<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
challenged_G:
|
||||
ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
challenged_generators: &'a mut CG,
|
||||
tape: Tape,
|
||||
pedersen_commitment_tape: PedersenCommitmentTape,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
C: Curves,
|
||||
CG: Iterator<
|
||||
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
|
||||
>,
|
||||
> Circuit<'a, C, CG>
|
||||
{
|
||||
/// Generate coefficients for secret-sharing via an eVRF.
|
||||
///
|
||||
/// This follows the methodology of Protocol 5 from the
|
||||
/// [eVRF paper](https://eprint.iacr.org/2024/397.pdf).
|
||||
fn coefficients(&mut self, evrf_public_key: EmbeddedPoint<C>, coefficients: usize) {
|
||||
/*
|
||||
Read the opening of the prover's eVRF public key, along with all the proofs for the eVRF.
|
||||
Each invocation of the eVRF requires performing _two_ Diffie-Hellmans against
|
||||
uniformly-sampled points.
|
||||
*/
|
||||
let mut point_with_dlogs = self.tape.read_points_with_common_dlog::<C>(1 + (2 * coefficients));
|
||||
|
||||
// Assert this discrete logarithm opens the prover's public key
|
||||
let point = self.circuit.discrete_log(
|
||||
self.curve_spec,
|
||||
point_with_dlogs.next().unwrap(),
|
||||
&self.challenge,
|
||||
&self.challenged_G,
|
||||
);
|
||||
self.circuit.equality(LinComb::from(point.x()), &LinComb::empty().constant(evrf_public_key.0));
|
||||
self.circuit.equality(LinComb::from(point.y()), &LinComb::empty().constant(evrf_public_key.1));
|
||||
|
||||
// Verify the eVRF invocations
|
||||
for _ in 0 .. coefficients {
|
||||
let mut lincomb = LinComb::empty();
|
||||
for challenged_generator in
|
||||
[self.challenged_generators.next().unwrap(), self.challenged_generators.next().unwrap()]
|
||||
{
|
||||
let point = self.circuit.discrete_log(
|
||||
self.curve_spec,
|
||||
point_with_dlogs.next().unwrap(),
|
||||
&self.challenge,
|
||||
&challenged_generator,
|
||||
);
|
||||
lincomb = lincomb.term(<C::ToweringCurve as Ciphersuite>::F::ONE, point.x());
|
||||
}
|
||||
/*
|
||||
Constrain the sum of the two `x` coordinates to be equal to the value committed to in a
|
||||
Pedersen commitment
|
||||
*/
|
||||
self.circuit.equality(
|
||||
lincomb,
|
||||
&LinComb::from(self.pedersen_commitment_tape.allocate_pedersen_commitment()),
|
||||
);
|
||||
}
|
||||
debug_assert!(point_with_dlogs.next().is_none());
|
||||
}
|
||||
|
||||
/// Sample an encryption key, proving it's correctly-formed and committed to within a Pedersen
|
||||
/// commitment.
|
||||
fn verifiable_encryption(&mut self, ecdh_commitments: &[EmbeddedPoint<C>; 2]) {
|
||||
// Read the public key used for this encryption
|
||||
let challenged_public_key = self.challenged_generators.next().unwrap();
|
||||
// We perform two separate ECDHs, the sum of their `x` coordinates being our encryption key
|
||||
let mut lincomb = LinComb::empty();
|
||||
for ecdh_commitment in ecdh_commitments {
|
||||
// We open the posted commitment to the ephemeral secret used, and the ECDH value
|
||||
let mut point_with_dlogs = self.tape.read_points_with_common_dlog::<C>(2);
|
||||
|
||||
let point = self.circuit.discrete_log(
|
||||
self.curve_spec,
|
||||
point_with_dlogs.next().unwrap(),
|
||||
&self.challenge,
|
||||
&self.challenged_G,
|
||||
);
|
||||
// Ensure this equals the publicly posted commitment
|
||||
self
|
||||
.circuit
|
||||
.equality(LinComb::from(point.x()), &LinComb::empty().constant(ecdh_commitment.0));
|
||||
self
|
||||
.circuit
|
||||
.equality(LinComb::from(point.y()), &LinComb::empty().constant(ecdh_commitment.1));
|
||||
|
||||
let point = self.circuit.discrete_log(
|
||||
self.curve_spec,
|
||||
point_with_dlogs.next().unwrap(),
|
||||
&self.challenge,
|
||||
&challenged_public_key,
|
||||
);
|
||||
lincomb = lincomb.term(<C::ToweringCurve as Ciphersuite>::F::ONE, point.x());
|
||||
debug_assert!(point_with_dlogs.next().is_none());
|
||||
}
|
||||
|
||||
// Require the encryption mask be successfully commited to within a Pedersen commitment
|
||||
self.circuit.equality(
|
||||
lincomb,
|
||||
&LinComb::from(self.pedersen_commitment_tape.allocate_pedersen_commitment()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of proving.
|
||||
pub(super) struct ProveResult<C: Curves> {
|
||||
/// The coefficients for use in the DKG.
|
||||
pub(super) coefficients: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
|
||||
/// The masks to encrypt secret shares with.
|
||||
pub(super) encryption_keys: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
|
||||
/// The proof itself.
|
||||
pub(super) proof: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(super) struct Verified<C: Curves> {
|
||||
/// The commitments to the coefficients used within the DKG.
|
||||
pub(super) coefficients: Vec<<C::ToweringCurve as Ciphersuite>::G>,
|
||||
/// The ephemeral public keys to perform ECDHs with
|
||||
pub(super) ecdh_commitments: Vec<[<C::EmbeddedCurve as Ciphersuite>::G; 2]>,
|
||||
/// The commitments to the masks used to encrypt secret shares with.
|
||||
pub(super) encryption_key_commitments: Vec<<C::ToweringCurve as Ciphersuite>::G>,
|
||||
}
|
||||
|
||||
impl<C: Curves> fmt::Debug for Verified<C> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Verified").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
type GeneratorTable<C> = generalized_bulletproofs_ec_gadgets::GeneratorTable<
|
||||
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
|
||||
<C as Curves>::EmbeddedCurveParameters,
|
||||
>;
|
||||
|
||||
pub(super) struct Proof<C>(PhantomData<C>);
|
||||
impl<C: Curves> Proof<C> {
|
||||
fn discrete_log_claims(coefficients: usize, participants: usize) -> usize {
|
||||
/*
|
||||
- 1 DLOG to prove the discrete logarithm corresponds to the eVRF public key
|
||||
- 2 DLOGs per coefficient in the secret-sharing polynomial
|
||||
- 2 DLOGs per each ECDH (one to open the commitment, one for the ECDH itself), with two ECDHs
|
||||
for each participant (with the sum of their `x` coordinates being uniform and used as the
|
||||
mask)
|
||||
*/
|
||||
const DLOGS_PER_COEFFICIENT: usize = 2;
|
||||
const ECDHS_PER_PARTICIPANT: usize = 2;
|
||||
const DLOGS_PER_ECDH: usize = 2;
|
||||
const DLOGS_PER_PARTICIPANT: usize = ECDHS_PER_PARTICIPANT * DLOGS_PER_ECDH;
|
||||
1 + (DLOGS_PER_COEFFICIENT * coefficients) + (DLOGS_PER_PARTICIPANT * participants)
|
||||
}
|
||||
|
||||
fn expected_multiplications(coefficients: usize, participants: usize) -> usize {
|
||||
const MULS_PER_DLOG: usize = 7;
|
||||
MULS_PER_DLOG * Self::discrete_log_claims(coefficients, participants)
|
||||
}
|
||||
|
||||
pub(crate) fn generators_to_use(coefficients: usize, participants: usize) -> usize {
|
||||
/*
|
||||
`expected_multiplications` may be as small as 16, which would create an excessive amount of
|
||||
vector commitments (as a vector commitment can only commit to as many variables as we have
|
||||
multiplications).
|
||||
|
||||
We require the actual amount of multiplications to be at least 2048 (even though that
|
||||
that 'wastes' thousands of multiplications) to ensure the bandwidth usage remains reasonable.
|
||||
*/
|
||||
Self::expected_multiplications(coefficients, participants).next_power_of_two().max(2048)
|
||||
}
|
||||
|
||||
fn variables_in_vector_commitments(coefficients: usize, participants: usize) -> usize {
|
||||
Tape::variables_for_points_with_common_dlog::<C>(1 + (2 * coefficients)) +
|
||||
(participants * 2 * Tape::variables_for_points_with_common_dlog::<C>(2))
|
||||
}
|
||||
|
||||
fn circuit(
|
||||
curve_spec: &CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
|
||||
evrf_public_key: EmbeddedPoint<C>,
|
||||
coefficients: usize,
|
||||
ecdh_commitments: &[[EmbeddedPoint<C>; 2]],
|
||||
generator_tables: &[&GeneratorTable<C>],
|
||||
circuit: &mut BpCircuit<C::ToweringCurve>,
|
||||
transcript: &mut impl Transcript,
|
||||
) {
|
||||
let participants = ecdh_commitments.len();
|
||||
let generators_to_use = Self::generators_to_use(coefficients, participants);
|
||||
|
||||
// Sample the challenge for all the discrete-logarithm claims
|
||||
let (challenge, challenged_generators) =
|
||||
circuit.discrete_log_challenge(transcript, curve_spec, generator_tables);
|
||||
|
||||
/*
|
||||
The generator tables, and the challenged generators, will have the following layout:
|
||||
- G
|
||||
- Generators for the eVRFs used to sample the coefficients
|
||||
- The participants' public keys, used for performing ECDHs with
|
||||
*/
|
||||
let mut challenged_generators = challenged_generators.into_iter();
|
||||
#[allow(non_snake_case)]
|
||||
let challenged_G = challenged_generators.next().unwrap();
|
||||
|
||||
let tape = Tape::new(generators_to_use);
|
||||
let pedersen_commitment_tape = PedersenCommitmentTape::new();
|
||||
|
||||
{
|
||||
let mut circuit = Circuit::<C, _> {
|
||||
curve_spec,
|
||||
circuit,
|
||||
challenge,
|
||||
challenged_G,
|
||||
challenged_generators: &mut challenged_generators,
|
||||
tape,
|
||||
pedersen_commitment_tape,
|
||||
};
|
||||
|
||||
circuit.coefficients(evrf_public_key, coefficients);
|
||||
|
||||
// Now execute the circuit for the ECDHs
|
||||
for ecdh_commitments in ecdh_commitments {
|
||||
circuit.verifiable_encryption(ecdh_commitments);
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert_eq!(
|
||||
Self::expected_multiplications(coefficients, participants),
|
||||
circuit.muls(),
|
||||
"unexpected amount of multiplications actually used"
|
||||
);
|
||||
debug_assert!(
|
||||
challenged_generators.next().is_none(),
|
||||
"didn't consume all challenged generators"
|
||||
);
|
||||
}
|
||||
|
||||
/// Sample the points for the eVRF invocations used for the coefficients.
|
||||
fn sample_coefficients_evrf_points(
|
||||
seed: [u8; 32],
|
||||
coefficients: usize,
|
||||
) -> Vec<<C::EmbeddedCurve as Ciphersuite>::G> {
|
||||
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||
let quantity = 2 * coefficients;
|
||||
let mut res = Vec::with_capacity(quantity);
|
||||
for _ in 0 .. quantity {
|
||||
res.push(crate::sample_point::<C::EmbeddedCurve>(&mut rng));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Create the required tables for the generators.
|
||||
fn generator_tables(
|
||||
coefficients_evrf_points: &[<C::EmbeddedCurve as Ciphersuite>::G],
|
||||
participants: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
|
||||
) -> Vec<GeneratorTable<C>> {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
|
||||
};
|
||||
|
||||
let mut generator_tables =
|
||||
Vec::with_capacity(1 + coefficients_evrf_points.len() + participants.len());
|
||||
{
|
||||
let (x, y) =
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(<C::EmbeddedCurve as Ciphersuite>::generator())
|
||||
.unwrap();
|
||||
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
|
||||
}
|
||||
for generator in coefficients_evrf_points {
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
|
||||
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
|
||||
}
|
||||
for generator in participants {
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
|
||||
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
|
||||
}
|
||||
generator_tables
|
||||
}
|
||||
|
||||
pub(super) fn prove(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
generators: &Generators<C::ToweringCurve>,
|
||||
transcript: [u8; 32],
|
||||
coefficients: usize,
|
||||
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_private_key: &Zeroizing<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
) -> Result<ProveResult<C>, AcError> {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
|
||||
};
|
||||
|
||||
let coefficients_evrf_points = Self::sample_coefficients_evrf_points(transcript, coefficients);
|
||||
let generator_tables =
|
||||
Self::generator_tables(&coefficients_evrf_points, participant_public_keys);
|
||||
|
||||
// Push a discrete logarithm onto the tape
|
||||
let discrete_log =
|
||||
|vector_commitment_tape: &mut Vec<_>,
|
||||
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>| {
|
||||
for coefficient in dlog.decomposition() {
|
||||
vector_commitment_tape.push(<_>::from(*coefficient));
|
||||
}
|
||||
};
|
||||
|
||||
// Push a discrete-log claim onto the tape.
|
||||
//
|
||||
// Returns the point for which the claim was made.
|
||||
let discrete_log_claim =
|
||||
|vector_commitment_tape: &mut Vec<_>,
|
||||
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
|
||||
generator: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G| {
|
||||
{
|
||||
let divisor =
|
||||
Zeroizing::new(dlog.scalar_mul_divisor(generator).normalize_x_coefficient());
|
||||
vector_commitment_tape.push(divisor.zero_coefficient);
|
||||
for coefficient in divisor.x_coefficients.iter().skip(1) {
|
||||
vector_commitment_tape.push(*coefficient);
|
||||
}
|
||||
for coefficient in divisor.yx_coefficients.first().unwrap_or(&vec![]) {
|
||||
vector_commitment_tape.push(*coefficient);
|
||||
}
|
||||
vector_commitment_tape.push(
|
||||
divisor
|
||||
.y_coefficients
|
||||
.first()
|
||||
.copied()
|
||||
.unwrap_or(<C::ToweringCurve as Ciphersuite>::F::ZERO),
|
||||
);
|
||||
}
|
||||
|
||||
let dh = generator * dlog.scalar();
|
||||
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(dh).unwrap();
|
||||
vector_commitment_tape.push(x);
|
||||
vector_commitment_tape.push(y);
|
||||
(dh, (x, y))
|
||||
};
|
||||
|
||||
let mut vector_commitment_tape = Zeroizing::new(Vec::with_capacity(
|
||||
Self::variables_in_vector_commitments(coefficients, participant_public_keys.len()),
|
||||
));
|
||||
|
||||
// Handle the coefficients
|
||||
let mut coefficients = Vec::with_capacity(coefficients);
|
||||
let evrf_public_key = {
|
||||
let evrf_private_key =
|
||||
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(**evrf_private_key)
|
||||
.expect("eVRF private key was zero");
|
||||
|
||||
discrete_log(&mut vector_commitment_tape, &evrf_private_key);
|
||||
|
||||
// Push the divisor for proving that we're using the correct scalar
|
||||
let (_, evrf_public_key) = discrete_log_claim(
|
||||
&mut vector_commitment_tape,
|
||||
&evrf_private_key,
|
||||
<<C as Curves>::EmbeddedCurve as Ciphersuite>::generator(),
|
||||
);
|
||||
|
||||
// Push the divisor for each point we use in the eVRF
|
||||
for pair in coefficients_evrf_points.chunks(2) {
|
||||
let mut coefficient = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
for point in pair {
|
||||
let (_, (dh_x, _)) =
|
||||
discrete_log_claim(&mut vector_commitment_tape, &evrf_private_key, *point);
|
||||
*coefficient += dh_x;
|
||||
}
|
||||
coefficients.push(coefficient);
|
||||
}
|
||||
|
||||
evrf_public_key
|
||||
};
|
||||
|
||||
// Handle the verifiable encryption
|
||||
let mut encryption_keys = Vec::with_capacity(participant_public_keys.len());
|
||||
let mut ecdh_commitments = Vec::with_capacity(2 * participant_public_keys.len());
|
||||
let mut ecdh_commitments_xy = Vec::with_capacity(participant_public_keys.len());
|
||||
for participant_public_key in participant_public_keys {
|
||||
let mut ecdh_commitments_xy_i =
|
||||
[(<C::ToweringCurve as Ciphersuite>::F::ZERO, <C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
2];
|
||||
let mut encryption_key = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
for ecdh_commitments_xy_i_j_dest in &mut ecdh_commitments_xy_i {
|
||||
let mut ecdh_ephemeral_secret;
|
||||
loop {
|
||||
ecdh_ephemeral_secret =
|
||||
Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut *rng));
|
||||
// 0 would produce the identity, which isn't representable within the discrete-log proof.
|
||||
if bool::from(!ecdh_ephemeral_secret.is_zero()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let ecdh_ephemeral_secret =
|
||||
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(*ecdh_ephemeral_secret)
|
||||
.expect("ECDH ephemeral secret zero");
|
||||
discrete_log(&mut vector_commitment_tape, &ecdh_ephemeral_secret);
|
||||
|
||||
// Push a divisor for proving that we're using the correct scalar for the commitment
|
||||
let (ecdh_commitment, ecdh_commitment_xy_i_j) = discrete_log_claim(
|
||||
&mut vector_commitment_tape,
|
||||
&ecdh_ephemeral_secret,
|
||||
<<C as Curves>::EmbeddedCurve as Ciphersuite>::generator(),
|
||||
);
|
||||
ecdh_commitments.push(ecdh_commitment);
|
||||
*ecdh_commitments_xy_i_j_dest = ecdh_commitment_xy_i_j;
|
||||
// Push a divisor for the key we're performing the ECDH with
|
||||
let (_, (dh_x, _)) = discrete_log_claim(
|
||||
&mut vector_commitment_tape,
|
||||
&ecdh_ephemeral_secret,
|
||||
*participant_public_key,
|
||||
);
|
||||
*encryption_key += dh_x;
|
||||
}
|
||||
ecdh_commitments_xy.push(ecdh_commitments_xy_i);
|
||||
encryption_keys.push(encryption_key);
|
||||
}
|
||||
|
||||
// Convert the vector commitment tape into vector commitments
|
||||
let generators_to_use =
|
||||
Self::generators_to_use(coefficients.len(), participant_public_keys.len());
|
||||
debug_assert_eq!(
|
||||
Self::variables_in_vector_commitments(coefficients.len(), participant_public_keys.len()),
|
||||
vector_commitment_tape.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) {
|
||||
vector_commitments.push(PedersenVectorCommitment {
|
||||
g_values: chunk.to_vec().into(),
|
||||
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
|
||||
});
|
||||
}
|
||||
|
||||
// Create the Pedersen commitments
|
||||
let mut commitments = Vec::with_capacity(coefficients.len() + participant_public_keys.len());
|
||||
for coefficient in &coefficients {
|
||||
commitments.push(PedersenCommitment {
|
||||
value: **coefficient,
|
||||
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
|
||||
});
|
||||
}
|
||||
for enc_mask in &encryption_keys {
|
||||
commitments.push(PedersenCommitment {
|
||||
value: **enc_mask,
|
||||
mask: <C::ToweringCurve as Ciphersuite>::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())
|
||||
.ok_or(AcError::NotEnoughGenerators)
|
||||
})
|
||||
.collect::<Result<_, _>>()?,
|
||||
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 = BpCircuit::prove(vector_commitments, commitments.clone());
|
||||
Self::circuit(
|
||||
&curve_spec,
|
||||
evrf_public_key,
|
||||
coefficients.len(),
|
||||
&ecdh_commitments_xy,
|
||||
&generator_tables.iter().collect::<Vec<_>>(),
|
||||
&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));
|
||||
}
|
||||
|
||||
// Prove the openings of the commitments were correct
|
||||
let mut x = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
|
||||
for commitment in commitments {
|
||||
*x += commitment.mask * transcript.challenge::<C::ToweringCurve>();
|
||||
}
|
||||
|
||||
// Produce a Schnorr PoK for the weighted-sum of the Pedersen commitments' blinding factors
|
||||
let r = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::random(&mut *rng));
|
||||
transcript.push_point(&(generators.h() * r.deref()));
|
||||
let c = transcript.challenge::<C::ToweringCurve>();
|
||||
transcript.push_scalar((c * x.deref()) + r.deref());
|
||||
|
||||
Ok(ProveResult { coefficients, encryption_keys, proof: transcript.complete() })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn verify(
|
||||
rng: &mut (impl RngCore + CryptoRng),
|
||||
generators: &Generators<C::ToweringCurve>,
|
||||
verifier: &mut BatchVerifier<C::ToweringCurve>,
|
||||
transcript: [u8; 32],
|
||||
coefficients: usize,
|
||||
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
|
||||
evrf_public_key: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G,
|
||||
proof: &[u8],
|
||||
) -> Result<Verified<C>, ()> {
|
||||
let (mut transcript, ecdh_commitments, pedersen_commitments) = {
|
||||
let curve_spec = CurveSpec {
|
||||
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
|
||||
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
|
||||
};
|
||||
|
||||
let coefficients_evrf_points =
|
||||
Self::sample_coefficients_evrf_points(transcript, coefficients);
|
||||
let generator_tables =
|
||||
Self::generator_tables(&coefficients_evrf_points, participant_public_keys);
|
||||
|
||||
let generators_to_use = Self::generators_to_use(coefficients, participant_public_keys.len());
|
||||
|
||||
let mut transcript = VerifierTranscript::new(transcript, proof);
|
||||
|
||||
let vector_commitments =
|
||||
Self::variables_in_vector_commitments(coefficients, participant_public_keys.len())
|
||||
.div_ceil(generators_to_use);
|
||||
/*
|
||||
One commitment is used to commit to each coefficient of the secret-sharing polynomial, and
|
||||
one commitment is used to commit to each encryption key used to encrypt a secret share to
|
||||
its recipient.
|
||||
*/
|
||||
let pedersen_commitments = coefficients + participant_public_keys.len();
|
||||
let all_commitments =
|
||||
transcript.read_commitments(vector_commitments, pedersen_commitments).map_err(|_| ())?;
|
||||
let pedersen_commitments = all_commitments.V().to_vec();
|
||||
|
||||
// Read the commitments to the ephemeral secrets for the ECDHs
|
||||
let mut ecdh_commitments = Vec::with_capacity(participant_public_keys.len());
|
||||
let mut ecdh_commitments_xy = Vec::with_capacity(participant_public_keys.len());
|
||||
for _ in 0 .. participant_public_keys.len() {
|
||||
let ecdh_commitments_i = [
|
||||
transcript.read_point::<C::EmbeddedCurve>().map_err(|_| ())?,
|
||||
transcript.read_point::<C::EmbeddedCurve>().map_err(|_| ())?,
|
||||
];
|
||||
ecdh_commitments.push(ecdh_commitments_i);
|
||||
// This inherently bans using the identity point, as it won't have an affine representation
|
||||
ecdh_commitments_xy.push([
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[0])
|
||||
.ok_or(())?,
|
||||
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[1])
|
||||
.ok_or(())?,
|
||||
]);
|
||||
}
|
||||
|
||||
let mut circuit = BpCircuit::verify();
|
||||
Self::circuit(
|
||||
&curve_spec,
|
||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(evrf_public_key).ok_or(())?,
|
||||
coefficients,
|
||||
&ecdh_commitments_xy,
|
||||
&generator_tables.iter().collect::<Vec<_>>(),
|
||||
&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(|_| ())?;
|
||||
|
||||
(transcript, ecdh_commitments, pedersen_commitments)
|
||||
};
|
||||
|
||||
// Read the openings for each of the Pedersen commitments
|
||||
let mut openings = Vec::with_capacity(pedersen_commitments.len());
|
||||
for _ in 0 .. pedersen_commitments.len() {
|
||||
openings.push(transcript.read_point::<C::ToweringCurve>().map_err(|_| ())?);
|
||||
}
|
||||
|
||||
/*
|
||||
Verify the openings of each of the Pedersen commitments.
|
||||
|
||||
We do this via verifying the prover knows an opening of their Pedersen commitment, minus the
|
||||
claimed opening, over the blinding generator. For efficiency, we take a random combination of
|
||||
all commitments/openings, solely requiring the prover know the single opening for the
|
||||
combination.
|
||||
*/
|
||||
{
|
||||
let (weighted_sum_commitments, weighted_sum_openings) = {
|
||||
let mut weighted_sum_commitments = Vec::with_capacity(pedersen_commitments.len());
|
||||
let mut weighted_sum_openings = Vec::with_capacity(pedersen_commitments.len());
|
||||
for (pedersen_commitment, opening) in pedersen_commitments.iter().zip(&openings) {
|
||||
let weight = transcript.challenge::<C::ToweringCurve>();
|
||||
weighted_sum_commitments.push((weight, *pedersen_commitment));
|
||||
weighted_sum_openings.push((weight, *opening));
|
||||
}
|
||||
(
|
||||
multiexp::multiexp_vartime(&weighted_sum_commitments),
|
||||
multiexp::multiexp_vartime(&weighted_sum_openings),
|
||||
)
|
||||
};
|
||||
#[allow(non_snake_case)]
|
||||
let A = weighted_sum_commitments - weighted_sum_openings;
|
||||
|
||||
// Schnorr signature
|
||||
#[allow(non_snake_case)]
|
||||
let R = transcript.read_point::<C::ToweringCurve>().map_err(|_| ())?;
|
||||
let c = transcript.challenge::<C::ToweringCurve>();
|
||||
let s = transcript.read_scalar::<C::ToweringCurve>().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 coefficients = openings[.. coefficients].to_vec();
|
||||
let encryption_key_commitments = openings[coefficients.len() ..].to_vec();
|
||||
Ok(Verified { coefficients, ecdh_commitments, encryption_key_commitments })
|
||||
}
|
||||
}
|
||||
106
crypto/dkg/evrf/src/proof/tape.rs
Normal file
106
crypto/dkg/evrf/src/proof/tape.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray};
|
||||
|
||||
use generalized_bulletproofs_circuit_abstraction::Variable;
|
||||
use generalized_bulletproofs_ec_gadgets::{DiscreteLogParameters, Divisor, PointWithDlog};
|
||||
|
||||
use crate::Curves;
|
||||
|
||||
/*
|
||||
For all variables we must commit to during the ZK proof, we place them on the 'tape'. The tape
|
||||
is a linear representation of every single variable committed to by the proof, from which we can
|
||||
read a collection of variables from/push a collection of variables onto. This offers an API
|
||||
similar to reading/writing to a byte stream, despite working with variables in a ZK proof.
|
||||
*/
|
||||
pub(super) struct Tape {
|
||||
generators: usize,
|
||||
current_position: usize,
|
||||
}
|
||||
impl Tape {
|
||||
// Construct a new tape.
|
||||
pub(super) fn new(generators: usize) -> Self {
|
||||
Self { generators, current_position: 0 }
|
||||
}
|
||||
|
||||
/// Read a Variable from the tape.
|
||||
fn read_one_from_tape(&mut self) -> Variable {
|
||||
let commitment = self.current_position / self.generators;
|
||||
let index = self.current_position % self.generators;
|
||||
let res = Variable::CG { commitment, index };
|
||||
self.current_position += 1;
|
||||
res
|
||||
}
|
||||
|
||||
/// Read a fixed-length array of variables from the tape.
|
||||
fn read_from_tape<N: ArrayLength>(&mut self) -> GenericArray<Variable, N> {
|
||||
GenericArray::<Variable, N>::generate(|_| self.read_one_from_tape())
|
||||
}
|
||||
|
||||
/// Read `PointWithDlog`s, which share a discrete logarithm, from the tape.
|
||||
pub(super) fn read_points_with_common_dlog<C: Curves>(
|
||||
&mut self,
|
||||
quantity: usize,
|
||||
) -> impl use<'_, C> + Iterator<Item = PointWithDlog<C::EmbeddedCurveParameters>> {
|
||||
/*
|
||||
The tape expects the format of:
|
||||
- Discrete logarithm
|
||||
- Divisor (zero coefficient, x coefficients, y x**i coefficients, y coefficient)
|
||||
- Point (x, y)
|
||||
Note the `x` coefficients are only from the power of two, and `i >= 1`.
|
||||
*/
|
||||
let dlog =
|
||||
self.read_from_tape::<<C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits>();
|
||||
|
||||
struct PointIterator<'a, C: Curves>(
|
||||
&'a mut Tape,
|
||||
GenericArray<Variable, <C::EmbeddedCurveParameters as DiscreteLogParameters>::ScalarBits>,
|
||||
PhantomData<C>,
|
||||
);
|
||||
impl<'a, C: Curves> Iterator for PointIterator<'a, C> {
|
||||
type Item = PointWithDlog<C::EmbeddedCurveParameters>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let divisor = {
|
||||
let zero = self.0.read_one_from_tape();
|
||||
let x_from_power_of_2 = self.0.read_from_tape();
|
||||
let yx = self.0.read_from_tape();
|
||||
let y = self.0.read_one_from_tape();
|
||||
Divisor { zero, x_from_power_of_2, yx, y }
|
||||
};
|
||||
|
||||
let point = (
|
||||
// x coordinate
|
||||
self.0.read_one_from_tape(),
|
||||
// y coordinate
|
||||
self.0.read_one_from_tape(),
|
||||
);
|
||||
|
||||
Some(PointWithDlog { dlog: self.1.clone(), divisor, point })
|
||||
}
|
||||
}
|
||||
|
||||
PointIterator(self, dlog, PhantomData::<C>).take(quantity)
|
||||
}
|
||||
|
||||
/// The amount of variables the points with a common discrete logarithm will use on the tape.
|
||||
pub(super) fn variables_for_points_with_common_dlog<C: Curves>(quantity: usize) -> usize {
|
||||
let mut dummy_tape = Tape::new(usize::MAX);
|
||||
for _ in dummy_tape.read_points_with_common_dlog::<C>(quantity) {}
|
||||
dummy_tape.current_position
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct PedersenCommitmentTape {
|
||||
pedersen_commitments: usize,
|
||||
}
|
||||
impl PedersenCommitmentTape {
|
||||
pub(super) fn new() -> Self {
|
||||
Self { pedersen_commitments: 0 }
|
||||
}
|
||||
/// Allocate a Pedersen commitment.
|
||||
pub(super) fn allocate_pedersen_commitment(&mut self) -> Variable {
|
||||
let res = Variable::V(self.pedersen_commitments);
|
||||
self.pedersen_commitments += 1;
|
||||
res
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user