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
jump to
@@ -21,8 +21,8 @@ espresso_transcript = {git="https://github.com/EspressoSystems/hyperplonk", package="transcript"}
[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,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<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 @@ #[cfg(test)]
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();
@@ -19,24 +19,7 @@
#[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 @@ input * input * input + input + 5, // x^3 + x + 5
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,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 @@
#[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()); } }
@@ -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;
@@ -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()); + } +}
@@ -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 u: C::ScalarField,
pub cmW: C, pub x: Vec<C::ScalarField>, } + impl<C: CurveGroup> CommittedInstance<C> { pub fn empty() -> Self { CommittedInstance {
@@ -56,7 +56,8 @@ &vec_add(&w1.E, &vec_scalar_mul(T, &r)),
&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 @@ ci2: &CommittedInstance<C>,
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 @@ // NIFS.P
#[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 @@ }
// 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 @@ let z2 = get_test_z(4);
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 @@ assert_eq!(ci3_expected.cmE, ci3.cmE);
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 @@ &ci3,
T, &cmT, ); - let v = NIFS::<G1Projective>::verify_commitments( + let v = NIFS::<Projective>::verify_commitments( &mut transcript_v, &pedersen_params, ci3,@@ -272,10 +280,10 @@ let z = get_test_z(3);
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 @@ for i in 0..num_iters {
// 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 @@
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 @@ &incomming_committed_instance,
); // NIFS.V - let folded_committed_instance = NIFS::<G1Projective>::verify( + let folded_committed_instance = NIFS::<Projective>::verify( r, &running_committed_instance, &incomming_committed_instance,
@@ -9,6 +9,7 @@
pub mod transcript; use transcript::Transcript; pub mod ccs; +pub mod constants; pub mod folding; pub mod pedersen; pub mod utils;
@@ -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 @@ let mut rng = ark_std::test_rng();
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(¶ms, &v, &r); - let proof = Pedersen::<G1Projective>::prove(¶ms, &mut transcript_p, &cm, &v, &r); - let v = Pedersen::<G1Projective>::verify(¶ms, &mut transcript_v, cm, proof); + let cm = Pedersen::<Projective>::commit(¶ms, &v, &r); + let proof = Pedersen::<Projective>::prove(¶ms, &mut transcript_p, &cm, &v, &r); + let v = Pedersen::<Projective>::verify(¶ms, &mut transcript_v, cm, proof); assert!(v); } }
@@ -11,5 +11,7 @@ fn absorb(&mut self, v: &C::ScalarField);
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>; }
@@ -5,7 +5,7 @@ Absorb, CryptographicSponge,
}; 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;@@ -41,6 +41,9 @@ fn get_challenge(&mut self) -> C::ScalarField {
let c = self.sponge.squeeze_field_elements(1); 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);@@ -92,6 +95,12 @@ let c = self.sponge.squeeze_field_elements(1)?;
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 @@
#[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 @@ )
} #[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();@@ -151,5 +162,53 @@ let c_var = tr_var.get_challenge().unwrap();
// 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() + ); } }
@@ -321,7 +321,7 @@
#[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;
@@ -412,8 +412,8 @@ #[cfg(test)]
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,
@@ -74,7 +74,7 @@ r
} 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<F: PrimeField>(a: &[F], b: &[F]) -> Vec<F> {
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])); + } +}