Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CommitmentProver trait, and add KZG prover to it #62

Merged
merged 5 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ark-ff = "^0.4.0"
ark-poly = "^0.4.0"
ark-std = "^0.4.0"
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = ["r1cs", "sponge", "crh"] }
ark-poly-commit = "^0.4.0"
ark-relations = { version = "^0.4.0", default-features = false }
ark-r1cs-std = { default-features = false } # use latest version from the patch
ark-serialize = "^0.4.0"
Expand Down
234 changes: 234 additions & 0 deletions src/commitment/kzg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/// Adaptation of the prover methods and structs from arkworks/poly-commit's KZG10 implementation
/// into the CommitmentProver trait.
///
/// The motivation to do so, is that we want to be able to use KZG / Pedersen for committing to
/// vectors indistinctly, and the arkworks KZG10 implementation contains all the methods under the
/// same trait, which requires the Pairing trait, where the prover does not need access to the
/// Pairing but only to G1.
/// For our case, we want the folding schemes prover to be agnostic to pairings, since in the
/// non-ethereum cases we may use non-pairing-friendly curves with Pedersen commitments, so the
/// trait & types that we use should not depend on the Pairing type for the prover. Therefore, we
/// separate the CommitmentSchemeProver from the setup and verify phases, so the prover can be
/// defined without depending on pairings.
use ark_ec::{pairing::Pairing, CurveGroup, VariableBaseMSM};
use ark_ff::PrimeField;
use ark_poly::{
univariate::{DenseOrSparsePolynomial, DensePolynomial},
DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial,
};
use ark_poly_commit::kzg10::{VerifierKey, KZG10};
use ark_std::rand::Rng;
use ark_std::{borrow::Cow, fmt::Debug};
use ark_std::{One, Zero};
use core::marker::PhantomData;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};

use super::CommitmentProver;
use crate::transcript::Transcript;
use crate::Error;

/// ProverKey defines a similar struct as in ark_poly_commit::kzg10::Powers, but instead of
/// depending on the Pairing trait it depends on the CurveGroup trait.
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct ProverKey<'a, C: CurveGroup> {
/// Group elements of the form `β^i G`, for different values of `i`.
pub powers_of_g: Cow<'a, [C::Affine]>,
}

pub struct KZGSetup<P: Pairing> {
_p: PhantomData<P>,
}
impl<'a, P> KZGSetup<P>
where
P: Pairing,
{
/// setup returns the tuple (ProverKey, VerifierKey). For real world deployments the setup must
/// be computed in the most trustless way possible, usually through a MPC ceremony.
pub fn setup<R: Rng>(rng: &mut R, len: usize) -> (ProverKey<'a, P::G1>, VerifierKey<P>) {
let len = len.next_power_of_two();
let universal_params = KZG10::<P, DensePolynomial<P::ScalarField>>::setup(len, false, rng)
.expect("Setup failed");
let powers_of_g = universal_params.powers_of_g[..=len].to_vec();
let powers = ProverKey::<P::G1> {
powers_of_g: ark_std::borrow::Cow::Owned(powers_of_g),
};
let vk = VerifierKey {
g: universal_params.powers_of_g[0],
gamma_g: universal_params.powers_of_gamma_g[&0],
h: universal_params.h,
beta_h: universal_params.beta_h,
prepared_h: universal_params.prepared_h.clone(),
prepared_beta_h: universal_params.prepared_beta_h.clone(),
};
(powers, vk)
}
}

/// KZGProver implements the CommitmentProver trait for the KZG commitment scheme.
pub struct KZGProver<'a, C: CurveGroup> {
_a: PhantomData<&'a ()>,
_c: PhantomData<C>,
}
impl<'a, C> CommitmentProver<C> for KZGProver<'a, C>
where
C: CurveGroup,
{
type Params = ProverKey<'a, C>;
/// Proof is a tuple containing (evaluation, proof)
type Proof = (C::ScalarField, C);

/// commit implements the CommitmentProver commit interface, adapting the implementation from
/// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L178
/// with the main difference being the removal of the blinding factors and the no-dependancy to
/// the Pairing trait.
CPerezz marked this conversation as resolved.
Show resolved Hide resolved
fn commit(
params: &Self::Params,
v: &[C::ScalarField],
_blind: &C::ScalarField,
) -> Result<C, Error> {
if !_blind.is_zero() {
return Err(Error::NotSupportedYet("blinding factors".to_string()));
}
CPerezz marked this conversation as resolved.
Show resolved Hide resolved

let polynomial = poly_from_vec(v.to_vec())?;
check_degree_is_too_large(polynomial.degree(), params.powers_of_g.len())?;

let (num_leading_zeros, plain_coeffs) =
skip_first_zero_coeffs_and_convert_to_bigints(&polynomial);
let commitment = <C as VariableBaseMSM>::msm_bigint(
&params.powers_of_g[num_leading_zeros..],
&plain_coeffs,
);
Ok(commitment)
}

/// prove implements the CommitmentProver prove interface, adapting the implementation from
/// https://github.com/arkworks-rs/poly-commit/tree/c724fa666e935bbba8db5a1421603bab542e15ab/poly-commit/src/kzg10/mod.rs#L307
/// with the main difference being the removal of the blinding factors and the no-dependancy to
/// the Pairing trait.
fn prove(
params: &Self::Params,
transcript: &mut impl Transcript<C>,
cm: &C,
v: &[C::ScalarField],
_blind: &C::ScalarField,
) -> Result<Self::Proof, Error> {
if !_blind.is_zero() {
return Err(Error::NotSupportedYet("blinding factors".to_string()));
}

let polynomial = poly_from_vec(v.to_vec())?;
check_degree_is_too_large(polynomial.degree(), params.powers_of_g.len())?;

transcript.absorb_point(cm)?;
let challenge = transcript.get_challenge();

// Compute q(x) = (p(x) - p(z)) / (x-z). Observe that this quotient does not change with z
// because p(z) is the remainder term. We can therefore omit p(z) when computing the
// quotient.
let divisor = DensePolynomial::<C::ScalarField>::from_coefficients_vec(vec![
-challenge,
C::ScalarField::one(),
]);
let (witness_poly, remainder_poly) = DenseOrSparsePolynomial::from(&polynomial)
.divide_with_q_and_r(&DenseOrSparsePolynomial::from(&divisor))
// the panic inside `divide_with_q_and_r` should never be reached, since the divisor
// polynomial is constructed right before and is set to not be zero. And the `.unwrap`
// should not give an error.
.unwrap();
let evaluation = remainder_poly[0];

check_degree_is_too_large(witness_poly.degree(), params.powers_of_g.len())?;
let (num_leading_zeros, witness_coeffs) =
skip_first_zero_coeffs_and_convert_to_bigints(&witness_poly);
let proof = <C as VariableBaseMSM>::msm_bigint(
&params.powers_of_g[num_leading_zeros..],
&witness_coeffs,
);

Ok((evaluation, proof))
}
}

/// returns the interpolated polynomial of degree=v.len().next_power_of_two(), which passes through all
/// the given elements of v.
fn poly_from_vec<F: PrimeField>(v: Vec<F>) -> Result<DensePolynomial<F>, Error> {
CPerezz marked this conversation as resolved.
Show resolved Hide resolved
let D = GeneralEvaluationDomain::<F>::new(v.len()).ok_or(Error::NewDomainFail)?;
Ok(Evaluations::from_vec_and_domain(v, D).interpolate())
}

fn check_degree_is_too_large(
degree: usize,
num_powers: usize,
) -> Result<(), ark_poly_commit::error::Error> {
let num_coefficients = degree + 1;
if num_coefficients > num_powers {
Err(ark_poly_commit::error::Error::TooManyCoefficients {
num_coefficients,
num_powers,
})
} else {
Ok(())
}
}

fn skip_first_zero_coeffs_and_convert_to_bigints<F: PrimeField, P: DenseUVPolynomial<F>>(
p: &P,
) -> (usize, Vec<F::BigInt>) {
let mut num_leading_zeros = 0;
while num_leading_zeros < p.coeffs().len() && p.coeffs()[num_leading_zeros].is_zero() {
num_leading_zeros += 1;
}
let coeffs = convert_to_bigints(&p.coeffs()[num_leading_zeros..]);
(num_leading_zeros, coeffs)
}

fn convert_to_bigints<F: PrimeField>(p: &[F]) -> Vec<F::BigInt> {
ark_std::cfg_iter!(p)
.map(|s| s.into_bigint())
.collect::<Vec<_>>()
}

#[cfg(test)]
mod tests {
use ark_bn254::{Bn254, Fr, G1Projective as G1};
use ark_poly_commit::kzg10::{Commitment as KZG10Commitment, Proof as KZG10Proof, KZG10};
use ark_std::{test_rng, UniformRand};

use super::*;
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};

#[test]
fn test_kzg_commitment_scheme() {
let rng = &mut test_rng();
let poseidon_config = poseidon_test_config::<Fr>();
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);

let n = 10;
let (pk, vk): (ProverKey<G1>, VerifierKey<Bn254>) = KZGSetup::<Bn254>::setup(rng, n);

let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect();
let cm = KZGProver::<G1>::commit(&pk, &v, &Fr::zero()).unwrap();

let (eval, proof) =
KZGProver::<G1>::prove(&pk, transcript_p, &cm, &v, &Fr::zero()).unwrap();

// verify the proof:
// get evaluation challenge
transcript_v.absorb_point(&cm).unwrap();
let challenge = transcript_v.get_challenge();
// verify the KZG proof using arkworks method
assert!(KZG10::<Bn254, DensePolynomial<Fr>>::check(
&vk,
&KZG10Commitment(cm.into_affine()),
challenge,
eval,
&KZG10Proof::<Bn254> {
w: proof.into_affine(),
random_v: None,
},
)
.unwrap());
}
}
127 changes: 127 additions & 0 deletions src/commitment/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use ark_ec::CurveGroup;
use ark_std::fmt::Debug;

use crate::transcript::Transcript;
use crate::Error;

pub mod kzg;
pub mod pedersen;

/// CommitmentProver defines the vector commitment scheme prover trait.
pub trait CommitmentProver<C: CurveGroup> {
type Params: Debug;
type Proof: Debug;

fn commit(
params: &Self::Params,
v: &[C::ScalarField],
blind: &C::ScalarField,
) -> Result<C, Error>;
fn prove(
params: &Self::Params,
transcript: &mut impl Transcript<C>,
cm: &C,
v: &[C::ScalarField],
blind: &C::ScalarField,
) -> Result<Self::Proof, Error>;
}

#[cfg(test)]
mod tests {
use super::*;
use ark_bn254::{Bn254, Fr, G1Projective as G1};
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
use ark_poly::univariate::DensePolynomial;
use ark_poly_commit::kzg10::{
Commitment as KZG10Commitment, Proof as KZG10Proof, VerifierKey, KZG10,
};
use ark_std::Zero;
use ark_std::{test_rng, UniformRand};

use super::kzg::{KZGProver, KZGSetup, ProverKey};
use super::pedersen::Pedersen;
use crate::transcript::{
poseidon::{tests::poseidon_test_config, PoseidonTranscript},
Transcript,
};

// Computes the commitment of the two vectors using the given CommitmentProver, then computes
// their random linear combination, and returns it together with the proof of it.
fn commit_rlc_and_prove<C: CurveGroup, CP: CommitmentProver<C>>(
poseidon_config: &PoseidonConfig<C::ScalarField>,
params: &CP::Params,
r: C::ScalarField,
v_1: &[C::ScalarField],
v_2: &[C::ScalarField],
) -> Result<(C, CP::Proof), Error>
where
<C as ark_ec::Group>::ScalarField: Absorb,
{
let cm_1 = CP::commit(params, v_1, &C::ScalarField::zero())?;
let cm_2 = CP::commit(params, v_2, &C::ScalarField::zero())?;

// random linear combination of the commitment and the witness (vector v)
let cm_3 = cm_1 + cm_2.mul(r);
let v_3: Vec<C::ScalarField> = v_1.iter().zip(v_2).map(|(a, b)| *a + (r * b)).collect();

let transcript = &mut PoseidonTranscript::<C>::new(poseidon_config);
let proof = CP::prove(params, transcript, &cm_3, &v_3, &C::ScalarField::zero()).unwrap();

Ok((cm_3, proof))
}

#[test]
fn test_homomorphic_property_using_CommitmentProver_trait() {
let rng = &mut test_rng();
let poseidon_config = poseidon_test_config::<Fr>();
let n: usize = 100;

// set random vector for the test
let v_1: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect();
let v_2: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect();
// set a random challenge for the random linear combination
let r = Fr::rand(rng);

// setup params for Pedersen & KZG
let pedersen_params = Pedersen::<G1>::new_params(rng, n);
let (kzg_pk, kzg_vk): (ProverKey<G1>, VerifierKey<Bn254>) =
KZGSetup::<Bn254>::setup(rng, n);

// Pedersen commit the two vectors and return their random linear combination and proof
let (pedersen_cm, pedersen_proof) = commit_rlc_and_prove::<G1, Pedersen<G1>>(
&poseidon_config,
&pedersen_params,
r,
&v_1,
&v_2,
)
.unwrap();

// KZG commit the two vectors and return their random linear combination and proof
let (kzg_cm, kzg_proof) =
commit_rlc_and_prove::<G1, KZGProver<G1>>(&poseidon_config, &kzg_pk, r, &v_1, &v_2)
.unwrap();

// verify Pedersen
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
Pedersen::<G1>::verify(&pedersen_params, transcript_v, pedersen_cm, pedersen_proof)
.unwrap();

// verify KZG
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
transcript_v.absorb_point(&kzg_cm).unwrap();
let challenge = transcript_v.get_challenge();
// verify the KZG proof using arkworks method
assert!(KZG10::<Bn254, DensePolynomial<Fr>>::check(
CPerezz marked this conversation as resolved.
Show resolved Hide resolved
&kzg_vk,
&KZG10Commitment(kzg_cm.into_affine()),
challenge,
kzg_proof.0, // eval
CPerezz marked this conversation as resolved.
Show resolved Hide resolved
&KZG10Proof::<Bn254> {
w: kzg_proof.1.into_affine(), // proof
random_v: None,
},
)
.unwrap());
}
}
Loading
Loading