Add IPA commitment scheme and the respective circuit verifier gadget (#72)

* Add IPA commitment native implementation

* Add IPA Gadget verifier

* polish Pedersen & IPA, add blind bool param to IPA

* Optimize IPA gadget constraints (and native):

- optimize <s,b> computation from linear to log time
- optimize s computation from k*2^k to k*(2^k)/2

* add small optimization: delegate u_i^-1 to prover and just check u_i*u_i^-1==1 in verifier circuit

* IPA polish and document

* Add 'BLIND' parameter to CommitmentProver trait (and to Pedersen and KZG impls). Fit IPA into CommitmentProver trait.

* rename 'BLIND' to 'H' (hiding) in commitment

* IPA: rm u_invs from Proof and compute them incircuit

* Update IPA's build_s & gadget to use Halo2 approach following @han0110 's suggestion.

This reduced further the amount of constraints needed.
- for k=4: -9k constraints (-7%)
- for k=8: -473k constr (-31%)
- for k=9: -1123k constr (-35%)
- for k=10: -2578k constr (-39%)
And now IPA verification (without amortizing) is very close to Pedersen
verification (in-circuits).

* rm dbg!(cs.num_constraints()) from multiple tests

* IPA::prove remove intermediate v_lo,v_hi vectors, add doc to build_s_gadget

* move powers_of into utils/mod.rs, update iters to cfg_iter
This commit is contained in:
arnaucube
2024-03-01 09:52:07 +01:00
committed by GitHub
parent 9159c5c84c
commit b25037e34c
18 changed files with 796 additions and 96 deletions

View File

@@ -1,8 +1,12 @@
use ark_ec::CurveGroup;
use ark_ff::Field;
use ark_r1cs_std::{boolean::Boolean, groups::GroupOpsBounds, prelude::CurveVar};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
use ark_relations::r1cs::SynthesisError;
use ark_std::{rand::Rng, UniformRand};
use ark_std::Zero;
use ark_std::{
rand::{Rng, RngCore},
UniformRand,
};
use core::marker::PhantomData;
use super::CommitmentProver;
@@ -24,25 +28,24 @@ pub struct Params<C: CurveGroup> {
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Pedersen<C: CurveGroup> {
pub struct Pedersen<C: CurveGroup, const H: bool = false> {
_c: PhantomData<C>,
}
impl<C: CurveGroup> Pedersen<C> {
impl<C: CurveGroup, const H: bool> Pedersen<C, H> {
pub fn new_params<R: Rng>(rng: &mut R, max: usize) -> Params<C> {
let generators: Vec<C::Affine> = std::iter::repeat_with(|| C::Affine::rand(rng))
.take(max.next_power_of_two())
.collect();
let params: Params<C> = Params::<C> {
Params::<C> {
h: C::rand(rng),
generators,
};
params
}
}
}
// implement the CommitmentProver trait for Pedersen
impl<C: CurveGroup> CommitmentProver<C> for Pedersen<C> {
impl<C: CurveGroup, const H: bool> CommitmentProver<C, H> for Pedersen<C, H> {
type Params = Params<C>;
type Proof = Proof<C>;
fn commit(
@@ -55,6 +58,9 @@ impl<C: CurveGroup> CommitmentProver<C> for Pedersen<C> {
}
// h⋅r + <g, v>
// use msm_unchecked because we already ensured at the if that lengths match
if !H {
return Ok(C::msm_unchecked(&params.generators[..v.len()], v));
}
Ok(params.h.mul(r) + C::msm_unchecked(&params.generators[..v.len()], v))
}
@@ -64,6 +70,7 @@ impl<C: CurveGroup> CommitmentProver<C> for Pedersen<C> {
cm: &C,
v: &[C::ScalarField],
r: &C::ScalarField, // blinding factor
_rng: Option<&mut dyn RngCore>,
) -> Result<Self::Proof, Error> {
if params.generators.len() < v.len() {
return Err(Error::PedersenParamsLen(params.generators.len(), v.len()));
@@ -75,7 +82,10 @@ impl<C: CurveGroup> CommitmentProver<C> for Pedersen<C> {
// R = h⋅r_1 + <g, d>
// use msm_unchecked because we already ensured at the if that lengths match
let R: C = params.h.mul(r1) + C::msm_unchecked(&params.generators[..d.len()], &d);
let mut R: C = C::msm_unchecked(&params.generators[..d.len()], &d);
if H {
R += params.h.mul(r1);
}
transcript.absorb_point(&R)?;
let e = transcript.get_challenge();
@@ -83,13 +93,16 @@ impl<C: CurveGroup> CommitmentProver<C> for Pedersen<C> {
// u = d + v⋅e
let u = vec_add(&vec_scalar_mul(v, &e), &d)?;
// r_u = e⋅r + r_1
let r_u = e * r + r1;
let mut r_u = C::ScalarField::zero();
if H {
r_u = e * r + r1;
}
Ok(Self::Proof { R, u, r_u })
}
}
impl<C: CurveGroup> Pedersen<C> {
impl<C: CurveGroup, const H: bool> Pedersen<C, H> {
pub fn verify(
params: &Params<C>,
transcript: &mut impl Transcript<C>,
@@ -112,8 +125,10 @@ impl<C: CurveGroup> Pedersen<C> {
// check that: R + cm⋅e == h⋅r_u + <g, u>
let lhs = proof.R + cm.mul(e);
// use msm_unchecked because we already ensured at the if that lengths match
let rhs = params.h.mul(proof.r_u)
+ C::msm_unchecked(&params.generators[..proof.u.len()], &proof.u);
let mut rhs = C::msm_unchecked(&params.generators[..proof.u.len()], &proof.u);
if H {
rhs += params.h.mul(proof.r_u);
}
if lhs != rhs {
return Err(Error::CommitmentVerificationFail);
}
@@ -123,7 +138,7 @@ impl<C: CurveGroup> Pedersen<C> {
pub type CF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
pub struct PedersenGadget<C, GC>
pub struct PedersenGadget<C, GC, const H: bool = false>
where
C: CurveGroup,
GC: CurveVar<C, CF<C>>,
@@ -133,7 +148,8 @@ where
_gc: PhantomData<GC>,
}
impl<C, GC> PedersenGadget<C, GC>
use ark_r1cs_std::{fields::nonnative::NonNativeFieldVar, ToBitsGadget};
impl<C, GC, const H: bool> PedersenGadget<C, GC, H>
where
C: CurveGroup,
GC: CurveVar<C, CF<C>>,
@@ -144,13 +160,15 @@ where
pub fn commit(
h: GC,
g: Vec<GC>,
v: Vec<Vec<Boolean<CF<C>>>>,
r: Vec<Boolean<CF<C>>>,
v: Vec<NonNativeFieldVar<C::ScalarField, CF<C>>>,
r: NonNativeFieldVar<C::ScalarField, CF<C>>,
) -> Result<GC, SynthesisError> {
let mut res = GC::zero();
res += h.scalar_mul_le(r.iter())?;
if H {
res += h.scalar_mul_le(r.to_bits_le()?.iter())?;
}
for (i, v_i) in v.iter().enumerate() {
res += g[i].scalar_mul_le(v_i.iter())?;
res += g[i].scalar_mul_le(v_i.to_bits_le()?.iter())?;
}
Ok(res)
}
@@ -158,9 +176,8 @@ where
#[cfg(test)]
mod tests {
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, bits::boolean::Boolean, eq::EqGadget};
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
@@ -186,15 +203,20 @@ mod tests {
.collect();
let r: Fr = Fr::rand(&mut rng);
let cm = Pedersen::<Projective>::commit(&params, &v, &r).unwrap();
let proof = Pedersen::<Projective>::prove(&params, &mut transcript_p, &cm, &v, &r).unwrap();
let proof =
Pedersen::<Projective>::prove(&params, &mut transcript_p, &cm, &v, &r, None).unwrap();
Pedersen::<Projective>::verify(&params, &mut transcript_v, cm, proof).unwrap();
}
#[test]
fn test_pedersen_circuit() {
test_pedersen_circuit_opt::<false>();
test_pedersen_circuit_opt::<true>();
}
fn test_pedersen_circuit_opt<const hiding: bool>() {
let mut rng = ark_std::test_rng();
let n: usize = 10;
let n: usize = 16;
// setup params
let params = Pedersen::<Projective>::new_params(&mut rng, n);
@@ -207,17 +229,9 @@ mod tests {
// circuit
let cs = ConstraintSystem::<Fq>::new_ref();
let v_bits: Vec<Vec<bool>> = v.iter().map(|val| val.into_bigint().to_bits_le()).collect();
let r_bits: Vec<bool> = r.into_bigint().to_bits_le();
// prepare inputs
let vVar: Vec<Vec<Boolean<Fq>>> = v_bits
.iter()
.map(|val_bits| {
Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(val_bits.clone())).unwrap()
})
.collect();
let rVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
let vVar = Vec::<NonNativeFieldVar<Fr, Fq>>::new_witness(cs.clone(), || Ok(v)).unwrap();
let rVar = NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r)).unwrap();
let gVar = Vec::<GVar>::new_witness(cs.clone(), || Ok(params.generators)).unwrap();
let hVar = GVar::new_witness(cs.clone(), || Ok(params.h)).unwrap();
let expected_cmVar = GVar::new_witness(cs.clone(), || Ok(cm)).unwrap();