Browse Source

Implement Nova's NIFS.Verify circuits (with CycleFold) (#11)

* Implement Nova's NIFS.Verify circuits (with CycleFold)

- Add circuit for NIFS.Verify on the main curve to check the folded `u`
  & `x`
- Add circuit for NIFS.Verify on the CycleFold's auxiliary curve to
  check the folded `cm(E)` & `cm(W)`
- Add transcript.get_challenge_nbits
- Add tests for utils::vec.rs

* replace bls12-377 & bw6-761 by pallas & vesta curves (only affects tests)

We will use pallas & vesta curves (for tests only, the non-tests code
uses generics) for the logic that does not require pairings, and while
Grumpkin is not available
(https://github.com/privacy-scaling-explorations/folding-schemes/issues/12).

* update links to papers to markdown style
update-nifs-interface
arnaucube 1 year ago
committed by GitHub
parent
commit
d9887af535
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 480 additions and 87 deletions
  1. +2
    -2
      Cargo.toml
  2. +10
    -10
      src/ccs/mod.rs
  3. +1
    -20
      src/ccs/r1cs.rs
  4. +1
    -0
      src/constants.rs
  5. +23
    -20
      src/folding/circuits/cyclefold.rs
  6. +2
    -1
      src/folding/circuits/mod.rs
  7. +273
    -0
      src/folding/nova/circuits.rs
  8. +2
    -0
      src/folding/nova/mod.rs
  9. +27
    -19
      src/folding/nova/nifs.rs
  10. +1
    -0
      src/lib.rs
  11. +7
    -7
      src/pedersen.rs
  12. +2
    -0
      src/transcript/mod.rs
  13. +64
    -5
      src/transcript/poseidon.rs
  14. +1
    -1
      src/utils/espresso/sum_check/verifier.rs
  15. +1
    -1
      src/utils/espresso/virtual_polynomial.rs
  16. +63
    -1
      src/utils/vec.rs

+ 2
- 2
Cargo.toml

@ -21,8 +21,8 @@ espresso_transcript = {git="https://github.com/EspressoSystems/hyperplonk", pack
[dev-dependencies]
ark-bls12-377 = {version="0.4.0", features=["r1cs"]}
ark-bw6-761 = {version="0.4.0"}
ark-pallas = {version="0.4.0", features=["r1cs"]}
ark-vesta = {version="0.4.0"}
[features]
default = ["parallel"]

+ 10
- 10
src/ccs/mod.rs

@ -10,12 +10,12 @@ pub mod r1cs;
use r1cs::R1CS;
/// CCS represents the Customizable Constraint Systems structure defined in
/// https://eprint.iacr.org/2023/552
/// the [CCS paper](https://eprint.iacr.org/2023/552)
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CCS<C: CurveGroup> {
/// m: number of columns in M_i (such that M_i \in F^{m, n})
/// m: number of rows in M_i (such that M_i \in F^{m, n})
pub m: usize,
/// n = |z|, number of rows in M_i
/// n = |z|, number of cols in M_i
pub n: usize,
/// l = |io|, size of public input/output
pub l: usize,
@ -73,13 +73,13 @@ impl CCS {
}
impl<C: CurveGroup> CCS<C> {
pub fn from_r1cs(r1cs: R1CS<C::ScalarField>, io_len: usize) -> Self {
let m = r1cs.A.n_cols;
let n = r1cs.A.n_rows;
pub fn from_r1cs(r1cs: R1CS<C::ScalarField>) -> Self {
let m = r1cs.A.n_rows;
let n = r1cs.A.n_cols;
CCS {
m,
n,
l: io_len,
l: r1cs.l,
s: log2(m) as usize,
s_prime: log2(n) as usize,
t: 3,
@ -105,17 +105,17 @@ impl CCS {
mod tests {
use super::*;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use ark_bls12_377::G1Projective;
use ark_pallas::Projective;
pub fn get_test_ccs<C: CurveGroup>() -> CCS<C> {
let r1cs = get_test_r1cs::<C::ScalarField>();
CCS::<C>::from_r1cs(r1cs, 1)
CCS::<C>::from_r1cs(r1cs)
}
/// Test that a basic CCS relation can be satisfied
#[test]
fn test_ccs_relation() {
let ccs = get_test_ccs::<G1Projective>();
let ccs = get_test_ccs::<Projective>();
let z = get_test_z(3);
ccs.check_relation(&z).unwrap();

+ 1
- 20
src/ccs/r1cs.rs

@ -19,24 +19,7 @@ impl R1CS {
#[cfg(test)]
pub mod tests {
use super::*;
pub fn to_F_matrix<F: PrimeField>(M: Vec<Vec<usize>>) -> Vec<Vec<F>> {
let mut R: Vec<Vec<F>> = vec![Vec::new(); M.len()];
for i in 0..M.len() {
R[i] = vec![F::zero(); M[i].len()];
for j in 0..M[i].len() {
R[i][j] = F::from(M[i][j] as u64);
}
}
R
}
pub fn to_F_vec<F: PrimeField>(z: Vec<usize>) -> Vec<F> {
let mut r: Vec<F> = vec![F::zero(); z.len()];
for i in 0..z.len() {
r[i] = F::from(z[i] as u64);
}
r
}
use crate::utils::vec::tests::{to_F_matrix, to_F_vec};
pub fn get_test_r1cs<F: PrimeField>() -> R1CS<F> {
// R1CS for: x^3 + x + 5 = y (example from article
@ -72,8 +55,6 @@ pub mod tests {
input * input, // x^2
input * input * input, // x^2 * x
input * input * input + input, // x^3 + x
0, // pad to pow of 2
0,
])
}
}

+ 1
- 0
src/constants.rs

@ -0,0 +1 @@
pub const N_BITS_CHALLENGE: usize = 250;

+ 23
- 20
src/folding/circuits/cyclefold.rs

@ -1,26 +1,28 @@
/// Implements the C_{EC} circuit described in CycleFold paper https://eprint.iacr.org/2023/1192.pdf
/// Implements the C_{EC} circuit described in [CycleFold paper](https://eprint.iacr.org/2023/1192.pdf)
use ark_ec::CurveGroup;
use ark_r1cs_std::{fields::nonnative::NonNativeFieldVar, prelude::CurveVar, ToBitsGadget};
use ark_r1cs_std::{boolean::Boolean, prelude::CurveVar};
use ark_relations::r1cs::SynthesisError;
use core::marker::PhantomData;
use super::ConstraintF;
use super::CF;
/// ECRLC implements gadget that checks the Elliptic Curve points RandomLinearCombination described
/// in CycleFold (https://eprint.iacr.org/2023/1192.pdf).
/// in [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
#[derive(Debug)]
pub struct ECRLC<C: CurveGroup, GC: CurveVar<C, ConstraintF<C>>> {
pub struct ECRLC<C: CurveGroup, GC: CurveVar<C, CF<C>>> {
_c: PhantomData<C>,
_gc: PhantomData<GC>,
}
impl<C: CurveGroup, GC: CurveVar<C, ConstraintF<C>>> ECRLC<C, GC> {
impl<C: CurveGroup, GC: CurveVar<C, CF<C>>> ECRLC<C, GC> {
pub fn check(
r: NonNativeFieldVar<C::ScalarField, ConstraintF<C>>,
// get r in bits format, so it can be reused across many instances of ECRLC gadget,
// reducing the number of constraints needed
r_bits: Vec<Boolean<CF<C>>>,
p1: GC,
p2: GC,
p3: GC,
) -> Result<(), SynthesisError> {
p3.enforce_equal(&(p1 + p2.scalar_mul_le(r.to_bits_le()?.iter())?))?;
p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?;
Ok(())
}
}
@ -28,35 +30,36 @@ impl>> ECRLC {
#[cfg(test)]
mod test {
use super::*;
use ark_bls12_377::{constraints::G1Var, Fq, Fr, G1Projective};
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::alloc::AllocVar;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
use std::ops::Mul;
/// Let Curve1=bls12-377::G1 and Curve2=bw6-761::G1. Here we have our constraint system will
/// work over Curve2::Fr = bw6-761::Fr (=bls12-377::Fq), thus our points are P_i \in Curve1
/// (=bls12-377).
/// Let Curve1=pallas and Curve2=vesta. Here our constraints system will work over Curve2::Fr =
/// vesta::Fr (=pallas::Fq), thus our points are P_i \in Curve1 (=pasta).
#[test]
fn test_ecrlc_check() {
let mut rng = ark_std::test_rng();
let r = Fr::rand(&mut rng);
let p1 = G1Projective::rand(&mut rng);
let p2 = G1Projective::rand(&mut rng);
let p1 = Projective::rand(&mut rng);
let p2 = Projective::rand(&mut rng);
let p3 = p1 + p2.mul(r);
let cs = ConstraintSystem::<Fq>::new_ref(); // CS over Curve2::Fr = Curve1::Fq
// prepare circuit inputs
let rVar = NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r)).unwrap();
let p1Var = G1Var::new_witness(cs.clone(), || Ok(p1)).unwrap();
let p2Var = G1Var::new_witness(cs.clone(), || Ok(p2)).unwrap();
let p3Var = G1Var::new_witness(cs.clone(), || Ok(p3)).unwrap();
let rbitsVar: Vec<Boolean<Fq>> =
Vec::new_witness(cs.clone(), || Ok(r.into_bigint().to_bits_le())).unwrap();
let p1Var = GVar::new_witness(cs.clone(), || Ok(p1)).unwrap();
let p2Var = GVar::new_witness(cs.clone(), || Ok(p2)).unwrap();
let p3Var = GVar::new_witness(cs.clone(), || Ok(p3)).unwrap();
// check ECRLC circuit
ECRLC::<G1Projective, G1Var>::check(rVar, p1Var, p2Var, p3Var).unwrap();
ECRLC::<Projective, GVar>::check(rbitsVar, p1Var, p2Var, p3Var).unwrap();
assert!(cs.is_satisfied().unwrap());
// dbg!(cs.num_constraints());
}
}

+ 2
- 1
src/folding/circuits/mod.rs

@ -4,4 +4,5 @@ use ark_ff::Field;
pub mod cyclefold;
pub type ConstraintF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
// CF represents the constraints field
pub type CF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;

+ 273
- 0
src/folding/nova/circuits.rs

@ -0,0 +1,273 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::Field;
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
eq::EqGadget,
fields::fp::FpVar,
groups::GroupOpsBounds,
prelude::CurveVar,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use core::{borrow::Borrow, marker::PhantomData};
use super::CommittedInstance;
use crate::folding::circuits::cyclefold::ECRLC;
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr.
pub type CF1<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
/// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq.
pub type CF2<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
/// CommittedInstance on E1 contains the u and x values which are folded on the main Nova
/// constraints field (E1::Fr).
#[derive(Debug, Clone)]
pub struct CommittedInstanceE1Var<C: CurveGroup> {
_c: PhantomData<C>,
u: FpVar<C::ScalarField>,
x: Vec<FpVar<C::ScalarField>>,
}
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceE1Var<C>
where
C: CurveGroup,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
cs: impl Into<Namespace<CF1<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let u = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
let x: Vec<FpVar<C::ScalarField>> =
Vec::new_variable(cs, || Ok(val.borrow().x.clone()), mode)?;
Ok(Self {
_c: PhantomData,
u,
x,
})
})
}
}
/// CommittedInstance on E2 contains the commitments to E and W, which are folded on the auxiliary
/// curve constraints field (E2::Fr = E1::Fq).
pub struct CommittedInstanceE2Var<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
_c: PhantomData<C>,
cmE: GC,
cmW: GC,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceE2Var<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
Ok(Self {
_c: PhantomData,
cmE,
cmW,
})
})
}
}
/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier
/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are
/// delegated to the NIFSCycleFoldGadget.
pub struct NIFSGadget<C: CurveGroup> {
_c: PhantomData<C>,
}
impl<C: CurveGroup> NIFSGadget<C>
where
C: CurveGroup,
{
/// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to
/// the CycleFold circuit.
pub fn verify(
r: FpVar<CF1<C>>,
ci1: CommittedInstanceE1Var<C>,
ci2: CommittedInstanceE1Var<C>,
ci3: CommittedInstanceE1Var<C>,
) -> Result<(), SynthesisError> {
// ensure that: ci3.u == ci1.u + r * ci2.u
ci3.u.enforce_equal(&(ci1.u + r.clone() * ci2.u))?;
// ensure that: ci3.x == ci1.x + r * ci2.x
let x_rlc = ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| a + &r * &b)
.collect::<Vec<FpVar<CF1<C>>>>();
x_rlc.enforce_equal(&ci3.x)?;
Ok(())
}
}
/// NIFSCycleFoldGadget performs the Nova NIFS.V elliptic curve points relation checks in the other
/// curve following [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
pub struct NIFSCycleFoldGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
_c: PhantomData<C>,
_gc: PhantomData<GC>,
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSCycleFoldGadget<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
pub fn verify(
r_bits: Vec<Boolean<CF2<C>>>,
cmT: GC,
ci1: CommittedInstanceE2Var<C, GC>,
ci2: CommittedInstanceE2Var<C, GC>,
ci3: CommittedInstanceE2Var<C, GC>,
) -> Result<(), SynthesisError> {
// cm(E) check: ci3.cmE == ci1.cmE + r * cmT + r^2 * ci2.cmE
ci3.cmE.enforce_equal(
&((ci2.cmE.scalar_mul_le(r_bits.iter())? + cmT).scalar_mul_le(r_bits.iter())?
+ ci1.cmE),
)?;
// cm(W) check: ci3.cmW == ci1.cmW + r * ci2.cmW
ECRLC::<C, GC>::check(r_bits, ci1.cmW, ci2.cmW, ci3.cmW)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::folding::nova::{nifs::NIFS, Witness};
use crate::pedersen::Pedersen;
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
use crate::transcript::Transcript;
#[test]
fn test_committed_instance_var() {
let mut rng = ark_std::test_rng();
let ci = CommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
x: vec![Fr::rand(&mut rng); 1],
};
let cs = ConstraintSystem::<Fr>::new_ref();
let ciVar =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci.clone()))
.unwrap();
assert_eq!(ciVar.u.value().unwrap(), ci.u);
assert_eq!(ciVar.x.value().unwrap(), ci.x);
// check the instantiation of the CycleFold side:
let cs = ConstraintSystem::<Fq>::new_ref();
let ciVar =
CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs.clone(), || Ok(ci.clone()))
.unwrap();
assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
}
#[test]
fn test_nifs_gadget() {
let r1cs = get_test_r1cs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
let (w1, x1) = r1cs.split_z(&z1);
let (w2, x2) = r1cs.split_z(&z2);
let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
let mut rng = ark_std::test_rng();
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
// compute committed instances
let ci1 = w1.commit(&pedersen_params, x1.clone());
let ci2 = w2.commit(&pedersen_params, x2.clone());
// get challenge from transcript
let config = poseidon_test_config::<Fr>();
let mut tr = PoseidonTranscript::<Projective>::new(&config);
let r_bits = tr.get_challenge_nbits(128);
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
let (_w3, ci3, _T, cmT) =
NIFS::<Projective>::prove(&pedersen_params, r_Fr, &r1cs, &w1, &ci1, &w2, &ci2);
let cs = ConstraintSystem::<Fr>::new_ref();
let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
let ci1Var =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci1.clone()))
.unwrap();
let ci2Var =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci2.clone()))
.unwrap();
let ci3Var =
CommittedInstanceE1Var::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
.unwrap();
NIFSGadget::<Projective>::verify(
rVar.clone(),
ci1Var.clone(),
ci2Var.clone(),
ci3Var.clone(),
)
.unwrap();
assert!(cs.is_satisfied().unwrap());
// cs_CC is the Constraint System on the Curve Cycle auxiliary curve constraints field
// (E2::Fr)
let cs_CC = ConstraintSystem::<Fq>::new_ref();
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs_CC.clone(), || Ok(r_bits)).unwrap();
let cmTVar = GVar::new_witness(cs_CC.clone(), || Ok(cmT)).unwrap();
let ci1Var = CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci1.clone())
})
.unwrap();
let ci2Var = CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci2.clone())
})
.unwrap();
let ci3Var = CommittedInstanceE2Var::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci3.clone())
})
.unwrap();
NIFSCycleFoldGadget::<Projective, GVar>::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var)
.unwrap();
assert!(cs_CC.is_satisfied().unwrap());
}
}

+ 2
- 0
src/folding/nova/mod.rs

@ -5,6 +5,7 @@ use ark_std::{One, Zero};
use crate::pedersen::{Params as PedersenParams, Pedersen};
pub mod circuits;
pub mod nifs;
#[derive(Debug, Clone, Eq, PartialEq)]
@ -14,6 +15,7 @@ pub struct CommittedInstance {
pub cmW: C,
pub x: Vec<C::ScalarField>,
}
impl<C: CurveGroup> CommittedInstance<C> {
pub fn empty() -> Self {
CommittedInstance {

+ 27
- 19
src/folding/nova/nifs.rs

@ -56,7 +56,8 @@ where
&vec_scalar_mul(&w2.E, &r2),
);
let rE = w1.rE + r * rT + r2 * w2.rE;
let W = vec_add(&w1.W, &vec_scalar_mul(&w2.W, &r));
let W: Vec<C::ScalarField> = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect();
let rW = w1.rW + r * w2.rW;
Witness::<C> { E, rE, W, rW }
}
@ -68,11 +69,15 @@ where
cmT: &C,
) -> CommittedInstance<C> {
let r2 = r * r;
let cmE = ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2);
let u = ci1.u + r * ci2.u;
let cmW = ci1.cmW + ci2.cmW.mul(r);
let x = vec_add(&ci1.x, &vec_scalar_mul(&ci2.x, &r));
let x = ci1
.x
.iter()
.zip(&ci2.x)
.map(|(a, b)| *a + (r * b))
.collect::<Vec<C::ScalarField>>();
CommittedInstance::<C> { cmE, u, cmW, x }
}
@ -81,6 +86,7 @@ where
#[allow(clippy::type_complexity)]
pub fn prove(
pedersen_params: &PedersenParams<C>,
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
r: C::ScalarField,
r1cs: &R1CS<C::ScalarField>,
w1: &Witness<C>,
@ -107,6 +113,7 @@ where
// NIFS.V
pub fn verify(
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
r: C::ScalarField,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
@ -179,8 +186,8 @@ mod tests {
use super::*;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
use ark_bls12_377::{Fr, G1Projective};
use ark_ff::PrimeField;
use ark_pallas::{Fr, Projective};
use ark_std::UniformRand;
pub fn check_relaxed_r1cs<F: PrimeField>(r1cs: &R1CS<F>, z: Vec<F>, u: F, E: &[F]) {
@ -199,23 +206,24 @@ mod tests {
let (w1, x1) = r1cs.split_z(&z1);
let (w2, x2) = r1cs.split_z(&z2);
let w1 = Witness::<G1Projective>::new(w1.clone(), r1cs.A.n_cols);
let w2 = Witness::<G1Projective>::new(w2.clone(), r1cs.A.n_cols);
let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
let mut rng = ark_std::test_rng();
let pedersen_params = Pedersen::<G1Projective>::new_params(&mut rng, r1cs.A.n_cols);
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
// compute committed instances
let ci1 = w1.commit(&pedersen_params, x1.clone());
let ci2 = w2.commit(&pedersen_params, x2.clone());
// NIFS.P
let (w3, _, T, cmT) =
NIFS::<G1Projective>::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2);
NIFS::<Projective>::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2);
// NIFS.V
let ci3 = NIFS::<G1Projective>::verify(r, &ci1, &ci2, &cmT);
let ci3 = NIFS::<Projective>::verify(r, &ci1, &ci2, &cmT);
// naive check that the folded witness satisfies the relaxed r1cs
let z3: Vec<Fr> = [vec![ci3.u], ci3.x.to_vec(), w3.W.to_vec()].concat();
@ -234,18 +242,18 @@ mod tests {
assert_eq!(ci3_expected.cmW, ci3.cmW);
// NIFS.Verify_Folded_Instance:
assert!(NIFS::<G1Projective>::verify_folded_instance(
assert!(NIFS::<Projective>::verify_folded_instance(
r, &ci1, &ci2, &ci3, &cmT
));
let poseidon_config = poseidon_test_config::<Fr>();
// init Prover's transcript
let mut transcript_p = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
// init Verifier's transcript
let mut transcript_v = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
// check openings of ci3.cmE, ci3.cmW and cmT
let (cmE_proof, cmW_proof, cmT_proof) = NIFS::<G1Projective>::open_commitments(
let (cmE_proof, cmW_proof, cmT_proof) = NIFS::<Projective>::open_commitments(
&mut transcript_p,
&pedersen_params,
&w3,
@ -253,7 +261,7 @@ mod tests {
T,
&cmT,
);
let v = NIFS::<G1Projective>::verify_commitments(
let v = NIFS::<Projective>::verify_commitments(
&mut transcript_v,
&pedersen_params,
ci3,
@ -272,10 +280,10 @@ mod tests {
let (w, x) = r1cs.split_z(&z);
let mut rng = ark_std::test_rng();
let pedersen_params = Pedersen::<G1Projective>::new_params(&mut rng, r1cs.A.n_cols);
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
// prepare the running instance
let mut running_instance_w = Witness::<G1Projective>::new(w.clone(), r1cs.A.n_cols);
let mut running_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
let mut running_committed_instance = running_instance_w.commit(&pedersen_params, x);
check_relaxed_r1cs(
&r1cs,
@ -289,7 +297,7 @@ mod tests {
// prepare the incomming instance
let incomming_instance_z = get_test_z(i + 4);
let (w, x) = r1cs.split_z(&incomming_instance_z);
let incomming_instance_w = Witness::<G1Projective>::new(w.clone(), r1cs.A.n_cols);
let incomming_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
let incomming_committed_instance = incomming_instance_w.commit(&pedersen_params, x);
check_relaxed_r1cs(
&r1cs,
@ -301,7 +309,7 @@ mod tests {
let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
// NIFS.P
let (folded_w, _, _, cmT) = NIFS::<G1Projective>::prove(
let (folded_w, _, _, cmT) = NIFS::<Projective>::prove(
&pedersen_params,
r,
&r1cs,
@ -312,7 +320,7 @@ mod tests {
);
// NIFS.V
let folded_committed_instance = NIFS::<G1Projective>::verify(
let folded_committed_instance = NIFS::<Projective>::verify(
r,
&running_committed_instance,
&incomming_committed_instance,

+ 1
- 0
src/lib.rs

@ -9,6 +9,7 @@ use thiserror::Error;
pub mod transcript;
use transcript::Transcript;
pub mod ccs;
pub mod constants;
pub mod folding;
pub mod pedersen;
pub mod utils;

+ 7
- 7
src/pedersen.rs

@ -101,7 +101,7 @@ mod tests {
use super::*;
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
use ark_bls12_377::{Fr, G1Projective};
use ark_pallas::{Fr, Projective};
#[test]
fn test_pedersen_vector() {
@ -109,19 +109,19 @@ mod tests {
const n: usize = 10;
// setup params
let params = Pedersen::<G1Projective>::new_params(&mut rng, n);
let params = Pedersen::<Projective>::new_params(&mut rng, n);
let poseidon_config = poseidon_test_config::<Fr>();
// init Prover's transcript
let mut transcript_p = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
// init Verifier's transcript
let mut transcript_v = PoseidonTranscript::<G1Projective>::new(&poseidon_config);
let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
let v: Vec<Fr> = vec![Fr::rand(&mut rng); n];
let r: Fr = Fr::rand(&mut rng);
let cm = Pedersen::<G1Projective>::commit(&params, &v, &r);
let proof = Pedersen::<G1Projective>::prove(&params, &mut transcript_p, &cm, &v, &r);
let v = Pedersen::<G1Projective>::verify(&params, &mut transcript_v, cm, proof);
let cm = Pedersen::<Projective>::commit(&params, &v, &r);
let proof = Pedersen::<Projective>::prove(&params, &mut transcript_p, &cm, &v, &r);
let v = Pedersen::<Projective>::verify(&params, &mut transcript_v, cm, proof);
assert!(v);
}
}

+ 2
- 0
src/transcript/mod.rs

@ -11,5 +11,7 @@ pub trait Transcript {
fn absorb_vec(&mut self, v: &[C::ScalarField]);
fn absorb_point(&mut self, v: &C);
fn get_challenge(&mut self) -> C::ScalarField;
/// get_challenge_nbits returns a field element of size nbits
fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool>;
fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField>;
}

+ 64
- 5
src/transcript/poseidon.rs

@ -5,7 +5,7 @@ use ark_crypto_primitives::sponge::{
};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, Field, PrimeField};
use ark_r1cs_std::fields::fp::FpVar;
use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar};
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
use crate::transcript::Transcript;
@ -42,6 +42,9 @@ where
self.sponge.absorb(&c[0]);
c[0]
}
fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool> {
self.sponge.squeeze_bits(nbits)
}
fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField> {
let c = self.sponge.squeeze_field_elements(n);
self.sponge.absorb(&c);
@ -92,6 +95,12 @@ impl PoseidonTranscriptVar {
self.sponge.absorb(&c[0])?;
Ok(c[0].clone())
}
/// returns the bit representation of the challenge, we use its output in-circuit for the
/// `GC.scalar_mul_le` method.
pub fn get_challenge_nbits(&mut self, nbits: usize) -> Result<Vec<Boolean<F>>, SynthesisError> {
self.sponge.squeeze_bits(nbits)
}
pub fn get_challenges(&mut self, n: usize) -> Result<Vec<FpVar<F>>, SynthesisError> {
let c = self.sponge.squeeze_field_elements(n)?;
self.sponge.absorb(&c)?;
@ -102,10 +111,12 @@ impl PoseidonTranscriptVar {
#[cfg(test)]
pub mod tests {
use super::*;
use ark_bls12_377::{Fr, G1Projective};
use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds;
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar};
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_vesta::Projective as E2Projective;
use std::ops::Mul;
/// WARNING the method poseidon_test_config is for tests only
#[cfg(test)]
@ -135,10 +146,10 @@ pub mod tests {
}
#[test]
fn test_transcript_and_transcriptvar() {
fn test_transcript_and_transcriptvar_get_challenge() {
// use 'native' transcript
let config = poseidon_test_config::<Fr>();
let mut tr = PoseidonTranscript::<G1Projective>::new(&config);
let mut tr = PoseidonTranscript::<Projective>::new(&config);
tr.absorb(&Fr::from(42_u32));
let c = tr.get_challenge();
@ -152,4 +163,52 @@ pub mod tests {
// assert that native & gadget transcripts return the same challenge
assert_eq!(c, c_var.value().unwrap());
}
#[test]
fn test_transcript_and_transcriptvar_nbits() {
let nbits = crate::constants::N_BITS_CHALLENGE;
// use 'native' transcript
let config = poseidon_test_config::<Fq>();
let mut tr = PoseidonTranscript::<E2Projective>::new(&config);
tr.absorb(&Fq::from(42_u32));
// get challenge from native transcript
let c_bits = tr.get_challenge_nbits(nbits);
// use 'gadget' transcript
let cs = ConstraintSystem::<Fq>::new_ref();
let mut tr_var = PoseidonTranscriptVar::<Fq>::new(cs.clone(), &config);
let v = FpVar::<Fq>::new_witness(cs.clone(), || Ok(Fq::from(42_u32))).unwrap();
tr_var.absorb(v).unwrap();
// get challenge from circuit transcript
let c_var = tr_var.get_challenge_nbits(nbits).unwrap();
let P = Projective::generator();
let PVar = GVar::new_witness(cs.clone(), || Ok(P)).unwrap();
// multiply point P by the challenge in different formats, to ensure that we get the same
// result natively and in-circuit
// native c*P
let c_Fr = Fr::from_bigint(BigInteger::from_bits_le(&c_bits)).unwrap();
let cP_native = P.mul(c_Fr);
// native c*P using mul_bits_be (notice the .rev to convert the LE to BE)
let cP_native_bits = P.mul_bits_be(c_bits.into_iter().rev());
// in-circuit c*P using scalar_mul_le
let cPVar = PVar.scalar_mul_le(c_var.iter()).unwrap();
// check that they are equal
assert_eq!(
cP_native.into_affine(),
cPVar.value().unwrap().into_affine()
);
assert_eq!(
cP_native_bits.into_affine(),
cPVar.value().unwrap().into_affine()
);
}
}

+ 1
- 1
src/utils/espresso/sum_check/verifier.rs

@ -321,7 +321,7 @@ fn u64_factorial(a: usize) -> u64 {
#[cfg(test)]
mod test {
use super::interpolate_uni_poly;
use ark_bls12_377::Fr;
use ark_pallas::Fr;
use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial};
use ark_std::{vec::Vec, UniformRand};
use espresso_subroutines::poly_iop::prelude::PolyIOPErrors;

+ 1
- 1
src/utils/espresso/virtual_polynomial.rs

@ -412,8 +412,8 @@ pub fn bit_decompose(input: u64, num_var: usize) -> Vec {
mod test {
use super::*;
use crate::utils::multilinear_polynomial::tests::random_mle_list;
use ark_bls12_377::Fr;
use ark_ff::UniformRand;
use ark_pallas::Fr;
use ark_std::{
rand::{Rng, RngCore},
test_rng,

+ 63
- 1
src/utils/vec.rs

@ -74,7 +74,7 @@ pub fn mat_vec_mul(M: &Vec>, z: &[F]) -> Vec {
}
pub fn mat_vec_mul_sparse<F: PrimeField>(matrix: &SparseMatrix<F>, vector: &[F]) -> Vec<F> {
let mut res = vec![F::zero(); matrix.n_cols];
let mut res = vec![F::zero(); matrix.n_rows];
for &(row, col, value) in matrix.coeffs.iter() {
res[row] += value * vector[col];
}
@ -85,3 +85,65 @@ pub fn hadamard(a: &[F], b: &[F]) -> Vec {
assert_eq!(a.len(), b.len());
cfg_iter!(a).zip(b).map(|(a, b)| *a * b).collect()
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_pallas::Fr;
pub fn to_F_matrix<F: PrimeField>(M: Vec<Vec<usize>>) -> Vec<Vec<F>> {
let mut R: Vec<Vec<F>> = vec![Vec::new(); M.len()];
for i in 0..M.len() {
R[i] = vec![F::zero(); M[i].len()];
for j in 0..M[i].len() {
R[i][j] = F::from(M[i][j] as u64);
}
}
R
}
pub fn to_F_vec<F: PrimeField>(z: Vec<usize>) -> Vec<F> {
let mut r: Vec<F> = vec![F::zero(); z.len()];
for i in 0..z.len() {
r[i] = F::from(z[i] as u64);
}
r
}
#[test]
fn test_mat_vec_mul() {
let A = to_F_matrix::<Fr>(vec![
vec![0, 1, 0, 0, 0, 0],
vec![0, 0, 0, 1, 0, 0],
vec![0, 1, 0, 0, 1, 0],
vec![5, 0, 0, 0, 0, 1],
]);
let z = to_F_vec(vec![1, 3, 35, 9, 27, 30]);
assert_eq!(mat_vec_mul(&A, &z), to_F_vec(vec![3, 9, 30, 35]));
assert_eq!(
mat_vec_mul_sparse(&dense_matrix_to_sparse(A), &z),
to_F_vec(vec![3, 9, 30, 35])
);
let A = to_F_matrix::<Fr>(vec![vec![2, 3, 4, 5], vec![4, 8, 12, 14], vec![9, 8, 7, 6]]);
let v = to_F_vec(vec![19, 55, 50, 3]);
assert_eq!(mat_vec_mul(&A, &v), to_F_vec(vec![418, 1158, 979]));
assert_eq!(
mat_vec_mul_sparse(&dense_matrix_to_sparse(A), &v),
to_F_vec(vec![418, 1158, 979])
);
}
#[test]
fn test_hadamard_product() {
let a = to_F_vec::<Fr>(vec![1, 2, 3, 4, 5, 6]);
let b = to_F_vec(vec![7, 8, 9, 10, 11, 12]);
assert_eq!(hadamard(&a, &b), to_F_vec(vec![7, 16, 27, 40, 55, 72]));
}
#[test]
fn test_vec_add() {
let a: Vec<Fr> = to_F_vec::<Fr>(vec![1, 2, 3, 4, 5, 6]);
let b: Vec<Fr> = to_F_vec(vec![7, 8, 9, 10, 11, 12]);
assert_eq!(vec_add(&a, &b), to_F_vec(vec![8, 10, 12, 14, 16, 18]));
}
}

Loading…
Cancel
Save