From d9887af5352ad4d51d1db049a8f0eb4019f47905 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 5 Sep 2023 17:17:59 +0200 Subject: [PATCH] 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 --- Cargo.toml | 4 +- src/ccs/mod.rs | 20 +- src/ccs/r1cs.rs | 21 +- src/constants.rs | 1 + src/folding/circuits/cyclefold.rs | 43 ++-- src/folding/circuits/mod.rs | 3 +- src/folding/nova/circuits.rs | 273 +++++++++++++++++++++++ src/folding/nova/mod.rs | 2 + src/folding/nova/nifs.rs | 46 ++-- src/lib.rs | 1 + src/pedersen.rs | 14 +- src/transcript/mod.rs | 2 + src/transcript/poseidon.rs | 69 +++++- src/utils/espresso/sum_check/verifier.rs | 2 +- src/utils/espresso/virtual_polynomial.rs | 2 +- src/utils/vec.rs | 64 +++++- 16 files changed, 480 insertions(+), 87 deletions(-) create mode 100644 src/constants.rs create mode 100644 src/folding/nova/circuits.rs diff --git a/Cargo.toml b/Cargo.toml index 2975aac..08f0d87 100644 --- a/Cargo.toml +++ b/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"] diff --git a/src/ccs/mod.rs b/src/ccs/mod.rs index 9a052c1..49163fd 100644 --- a/src/ccs/mod.rs +++ b/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 { - /// 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 CCS { - pub fn from_r1cs(r1cs: R1CS, io_len: usize) -> Self { - let m = r1cs.A.n_cols; - let n = r1cs.A.n_rows; + pub fn from_r1cs(r1cs: R1CS) -> 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() -> CCS { let r1cs = get_test_r1cs::(); - CCS::::from_r1cs(r1cs, 1) + CCS::::from_r1cs(r1cs) } /// Test that a basic CCS relation can be satisfied #[test] fn test_ccs_relation() { - let ccs = get_test_ccs::(); + let ccs = get_test_ccs::(); let z = get_test_z(3); ccs.check_relation(&z).unwrap(); diff --git a/src/ccs/r1cs.rs b/src/ccs/r1cs.rs index 8384ba4..78d04b5 100644 --- a/src/ccs/r1cs.rs +++ b/src/ccs/r1cs.rs @@ -19,24 +19,7 @@ impl R1CS { #[cfg(test)] pub mod tests { use super::*; - - pub fn to_F_matrix(M: Vec>) -> Vec> { - let mut R: Vec> = 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(z: Vec) -> Vec { - let mut r: Vec = 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() -> R1CS { // 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, ]) } } diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..b2dc013 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1 @@ +pub const N_BITS_CHALLENGE: usize = 250; diff --git a/src/folding/circuits/cyclefold.rs b/src/folding/circuits/cyclefold.rs index 1f34b23..5d82770 100644 --- a/src/folding/circuits/cyclefold.rs +++ b/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>> { +pub struct ECRLC>> { _c: PhantomData, _gc: PhantomData, } -impl>> ECRLC { +impl>> ECRLC { pub fn check( - r: NonNativeFieldVar>, + // 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>>, 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::::new_ref(); // CS over Curve2::Fr = Curve1::Fq // prepare circuit inputs - let rVar = NonNativeFieldVar::::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> = + 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::::check(rVar, p1Var, p2Var, p3Var).unwrap(); + ECRLC::::check(rbitsVar, p1Var, p2Var, p3Var).unwrap(); assert!(cs.is_satisfied().unwrap()); - // dbg!(cs.num_constraints()); } } diff --git a/src/folding/circuits/mod.rs b/src/folding/circuits/mod.rs index 2542c54..02fb9b6 100644 --- a/src/folding/circuits/mod.rs +++ b/src/folding/circuits/mod.rs @@ -4,4 +4,5 @@ use ark_ff::Field; pub mod cyclefold; -pub type ConstraintF = <::BaseField as Field>::BasePrimeField; +// CF represents the constraints field +pub type CF = <::BaseField as Field>::BasePrimeField; diff --git a/src/folding/nova/circuits.rs b/src/folding/nova/circuits.rs new file mode 100644 index 0000000..9d97ade --- /dev/null +++ b/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 = <::Affine as AffineRepr>::ScalarField; +/// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq. +pub type CF2 = <::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: PhantomData, + u: FpVar, + x: Vec>, +} + +impl AllocVar, CF1> for CommittedInstanceE1Var +where + C: CurveGroup, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|val| { + let cs = cs.into(); + + let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; + let x: Vec> = + 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>> +where + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + _c: PhantomData, + cmE: GC, + cmW: GC, +} + +impl AllocVar, CF2> for CommittedInstanceE2Var +where + C: CurveGroup, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + fn new_variable>>( + cs: impl Into>>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + 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: PhantomData, +} + +impl NIFSGadget +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>, + ci1: CommittedInstanceE1Var, + ci2: CommittedInstanceE1Var, + ci3: CommittedInstanceE1Var, + ) -> 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::>>>(); + 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: PhantomData, + _gc: PhantomData, +} +impl>> NIFSCycleFoldGadget +where + C: CurveGroup, + GC: CurveVar>, + for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, +{ + pub fn verify( + r_bits: Vec>>, + cmT: GC, + ci1: CommittedInstanceE2Var, + ci2: CommittedInstanceE2Var, + ci3: CommittedInstanceE2Var, + ) -> 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::::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:: { + 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::::new_ref(); + let ciVar = + CommittedInstanceE1Var::::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::::new_ref(); + let ciVar = + CommittedInstanceE2Var::::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::::new(w1.clone(), r1cs.A.n_rows); + let w2 = Witness::::new(w2.clone(), r1cs.A.n_rows); + + let mut rng = ark_std::test_rng(); + let pedersen_params = Pedersen::::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::(); + let mut tr = PoseidonTranscript::::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::::prove(&pedersen_params, r_Fr, &r1cs, &w1, &ci1, &w2, &ci2); + + let cs = ConstraintSystem::::new_ref(); + + let rVar = FpVar::::new_witness(cs.clone(), || Ok(r_Fr)).unwrap(); + let ci1Var = + CommittedInstanceE1Var::::new_witness(cs.clone(), || Ok(ci1.clone())) + .unwrap(); + let ci2Var = + CommittedInstanceE1Var::::new_witness(cs.clone(), || Ok(ci2.clone())) + .unwrap(); + let ci3Var = + CommittedInstanceE1Var::::new_witness(cs.clone(), || Ok(ci3.clone())) + .unwrap(); + + NIFSGadget::::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::::new_ref(); + + let r_bitsVar = Vec::>::new_witness(cs_CC.clone(), || Ok(r_bits)).unwrap(); + + let cmTVar = GVar::new_witness(cs_CC.clone(), || Ok(cmT)).unwrap(); + let ci1Var = CommittedInstanceE2Var::::new_witness(cs_CC.clone(), || { + Ok(ci1.clone()) + }) + .unwrap(); + let ci2Var = CommittedInstanceE2Var::::new_witness(cs_CC.clone(), || { + Ok(ci2.clone()) + }) + .unwrap(); + let ci3Var = CommittedInstanceE2Var::::new_witness(cs_CC.clone(), || { + Ok(ci3.clone()) + }) + .unwrap(); + + NIFSCycleFoldGadget::::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var) + .unwrap(); + assert!(cs_CC.is_satisfied().unwrap()); + } +} diff --git a/src/folding/nova/mod.rs b/src/folding/nova/mod.rs index 84b03ab..33fe2d0 100644 --- a/src/folding/nova/mod.rs +++ b/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, } + impl CommittedInstance { pub fn empty() -> Self { CommittedInstance { diff --git a/src/folding/nova/nifs.rs b/src/folding/nova/nifs.rs index c2e2ab1..b48d2b8 100644 --- a/src/folding/nova/nifs.rs +++ b/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 = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect(); + let rW = w1.rW + r * w2.rW; Witness:: { E, rE, W, rW } } @@ -68,11 +69,15 @@ where cmT: &C, ) -> CommittedInstance { 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::>(); CommittedInstance:: { cmE, u, cmW, x } } @@ -81,6 +86,7 @@ where #[allow(clippy::type_complexity)] pub fn prove( pedersen_params: &PedersenParams, + // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element r: C::ScalarField, r1cs: &R1CS, w1: &Witness, @@ -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, ci2: &CommittedInstance, @@ -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(r1cs: &R1CS, z: Vec, 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::::new(w1.clone(), r1cs.A.n_cols); - let w2 = Witness::::new(w2.clone(), r1cs.A.n_cols); + let w1 = Witness::::new(w1.clone(), r1cs.A.n_rows); + let w2 = Witness::::new(w2.clone(), r1cs.A.n_rows); let mut rng = ark_std::test_rng(); - let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_cols); + let pedersen_params = Pedersen::::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::::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2); + NIFS::::prove(&pedersen_params, r, &r1cs, &w1, &ci1, &w2, &ci2); // NIFS.V - let ci3 = NIFS::::verify(r, &ci1, &ci2, &cmT); + let ci3 = NIFS::::verify(r, &ci1, &ci2, &cmT); // naive check that the folded witness satisfies the relaxed r1cs let z3: Vec = [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::::verify_folded_instance( + assert!(NIFS::::verify_folded_instance( r, &ci1, &ci2, &ci3, &cmT )); let poseidon_config = poseidon_test_config::(); // init Prover's transcript - let mut transcript_p = PoseidonTranscript::::new(&poseidon_config); + let mut transcript_p = PoseidonTranscript::::new(&poseidon_config); // init Verifier's transcript - let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); + let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); // check openings of ci3.cmE, ci3.cmW and cmT - let (cmE_proof, cmW_proof, cmT_proof) = NIFS::::open_commitments( + let (cmE_proof, cmW_proof, cmT_proof) = NIFS::::open_commitments( &mut transcript_p, &pedersen_params, &w3, @@ -253,7 +261,7 @@ mod tests { T, &cmT, ); - let v = NIFS::::verify_commitments( + let v = NIFS::::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::::new_params(&mut rng, r1cs.A.n_cols); + let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_cols); // prepare the running instance - let mut running_instance_w = Witness::::new(w.clone(), r1cs.A.n_cols); + let mut running_instance_w = Witness::::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::::new(w.clone(), r1cs.A.n_cols); + let incomming_instance_w = Witness::::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::::prove( + let (folded_w, _, _, cmT) = NIFS::::prove( &pedersen_params, r, &r1cs, @@ -312,7 +320,7 @@ mod tests { ); // NIFS.V - let folded_committed_instance = NIFS::::verify( + let folded_committed_instance = NIFS::::verify( r, &running_committed_instance, &incomming_committed_instance, diff --git a/src/lib.rs b/src/lib.rs index 4851ed5..78a01ce 100644 --- a/src/lib.rs +++ b/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; diff --git a/src/pedersen.rs b/src/pedersen.rs index e21406a..968cd4c 100644 --- a/src/pedersen.rs +++ b/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::::new_params(&mut rng, n); + let params = Pedersen::::new_params(&mut rng, n); let poseidon_config = poseidon_test_config::(); // init Prover's transcript - let mut transcript_p = PoseidonTranscript::::new(&poseidon_config); + let mut transcript_p = PoseidonTranscript::::new(&poseidon_config); // init Verifier's transcript - let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); + let mut transcript_v = PoseidonTranscript::::new(&poseidon_config); let v: Vec = vec![Fr::rand(&mut rng); n]; let r: Fr = Fr::rand(&mut rng); - let cm = Pedersen::::commit(¶ms, &v, &r); - let proof = Pedersen::::prove(¶ms, &mut transcript_p, &cm, &v, &r); - let v = Pedersen::::verify(¶ms, &mut transcript_v, cm, proof); + let cm = Pedersen::::commit(¶ms, &v, &r); + let proof = Pedersen::::prove(¶ms, &mut transcript_p, &cm, &v, &r); + let v = Pedersen::::verify(¶ms, &mut transcript_v, cm, proof); assert!(v); } } diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index 420e6bc..ccfc59c 100644 --- a/src/transcript/mod.rs +++ b/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; fn get_challenges(&mut self, n: usize) -> Vec; } diff --git a/src/transcript/poseidon.rs b/src/transcript/poseidon.rs index 29f2e4d..67d72ab 100644 --- a/src/transcript/poseidon.rs +++ b/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 { + self.sponge.squeeze_bits(nbits) + } fn get_challenges(&mut self, n: usize) -> Vec { 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>, SynthesisError> { + self.sponge.squeeze_bits(nbits) + } pub fn get_challenges(&mut self, n: usize) -> Result>, 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::(); - let mut tr = PoseidonTranscript::::new(&config); + let mut tr = PoseidonTranscript::::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::(); + let mut tr = PoseidonTranscript::::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::::new_ref(); + let mut tr_var = PoseidonTranscriptVar::::new(cs.clone(), &config); + let v = FpVar::::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() + ); + } } diff --git a/src/utils/espresso/sum_check/verifier.rs b/src/utils/espresso/sum_check/verifier.rs index cb60928..ef4bf76 100644 --- a/src/utils/espresso/sum_check/verifier.rs +++ b/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; diff --git a/src/utils/espresso/virtual_polynomial.rs b/src/utils/espresso/virtual_polynomial.rs index 25a1f0f..bbd65d7 100644 --- a/src/utils/espresso/virtual_polynomial.rs +++ b/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, diff --git a/src/utils/vec.rs b/src/utils/vec.rs index 837310f..9118c78 100644 --- a/src/utils/vec.rs +++ b/src/utils/vec.rs @@ -74,7 +74,7 @@ pub fn mat_vec_mul(M: &Vec>, z: &[F]) -> Vec { } pub fn mat_vec_mul_sparse(matrix: &SparseMatrix, vector: &[F]) -> Vec { - 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(M: Vec>) -> Vec> { + let mut R: Vec> = 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(z: Vec) -> Vec { + let mut r: Vec = 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::(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::(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::(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 = to_F_vec::(vec![1, 2, 3, 4, 5, 6]); + let b: Vec = 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])); + } +}