From 602a367411be182d5f26be31060284de464ece29 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Fri, 1 Mar 2024 15:05:51 +0100 Subject: [PATCH] Change CycleFold circuit approach (#77) * Change CycleFold approach: Instead of having a single CycleFold circuit that checks the 2 forign scalarmul of the main circuit instances, now there are 2 separated CycleFold circuits each of them checking a single foreign scalarmul. Increasing the number of constraints of the AugmentedFCircuit, but reducing the number of constraints in the CycleFold circuit, which will translate into reducing the number of constraints in the Decider circuit. * CycleFold circuits checks in AugmentedFCircuit: - update NonNativeAffineVar to work with NonNativeFieldVar directly instead of FpVar comming from NonNativeFieldVar.to_constraint_field() - include in AugmentedFCircuit intermediate steps inbetween CycleFold circuits, and update the internal checks of the CycleFold circuits Pending to document the new CycleFold circuits approach and better variable namings, rm unwraps, etc * matrix_vec_mul_sparse gadget: skip value * v[col_i] mul when value==1 Saves a notable amount of constraints since there is a notable amount of 1 values in R1CS matrices. * Reuse computed vector of U_i Reuse computed vector of U_i, saving 4k constraints in AugmentedFCircuit. * fixes post last rebase to main * rm test_augmentedfcircuit since it is already tested in test_ivc (and is a slow computation) * rm dbg!() * small fixes after last main rebase --- folding-schemes/src/commitment/pedersen.rs | 23 +- .../src/folding/circuits/nonnative.rs | 31 +- folding-schemes/src/folding/nova/circuits.rs | 497 ++++++------------ folding-schemes/src/folding/nova/cyclefold.rs | 195 +++---- .../src/folding/nova/decider_eth.rs | 4 +- .../src/folding/nova/decider_eth_circuit.rs | 31 +- folding-schemes/src/folding/nova/mod.rs | 226 +++++--- folding-schemes/src/folding/nova/nifs.rs | 4 +- folding-schemes/src/lib.rs | 3 +- folding-schemes/src/utils/gadgets.rs | 5 + 10 files changed, 446 insertions(+), 573 deletions(-) diff --git a/folding-schemes/src/commitment/pedersen.rs b/folding-schemes/src/commitment/pedersen.rs index 8542975..69a7d01 100644 --- a/folding-schemes/src/commitment/pedersen.rs +++ b/folding-schemes/src/commitment/pedersen.rs @@ -1,6 +1,6 @@ use ark_ec::CurveGroup; use ark_ff::Field; -use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar}; +use ark_r1cs_std::{boolean::Boolean, groups::GroupOpsBounds, prelude::CurveVar}; use ark_relations::r1cs::SynthesisError; use ark_std::Zero; use ark_std::{ @@ -148,7 +148,7 @@ where _gc: PhantomData, } -use ark_r1cs_std::{fields::nonnative::NonNativeFieldVar, ToBitsGadget}; +use ark_r1cs_std::ToBitsGadget; impl PedersenGadget where C: CurveGroup, @@ -160,12 +160,12 @@ where pub fn commit( h: GC, g: Vec, - v: Vec>>, - r: NonNativeFieldVar>, + v: Vec>>>, + r: Vec>>, ) -> Result { let mut res = GC::zero(); if H { - res += h.scalar_mul_le(r.to_bits_le()?.iter())?; + res += h.scalar_mul_le(r.iter())?; } for (i, v_i) in v.iter().enumerate() { res += g[i].scalar_mul_le(v_i.to_bits_le()?.iter())?; @@ -176,6 +176,7 @@ where #[cfg(test)] mod tests { + use ark_ff::{BigInteger, PrimeField}; use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; use ark_relations::r1cs::ConstraintSystem; @@ -226,12 +227,20 @@ mod tests { let r: Fr = Fr::rand(&mut rng); let cm = Pedersen::::commit(¶ms, &v, &r).unwrap(); + let v_bits: Vec> = v.iter().map(|val| val.into_bigint().to_bits_le()).collect(); + let r_bits: Vec = r.into_bigint().to_bits_le(); + // circuit let cs = ConstraintSystem::::new_ref(); // prepare inputs - let vVar = Vec::>::new_witness(cs.clone(), || Ok(v)).unwrap(); - let rVar = NonNativeFieldVar::::new_witness(cs.clone(), || Ok(r)).unwrap(); + let vVar: Vec>> = v_bits + .iter() + .map(|val_bits| { + Vec::>::new_witness(cs.clone(), || Ok(val_bits.clone())).unwrap() + }) + .collect(); + let rVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); let gVar = Vec::::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(); diff --git a/folding-schemes/src/folding/circuits/nonnative.rs b/folding-schemes/src/folding/circuits/nonnative.rs index c8c695a..8211d7a 100644 --- a/folding-schemes/src/folding/circuits/nonnative.rs +++ b/folding-schemes/src/folding/circuits/nonnative.rs @@ -1,10 +1,8 @@ use ark_ec::{AffineRepr, CurveGroup}; -use ark_ff::PrimeField; use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, - fields::{fp::FpVar, nonnative::NonNativeFieldVar}, - ToConstraintFieldGadget, + fields::nonnative::NonNativeFieldVar, }; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::{One, Zero}; @@ -14,12 +12,15 @@ use core::borrow::Borrow; /// field, over the constraint field. It is not intended to perform operations, but just to contain /// the affine coordinates in order to perform hash operations of the point. #[derive(Debug, Clone)] -pub struct NonNativeAffineVar { - pub x: Vec>, - pub y: Vec>, +pub struct NonNativeAffineVar +where + ::BaseField: ark_ff::PrimeField, +{ + pub x: NonNativeFieldVar, + pub y: NonNativeFieldVar, } -impl AllocVar for NonNativeAffineVar +impl AllocVar for NonNativeAffineVar where C: CurveGroup, ::BaseField: ark_ff::PrimeField, @@ -40,14 +41,12 @@ where cs.clone(), || Ok(xy.0), mode, - )? - .to_constraint_field()?; + )?; let y = NonNativeFieldVar::::new_variable( cs.clone(), || Ok(xy.1), mode, - )? - .to_constraint_field()?; + )?; Ok(Self { x, y }) }) @@ -108,7 +107,7 @@ where mod tests { use super::*; use ark_pallas::{Fr, Projective}; - use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; + use ark_r1cs_std::{alloc::AllocVar, R1CSVar, ToConstraintFieldGadget}; use ark_relations::r1cs::ConstraintSystem; use ark_std::{UniformRand, Zero}; @@ -118,14 +117,14 @@ mod tests { // dealing with the 'zero' point should not panic when doing the unwrap let p = Projective::zero(); - NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).unwrap(); + NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).unwrap(); // check that point_to_nonnative_limbs returns the expected values let mut rng = ark_std::test_rng(); let p = Projective::rand(&mut rng); - let pVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).unwrap(); + let pVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(p)).unwrap(); let (x, y) = point_to_nonnative_limbs(p).unwrap(); - assert_eq!(pVar.x.value().unwrap(), x); - assert_eq!(pVar.y.value().unwrap(), y); + assert_eq!(pVar.x.to_constraint_field().unwrap().value().unwrap(), x); + assert_eq!(pVar.y.to_constraint_field().unwrap().value().unwrap(), y); } } diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index c81ad01..b14db99 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -46,11 +46,14 @@ pub type CF2 = <::BaseField as Field>::BasePrimeField; /// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are /// represented non-natively over the constraint field. #[derive(Debug, Clone)] -pub struct CommittedInstanceVar { +pub struct CommittedInstanceVar +where + ::BaseField: ark_ff::PrimeField, +{ pub u: FpVar, pub x: Vec>, - pub cmE: NonNativeAffineVar, - pub cmW: NonNativeAffineVar, + pub cmE: NonNativeAffineVar, + pub cmW: NonNativeAffineVar, } impl AllocVar, CF1> for CommittedInstanceVar @@ -70,16 +73,10 @@ where let x: Vec> = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; - let cmE = NonNativeAffineVar::::new_variable( - cs.clone(), - || Ok(val.borrow().cmE), - mode, - )?; - let cmW = NonNativeAffineVar::::new_variable( - cs.clone(), - || Ok(val.borrow().cmW), - mode, - )?; + let cmE = + NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; + let cmW = + NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; Ok(Self { u, x, cmE, cmW }) }) @@ -90,31 +87,36 @@ impl CommittedInstanceVar where C: CurveGroup, ::ScalarField: Absorb, + ::BaseField: ark_ff::PrimeField, { /// hash implements the committed instance hash compatible with the native implementation from /// CommittedInstance.hash. /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the /// `CommittedInstance`. + /// Additionally it returns the vector of the field elements from the self parameters, so they + /// can be reused in other gadgets avoiding recalculating (reconstraining) them. + #[allow(clippy::type_complexity)] pub fn hash( self, crh_params: &CRHParametersVar>, i: FpVar>, z_0: Vec>>, z_i: Vec>>, - ) -> Result>, SynthesisError> { - let input = vec![ - vec![i], - z_0, - z_i, + ) -> Result<(FpVar>, Vec>>), SynthesisError> { + let U_vec = [ vec![self.u], self.x, - self.cmE.x, - self.cmE.y, - self.cmW.x, - self.cmW.y, + self.cmE.x.to_constraint_field()?, + self.cmE.y.to_constraint_field()?, + self.cmW.x.to_constraint_field()?, + self.cmW.y.to_constraint_field()?, ] .concat(); - CRHGadget::::evaluate(crh_params, &input) + let input = [vec![i], z_0, z_i, U_vec.clone()].concat(); + Ok(( + CRHGadget::::evaluate(crh_params, &input)?, + U_vec, + )) } } @@ -128,14 +130,15 @@ pub struct NIFSGadget { impl NIFSGadget where C: CurveGroup, + ::BaseField: ark_ff::PrimeField, { /// 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: CommittedInstanceVar, - ci2: CommittedInstanceVar, - ci3: CommittedInstanceVar, + ci1: CommittedInstanceVar, // U_i + ci2: CommittedInstanceVar, // u_i + ci3: CommittedInstanceVar, // U_{i+1} ) -> Result>, SynthesisError> { // ensure that: ci3.u == ci1.u + r * ci2.u let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?; @@ -166,30 +169,30 @@ where { pub fn get_challenge_native( poseidon_config: &PoseidonConfig, - u_i: CommittedInstance, U_i: CommittedInstance, + u_i: CommittedInstance, cmT: C, ) -> Result, SynthesisError> { - let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::(u_i.cmE)?; - let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::(u_i.cmW)?; let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::(U_i.cmE)?; let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::(U_i.cmW)?; + let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::(u_i.cmE)?; + let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::(u_i.cmW)?; let (cmT_x, cmT_y) = point_to_nonnative_limbs::(cmT)?; let mut sponge = PoseidonSponge::::new(poseidon_config); let input = vec![ - vec![u_i.u], - u_i.x.clone(), - u_cmE_x, - u_cmE_y, - u_cmW_x, - u_cmW_y, vec![U_i.u], U_i.x.clone(), U_cmE_x, U_cmE_y, U_cmW_x, U_cmW_y, + vec![u_i.u], + u_i.x.clone(), + u_cmE_x, + u_cmE_y, + u_cmW_x, + u_cmW_y, cmT_x, cmT_y, ] @@ -203,27 +206,22 @@ where pub fn get_challenge_gadget( cs: ConstraintSystemRef, poseidon_config: &PoseidonConfig, + U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values u_i: CommittedInstanceVar, - U_i: CommittedInstanceVar, - cmT: NonNativeAffineVar, + cmT: NonNativeAffineVar, ) -> Result>, SynthesisError> { let mut sponge = PoseidonSpongeVar::::new(cs, poseidon_config); let input: Vec> = vec![ + U_i_vec, vec![u_i.u.clone()], u_i.x.clone(), - u_i.cmE.x, - u_i.cmE.y, - u_i.cmW.x, - u_i.cmW.y, - vec![U_i.u.clone()], - U_i.x.clone(), - U_i.cmE.x, - U_i.cmE.y, - U_i.cmW.x, - U_i.cmW.y, - cmT.x, - cmT.y, + u_i.cmE.x.to_constraint_field()?, + u_i.cmE.y.to_constraint_field()?, + u_i.cmW.x.to_constraint_field()?, + u_i.cmW.y.to_constraint_field()?, + cmT.x.to_constraint_field()?, + cmT.y.to_constraint_field()?, ] .concat(); sponge.absorb(&input)?; @@ -257,11 +255,17 @@ pub struct AugmentedFCircuit< pub x: Option>, // public inputs (u_{i+1}.x) // cyclefold verifier on C1 - pub cf_u_i: Option>, - pub cf_U_i: Option>, - pub cf_U_i1: Option>, - pub cf_cmT: Option, - pub cf_r_nonnat: Option, + // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and + // cmE respectively + pub cf1_u_i: Option>, // input + pub cf2_u_i: Option>, // input + pub cf_U_i: Option>, // input + pub cf1_U_i1: Option>, // intermediate + pub cf_U_i1: Option>, // output + pub cf1_cmT: Option, + pub cf2_cmT: Option, + pub cf1_r_nonnat: Option, + pub cf2_r_nonnat: Option, } impl>, FC: FCircuit>> @@ -283,11 +287,15 @@ where F: F_circuit, x: None, // cyclefold values - cf_u_i: None, + cf1_u_i: None, + cf2_u_i: None, cf_U_i: None, + cf1_U_i1: None, cf_U_i1: None, - cf_cmT: None, - cf_r_nonnat: None, + cf1_cmT: None, + cf2_cmT: None, + cf1_r_nonnat: None, + cf2_r_nonnat: None, } } } @@ -347,9 +355,9 @@ where let is_not_basecase = i.is_neq(&zero)?; // 1. u_i.x == H(i, z_0, z_i, U_i) - let u_i_x = U_i - .clone() - .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; + let (u_i_x, U_i_vec) = + U_i.clone() + .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; // check that h == u_i.x (u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?; @@ -358,13 +366,11 @@ where let zero_x = NonNativeFieldVar::::new_constant( cs.clone(), C1::BaseField::zero(), - )? - .to_constraint_field()?; + )?; let zero_y = NonNativeFieldVar::::new_constant( cs.clone(), C1::BaseField::one(), - )? - .to_constraint_field()?; + )?; (u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; @@ -374,19 +380,19 @@ where let r_bits = ChallengeGadget::::get_challenge_gadget( cs.clone(), &self.poseidon_config, + U_i_vec, u_i.clone(), - U_i.clone(), cmT.clone(), )?; let r = Boolean::le_bits_to_fp_var(&r_bits)?; // Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will // be done on the other curve. - let nifs_check = NIFSGadget::::verify(r, u_i.clone(), U_i.clone(), U_i1.clone())?; + let nifs_check = NIFSGadget::::verify(r, U_i.clone(), u_i.clone(), U_i1.clone())?; nifs_check.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; // 4. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F' - let u_i1_x = U_i1.clone().hash( + let (u_i1_x, _) = U_i1.clone().hash( &crh_params, i + FpVar::>::one(), z_0.clone(), @@ -397,65 +403,99 @@ where // CycleFold part let cf_u_dummy_native = CommittedInstance::::dummy(CF_IO_LEN); - let cf_u_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { - Ok(self.cf_u_i.unwrap_or_else(|| cf_u_dummy_native.clone())) + // cf W circuit data + let cf1_u_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { + Ok(self.cf1_u_i.unwrap_or_else(|| cf_u_dummy_native.clone())) + })?; + let cf2_u_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { + Ok(self.cf2_u_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; + let cf1_U_i1 = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { + Ok(self.cf1_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone())) + })?; let cf_U_i1 = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; - let cf_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?; - // cf_r_nonnat is an auxiliary input - let cf_r_nonnat = - NonNativeFieldVar::>::new_witness(cs.clone(), || { - Ok(self.cf_r_nonnat.unwrap_or_else(C2::ScalarField::zero)) + let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?; + let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?; + let cf1_r_nonnat = + NonNativeFieldVar::::new_witness(cs.clone(), || { + Ok(self.cf1_r_nonnat.unwrap_or_else(C2::ScalarField::zero)) + })?; + let cf2_r_nonnat = + NonNativeFieldVar::::new_witness(cs.clone(), || { + Ok(self.cf2_r_nonnat.unwrap_or_else(C2::ScalarField::zero)) })?; - // check that cf_u_i.x == [u_i, U_i, U_{i+1}] - let incircuit_x = vec![ - u_i.cmE.x, u_i.cmE.y, u_i.cmW.x, u_i.cmW.y, U_i.cmE.x, U_i.cmE.y, U_i.cmW.x, U_i.cmW.y, - U_i1.cmE.x, U_i1.cmE.y, U_i1.cmW.x, U_i1.cmW.y, - ] - .concat(); + let cfW_x: Vec> = vec![ + U_i.cmW.x, U_i.cmW.y, u_i.cmW.x, u_i.cmW.y, U_i1.cmW.x, U_i1.cmW.y, + ]; + let cfE_x: Vec> = vec![ + U_i.cmE.x, U_i.cmE.y, u_i.cmE.x, u_i.cmE.y, U_i1.cmE.x, U_i1.cmE.y, + ]; - let mut cf_u_i_x: Vec>> = vec![]; - for x_i in cf_u_i.x.iter() { - let mut x_fpvar = x_i.to_constraint_field()?; - cf_u_i_x.append(&mut x_fpvar); - } - cf_u_i_x.conditional_enforce_equal(&incircuit_x, &is_not_basecase)?; + // ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i, + // u_i, U_i+1 coordinates of the commitments + cf1_u_i + .x + .conditional_enforce_equal(&cfW_x, &is_not_basecase)?; + cf2_u_i + .x + .conditional_enforce_equal(&cfE_x, &is_not_basecase)?; - // cf_r_bits is denoted by rho* in the paper - let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( + // cf_r_bits is denoted by rho* in the paper. + // assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary + // value used to compute RLC of NonNativeFieldVar values, since we can convert + // NonNativeFieldVar into Vec, but not in the other direction. + let cf1_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( cs.clone(), &self.poseidon_config, - cf_u_i.clone(), cf_U_i.clone(), - cf_cmT.clone(), + cf1_u_i.clone(), + cf1_cmT.clone(), )?; - // assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary - // value used to compute RLC of NonNativeFieldVar values, since we can convert - // NonNativeFieldVar into Vec, but not in the other direction. - let cf_r_nonnat_bits = cf_r_nonnat.to_bits_le()?; - cf_r_bits.conditional_enforce_equal(&cf_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?; + let cf1_r_nonnat_bits = cf1_r_nonnat.to_bits_le()?; + cf1_r_bits.conditional_enforce_equal(&cf1_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?; + // same for cf2_r: + let cf2_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( + cs.clone(), + &self.poseidon_config, + cf1_U_i1.clone(), + cf2_u_i.clone(), + cf2_cmT.clone(), + )?; + let cf2_r_nonnat_bits = cf2_r_nonnat.to_bits_le()?; + cf2_r_bits.conditional_enforce_equal(&cf2_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?; // check cf_u_i.cmE=0, cf_u_i.u=1 - (cf_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; - (cf_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; + (cf1_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; + (cf1_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; + (cf2_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; + (cf2_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; // check the fold of all the parameters of the CycleFold instances, where the elliptic // curve points relations are checked natively in Curve1 circuit (this one) - let v = NIFSFullGadget::::verify( - cf_r_bits, - cf_r_nonnat, - cf_cmT, + let v1 = NIFSFullGadget::::verify( + cf1_r_bits, + cf1_r_nonnat, + cf1_cmT, cf_U_i, - cf_u_i, + cf1_u_i, + cf1_U_i1.clone(), + )?; + v1.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; + let v2 = NIFSFullGadget::::verify( + cf2_r_bits, + cf2_r_nonnat, + cf2_cmT, + cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) + cf2_u_i, cf_U_i1, )?; - v.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; + v2.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; Ok(()) } @@ -465,21 +505,14 @@ where pub mod tests { use super::*; use ark_ff::BigInteger; - use ark_pallas::{Fq, Fr, Projective}; + use ark_pallas::{Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; - use ark_relations::r1cs::{ConstraintLayer, ConstraintSystem, TracingMode}; - use ark_std::One; + use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; - use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; - use tracing_subscriber::layer::SubscriberExt; - use crate::ccs::r1cs::{extract_r1cs, extract_w_x}; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; - use crate::folding::nova::{ - get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness, - }; - use crate::frontend::tests::CubicFCircuit; + use crate::folding::nova::nifs::NIFS; use crate::transcript::poseidon::poseidon_test_config; #[test] @@ -565,7 +598,7 @@ pub mod tests { let crh_params = CRHParametersVar::::new_constant(cs.clone(), poseidon_config).unwrap(); // compute the CommittedInstance hash in-circuit - let hVar = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap(); + let (hVar, _) = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap(); assert!(cs.is_satisfied().unwrap()); // check that the natively computed and in-circuit computed hashes match @@ -595,8 +628,8 @@ pub mod tests { // compute the challenge natively let r_bits = ChallengeGadget::::get_challenge_native( &poseidon_config, - u_i.clone(), U_i.clone(), + u_i.clone(), cmT, ) .unwrap(); @@ -609,14 +642,23 @@ pub mod tests { let U_iVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone())) .unwrap(); - let cmTVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(cmT)).unwrap(); + let cmTVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(cmT)).unwrap(); // compute the challenge in-circuit + let U_iVar_vec = [ + vec![U_iVar.u.clone()], + U_iVar.x.clone(), + U_iVar.cmE.x.to_constraint_field().unwrap(), + U_iVar.cmE.y.to_constraint_field().unwrap(), + U_iVar.cmW.x.to_constraint_field().unwrap(), + U_iVar.cmW.y.to_constraint_field().unwrap(), + ] + .concat(); let r_bitsVar = ChallengeGadget::::get_challenge_gadget( cs.clone(), &poseidon_config, + U_iVar_vec, u_iVar, - U_iVar, cmTVar, ) .unwrap(); @@ -627,225 +669,4 @@ pub mod tests { assert_eq!(rVar.value().unwrap(), r); assert_eq!(r_bitsVar.value().unwrap(), r_bits); } - - #[test] - /// test_augmented_f_circuit folds the CubicFCircuit circuit in multiple iterations, feeding the - /// values into the AugmentedFCircuit. - fn test_augmented_f_circuit() { - let mut layer = ConstraintLayer::default(); - layer.mode = TracingMode::OnlyConstraints; - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_test_config::(); - - // compute z vector for the initial instance - let cs = ConstraintSystem::::new_ref(); - - // prepare the circuit to obtain its R1CS - let F_circuit = CubicFCircuit::::new(()); - let mut augmented_F_circuit = - AugmentedFCircuit::>::empty( - &poseidon_config, - F_circuit, - ); - augmented_F_circuit - .generate_constraints(cs.clone()) - .unwrap(); - cs.finalize(); - let cs = cs.into_inner().unwrap(); - let r1cs = extract_r1cs::(&cs); - let (w, x) = extract_w_x::(&cs); - assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols); - assert_eq!(r1cs.l, x.len()); - - let pedersen_params = Pedersen::::new_params(&mut rng, r1cs.A.n_rows); - - // first step, set z_i=z_0=3 and z_{i+1}=35 (initial values) - let z_0 = vec![Fr::from(3_u32)]; - let mut z_i = z_0.clone(); - let mut z_i1 = vec![Fr::from(35_u32)]; - - let w_dummy = Witness::::new(vec![Fr::zero(); w.len()], r1cs.A.n_rows); - let u_dummy = CommittedInstance::::dummy(x.len()); - - // W_i is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that - // we're working with. - // set U_i <-- dummy instance - let mut W_i = w_dummy.clone(); - let mut U_i = u_dummy.clone(); - r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap(); - - let mut w_i = w_dummy.clone(); - let mut u_i = u_dummy.clone(); - let (mut W_i1, mut U_i1, mut cmT): ( - Witness, - CommittedInstance, - Projective, - ) = (w_dummy.clone(), u_dummy.clone(), Projective::generator()); - // as expected, dummy instances pass the relaxed_r1cs check - r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap(); - - let mut i = Fr::zero(); - let mut u_i1_x: Fr; - for _ in 0..4 { - if i == Fr::zero() { - // base case: i=0, z_i=z_0, U_i = U_d := dummy instance - // u_1.x = H(1, z_0, z_i, U_i) - u_i1_x = U_i - .hash(&poseidon_config, Fr::one(), z_0.clone(), z_i1.clone()) - .unwrap(); - - // base case - augmented_F_circuit = - AugmentedFCircuit::> { - _gc2: PhantomData, - poseidon_config: poseidon_config.clone(), - i: Some(i), // = 0 - z_0: Some(z_0.clone()), // = z_i=3 - z_i: Some(z_i.clone()), - u_i: Some(u_i.clone()), // = dummy - U_i: Some(U_i.clone()), // = dummy - U_i1: Some(U_i1.clone()), // = dummy - cmT: Some(cmT), - F: F_circuit, - x: Some(u_i1_x), - // cyclefold instances (not tested in this test) - cf_u_i: None, - cf_U_i: None, - cf_U_i1: None, - cf_cmT: None, - cf_r_nonnat: None, - }; - } else { - r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap(); - r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap(); - - // U_{i+1} - let T: Vec; - (T, cmT) = NIFS::>::compute_cmT( - &pedersen_params, - &r1cs, - &w_i, - &u_i, - &W_i, - &U_i, - ) - .unwrap(); - - // get challenge r - let r_bits = ChallengeGadget::::get_challenge_native( - &poseidon_config, - u_i.clone(), - U_i.clone(), - cmT, - ) - .unwrap(); - let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); - - (W_i1, U_i1) = NIFS::>::fold_instances( - r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT, - ) - .unwrap(); - - r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap(); - - // folded instance output (public input, x) - // u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1}) - u_i1_x = U_i1 - .hash(&poseidon_config, i + Fr::one(), z_0.clone(), z_i1.clone()) - .unwrap(); - - // set up dummy cyclefold instances just for the sake of this test. Warning, this - // is only because we are in a test were we're not testing the cyclefold side of - // things. - let cf_W_i = Witness::::new(vec![Fq::zero(); 1], 1); - let cf_U_i = CommittedInstance::::dummy(CF_IO_LEN); - let cf_u_i_x = [ - get_committed_instance_coordinates(&u_i), - get_committed_instance_coordinates(&U_i), - get_committed_instance_coordinates(&U_i1), - ] - .concat(); - let cf_u_i = CommittedInstance:: { - cmE: cf_U_i.cmE, - u: Fq::one(), - cmW: cf_U_i.cmW, - x: cf_u_i_x, - }; - let cf_w_i = cf_W_i.clone(); - let (cf_T, cf_cmT): (Vec, Projective2) = - (vec![Fq::zero(); cf_W_i.E.len()], Projective2::zero()); - let cf_r_bits = - CycleFoldChallengeGadget::::get_challenge_native( - &poseidon_config, - cf_u_i.clone(), - cf_U_i.clone(), - cf_cmT, - ) - .unwrap(); - let cf_r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&cf_r_bits)).unwrap(); - let (_, cf_U_i1) = NIFS::>::fold_instances( - cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, - ) - .unwrap(); - - augmented_F_circuit = - AugmentedFCircuit::> { - _gc2: PhantomData, - poseidon_config: poseidon_config.clone(), - i: Some(i), - z_0: Some(z_0.clone()), - z_i: Some(z_i.clone()), - u_i: Some(u_i), - U_i: Some(U_i.clone()), - U_i1: Some(U_i1.clone()), - cmT: Some(cmT), - F: F_circuit, - x: Some(u_i1_x), - cf_u_i: Some(cf_u_i), - cf_U_i: Some(cf_U_i), - cf_U_i1: Some(cf_U_i1), - cf_cmT: Some(cf_cmT), - cf_r_nonnat: Some(cf_r_Fq), - }; - } - - let cs = ConstraintSystem::::new_ref(); - - augmented_F_circuit - .generate_constraints(cs.clone()) - .unwrap(); - let is_satisfied = cs.is_satisfied().unwrap(); - if !is_satisfied { - dbg!(cs.which_is_unsatisfied().unwrap()); - } - assert!(is_satisfied); - - cs.finalize(); - let cs = cs.into_inner().unwrap(); - let (w_i1, x_i1) = extract_w_x::(&cs); - assert_eq!(x_i1.len(), 1); - assert_eq!(x_i1[0], u_i1_x); - - // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we - // assign them directly to w_i, u_i. - w_i = Witness::::new(w_i1.clone(), r1cs.A.n_rows); - u_i = w_i - .commit::>(&pedersen_params, vec![u_i1_x]) - .unwrap(); - - r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap(); - r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap(); - - // set values for next iteration - i += Fr::one(); - // advance the F circuit state - z_i = z_i1.clone(); - z_i1 = F_circuit.step_native(z_i.clone()).unwrap(); - U_i = U_i1.clone(); - W_i = W_i1.clone(); - } - } } diff --git a/folding-schemes/src/folding/nova/cyclefold.rs b/folding-schemes/src/folding/nova/cyclefold.rs index 2cc7bc8..59d81b3 100644 --- a/folding-schemes/src/folding/nova/cyclefold.rs +++ b/folding-schemes/src/folding/nova/cyclefold.rs @@ -27,8 +27,8 @@ use super::CommittedInstance; use crate::constants::N_BITS_RO; use crate::Error; -// publi inputs length for the CycleFoldCircuit, |[u_i, U_i, U_{i+1}]| -pub const CF_IO_LEN: usize = 12; +// publi inputs length for the CycleFoldCircuit, |[p1.x,y, p2.x,y, p3.x,y]| +pub const CF_IO_LEN: usize = 6; /// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova /// circuit. @@ -122,40 +122,6 @@ where } } -/// NIFSinCycleFoldGadget performs the Nova NIFS.V elliptic curve points relation checks in the other -/// curve (natively) following [CycleFold](https://eprint.iacr.org/2023/1192.pdf). -pub struct NIFSinCycleFoldGadget>> { - _c: PhantomData, - _gc: PhantomData, -} -impl>> NIFSinCycleFoldGadget -where - C: CurveGroup, - GC: CurveVar>, - ::BaseField: ark_ff::PrimeField, - for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, -{ - pub fn verify( - r_bits: Vec>>, - cmT: GC, - ci1: CommittedInstanceInCycleFoldVar, - ci2: CommittedInstanceInCycleFoldVar, - ci3: CommittedInstanceInCycleFoldVar, - ) -> Result>, SynthesisError> { - // cm(E) check: ci3.cmE == ci1.cmE + r * cmT + r^2 * ci2.cmE - let first_check = ci3.cmE.is_eq( - &((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 - let second_check = ci3 - .cmW - .is_eq(&(ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?))?; - - first_check.and(&second_check) - } -} - /// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding, /// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks /// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1. @@ -171,6 +137,7 @@ where for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { pub fn verify( + // assumes that r_bits is equal to r_nonnat just that in a different format r_bits: Vec>>, r_nonnat: NonNativeFieldVar>, cmT: GC, @@ -224,38 +191,38 @@ where { pub fn get_challenge_native( poseidon_config: &PoseidonConfig, - u_i: CommittedInstance, U_i: CommittedInstance, + u_i: CommittedInstance, cmT: C, ) -> Result, Error> { let mut sponge = PoseidonSponge::::new(poseidon_config); - let u_i_cmE_bytes = point_to_bytes(u_i.cmE); - let u_i_cmW_bytes = point_to_bytes(u_i.cmW); - let U_i_cmE_bytes = point_to_bytes(U_i.cmE); - let U_i_cmW_bytes = point_to_bytes(U_i.cmW); - let cmT_bytes = point_to_bytes(cmT); + let U_i_cmE_bytes = point_to_bytes(U_i.cmE)?; + let U_i_cmW_bytes = point_to_bytes(U_i.cmW)?; + let u_i_cmE_bytes = point_to_bytes(u_i.cmE)?; + let u_i_cmW_bytes = point_to_bytes(u_i.cmW)?; + let cmT_bytes = point_to_bytes(cmT)?; - let mut u_i_u_bytes = Vec::new(); - u_i.u.serialize_uncompressed(&mut u_i_u_bytes)?; - let mut u_i_x_bytes = Vec::new(); - u_i.x.serialize_uncompressed(&mut u_i_x_bytes)?; - u_i_x_bytes = u_i_x_bytes[8..].to_vec(); let mut U_i_u_bytes = Vec::new(); U_i.u.serialize_uncompressed(&mut U_i_u_bytes)?; let mut U_i_x_bytes = Vec::new(); U_i.x.serialize_uncompressed(&mut U_i_x_bytes)?; U_i_x_bytes = U_i_x_bytes[8..].to_vec(); + let mut u_i_u_bytes = Vec::new(); + u_i.u.serialize_uncompressed(&mut u_i_u_bytes)?; + let mut u_i_x_bytes = Vec::new(); + u_i.x.serialize_uncompressed(&mut u_i_x_bytes)?; + u_i_x_bytes = u_i_x_bytes[8..].to_vec(); let input: Vec = [ - u_i_cmE_bytes, - u_i_u_bytes, - u_i_cmW_bytes, - u_i_x_bytes, U_i_cmE_bytes, U_i_u_bytes, U_i_cmW_bytes, U_i_x_bytes, + u_i_cmE_bytes, + u_i_u_bytes, + u_i_cmW_bytes, + u_i_x_bytes, cmT_bytes, ] .concat(); @@ -267,32 +234,32 @@ where pub fn get_challenge_gadget( cs: ConstraintSystemRef, poseidon_config: &PoseidonConfig, - u_i: CycleFoldCommittedInstanceVar, U_i: CycleFoldCommittedInstanceVar, + u_i: CycleFoldCommittedInstanceVar, cmT: GC, ) -> Result>, SynthesisError> { let mut sponge = PoseidonSpongeVar::::new(cs, poseidon_config); - let u_i_x_bytes: Vec>> = u_i + let U_i_x_bytes: Vec>> = U_i .x .iter() .flat_map(|e| e.to_bytes().unwrap_or(vec![])) .collect::>>>(); - let U_i_x_bytes: Vec>> = U_i + let u_i_x_bytes: Vec>> = u_i .x .iter() .flat_map(|e| e.to_bytes().unwrap_or(vec![])) .collect::>>>(); let input: Vec>> = [ - u_i.cmE.to_bytes()?, - u_i.u.to_bytes()?, - u_i.cmW.to_bytes()?, - u_i_x_bytes, U_i.cmE.to_bytes()?, U_i.u.to_bytes()?, U_i.cmW.to_bytes()?, U_i_x_bytes, + u_i.cmE.to_bytes()?, + u_i.u.to_bytes()?, + u_i.cmW.to_bytes()?, + u_i_x_bytes, cmT.to_bytes()?, // TODO instead of bytes, use field elements, but needs x,y coordinates from // u_i.{cmE,cmW}, U_i.{cmE,cmW}, cmT. Depends exposing x,y coordinates of GC. Issue to @@ -307,16 +274,16 @@ where } /// returns the bytes being compatible with the ark_r1cs_std `.to_bytes` approach -fn point_to_bytes(p: C) -> Vec { +fn point_to_bytes(p: C) -> Result, Error> { let l = p.uncompressed_size(); let mut b = Vec::new(); - p.serialize_uncompressed(&mut b).unwrap(); + p.serialize_uncompressed(&mut b)?; b[l - 1] = 0; if p.is_zero() { b[l / 2] = 1; b[l - 1] = 1; } - b + Ok(b) } /// CycleFoldCircuit contains the constraints that check the correct fold of the committed @@ -326,12 +293,9 @@ fn point_to_bytes(p: C) -> Vec { pub struct CycleFoldCircuit>> { pub _gc: PhantomData, pub r_bits: Option>, - pub cmT: Option, - // u_i,U_i,U_i1 are the nova instances from AugmentedFCircuit which will be (their elliptic - // curve points) checked natively in CycleFoldCircuit - pub u_i: Option>, - pub U_i: Option>, - pub U_i1: Option>, + pub p1: Option, + pub p2: Option, + pub p3: Option, pub x: Option>>, // public inputs (cf_u_{i+1}.x) } impl>> CycleFoldCircuit { @@ -339,10 +303,9 @@ impl>> CycleFoldCircuit { Self { _gc: PhantomData, r_bits: None, - cmT: None, - u_i: None, - U_i: None, - U_i1: None, + p1: None, + p2: None, + p3: None, x: None, } } @@ -358,29 +321,21 @@ where let r_bits: Vec>> = Vec::new_witness(cs.clone(), || { Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO])) })?; - let cmT = GC::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or(C::zero())))?; - - let u_dummy_native = CommittedInstance::::dummy(1); + let p1 = GC::new_witness(cs.clone(), || Ok(self.p1.unwrap_or(C::zero())))?; + let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?; + let p3 = GC::new_witness(cs.clone(), || Ok(self.p3.unwrap_or(C::zero())))?; - let u_i = CommittedInstanceInCycleFoldVar::::new_witness(cs.clone(), || { - Ok(self.u_i.unwrap_or(u_dummy_native.clone())) - })?; - let U_i = CommittedInstanceInCycleFoldVar::::new_witness(cs.clone(), || { - Ok(self.U_i.unwrap_or(u_dummy_native.clone())) - })?; - let U_i1 = CommittedInstanceInCycleFoldVar::::new_witness(cs.clone(), || { - Ok(self.U_i1.unwrap_or(u_dummy_native.clone())) - })?; let _x = Vec::>>::new_input(cs.clone(), || { Ok(self.x.unwrap_or(vec![CF2::::zero(); CF_IO_LEN])) })?; #[cfg(test)] assert_eq!(_x.len(), CF_IO_LEN); // non-constrained sanity check - // fold the original Nova instances natively in CycleFold - let v = - NIFSinCycleFoldGadget::::verify(r_bits.clone(), cmT, u_i.clone(), U_i, U_i1)?; - v.enforce_equal(&Boolean::TRUE)?; + // Fold the original Nova instances natively in CycleFold + // For the cmW we're checking: U_i1.cmW == U_i.cmW + r * u_i.cmW + // For the cmE we're checking: U_i1.cmE == U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE + // is assumed to be 0, so, U_i1.cmE == U_i.cmE + r * cmT + p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?; // check that x == [u_i, U_i, U_{i+1}], check that the cmW & cmW from u_i, U_i, U_{i+1} in // the CycleFoldCircuit are the sames used in the public inputs 'x', which come from the @@ -401,6 +356,7 @@ pub mod tests { use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; + use crate::folding::nova::get_cm_coordinates; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::transcript::poseidon::poseidon_test_config; @@ -427,37 +383,48 @@ pub mod tests { } #[test] - fn test_nifs_gadget_cyclefold() { + fn test_CycleFoldCircuit_constraints() { let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs(); // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field - // (E2::Fr) + // (E1::Fq=E2::Fr) let cs = ConstraintSystem::::new_ref(); - let r_bitsVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); - - let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap(); - let ci1Var = - CommittedInstanceInCycleFoldVar::::new_witness(cs.clone(), || { - Ok(ci1.clone()) - }) - .unwrap(); - let ci2Var = - CommittedInstanceInCycleFoldVar::::new_witness(cs.clone(), || { - Ok(ci2.clone()) - }) - .unwrap(); - let ci3Var = - CommittedInstanceInCycleFoldVar::::new_witness(cs.clone(), || { - Ok(ci3.clone()) - }) - .unwrap(); + let cfW_u_i_x = [ + get_cm_coordinates(&ci1.cmW), + get_cm_coordinates(&ci2.cmW), + get_cm_coordinates(&ci3.cmW), + ] + .concat(); + let cfW_circuit = CycleFoldCircuit:: { + _gc: PhantomData, + r_bits: Some(r_bits.clone()), + p1: Some(ci1.clone().cmW), + p2: Some(ci2.clone().cmW), + p3: Some(ci3.clone().cmW), + x: Some(cfW_u_i_x.clone()), + }; + cfW_circuit.generate_constraints(cs.clone()).unwrap(); + assert!(cs.is_satisfied().unwrap()); + dbg!(cs.num_constraints()); - let nifs_cf_check = NIFSinCycleFoldGadget::::verify( - r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var, - ) - .unwrap(); - nifs_cf_check.enforce_equal(&Boolean::::TRUE).unwrap(); + // same for E: + let cs = ConstraintSystem::::new_ref(); + let cfE_u_i_x = [ + get_cm_coordinates(&ci1.cmE), + get_cm_coordinates(&ci2.cmE), + get_cm_coordinates(&ci3.cmE), + ] + .concat(); + let cfE_circuit = CycleFoldCircuit:: { + _gc: PhantomData, + r_bits: Some(r_bits.clone()), + p1: Some(ci1.clone().cmE), + p2: Some(cmT), + p3: Some(ci3.clone().cmE), + x: Some(cfE_u_i_x.clone()), + }; + cfE_circuit.generate_constraints(cs.clone()).unwrap(); assert!(cs.is_satisfied().unwrap()); } @@ -527,8 +494,8 @@ pub mod tests { // compute the challenge natively let r_bits = CycleFoldChallengeGadget::::get_challenge_native( &poseidon_config, - u_i.clone(), U_i.clone(), + u_i.clone(), cmT, ) .unwrap(); @@ -549,8 +516,8 @@ pub mod tests { let r_bitsVar = CycleFoldChallengeGadget::::get_challenge_gadget( cs.clone(), &poseidon_config, - u_iVar, U_iVar, + u_iVar, cmTVar, ) .unwrap(); diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 6c07a08..4b735e2 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -68,9 +68,7 @@ where let circuit = DeciderEthCircuit::::from_nova::(folding_scheme.into()); - let proof = S::prove(pp, circuit.clone(), &mut rng).unwrap(); - - Ok(proof) + S::prove(pp, circuit.clone(), &mut rng).map_err(|e| Error::Other(e.to_string())) } fn verify( diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 1e6dc7b..b9fa400 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -11,7 +11,6 @@ use ark_r1cs_std::{ fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, - ToConstraintFieldGadget, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::{One, Zero}; @@ -344,19 +343,17 @@ where let zero_x = NonNativeFieldVar::::new_constant( cs.clone(), C1::BaseField::zero(), - )? - .to_constraint_field()?; + )?; let zero_y = NonNativeFieldVar::::new_constant( cs.clone(), C1::BaseField::one(), - )? - .to_constraint_field()?; + )?; (u_i.cmE.x.is_eq(&zero_x)?).enforce_equal(&Boolean::TRUE)?; (u_i.cmE.y.is_eq(&zero_y)?).enforce_equal(&Boolean::TRUE)?; (u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?; // 4. u_i.x == H(i, z_0, z_i, U_i) - let u_i_x = U_i + let (u_i_x, _) = U_i .clone() .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; (u_i.x[0]).enforce_equal(&u_i_x)?; @@ -370,6 +367,7 @@ where // `#[cfg(not(test))]` use crate::commitment::pedersen::PedersenGadget; use crate::folding::nova::cyclefold::{CycleFoldCommittedInstanceVar, CF_IO_LEN}; + use ark_r1cs_std::ToBitsGadget; let cf_u_dummy_native = CommittedInstance::::dummy(CF_IO_LEN); let w_dummy_native = Witness::::new( @@ -386,16 +384,20 @@ where // 5. check Pedersen commitments of cf_U_i.{cmE, cmW} let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?; let G = Vec::::new_constant(cs.clone(), self.cf_pedersen_params.generators)?; + let cf_W_i_E_bits: Result>>>, SynthesisError> = + cf_W_i.E.iter().map(|E_i| E_i.to_bits_le()).collect(); + let cf_W_i_W_bits: Result>>>, SynthesisError> = + cf_W_i.W.iter().map(|W_i| W_i.to_bits_le()).collect(); let computed_cmE = PedersenGadget::::commit( H.clone(), G.clone(), - cf_W_i.E.clone(), - cf_W_i.rE, + cf_W_i_E_bits?, + cf_W_i.rE.to_bits_le()?, )?; cf_U_i.cmE.enforce_equal(&computed_cmE)?; let computed_cmW = - PedersenGadget::::commit(H, G, cf_W_i.W.clone(), cf_W_i.rW)?; + PedersenGadget::::commit(H, G, cf_W_i_W_bits?, cf_W_i.rW.to_bits_le()?)?; cf_U_i.cmW.enforce_equal(&computed_cmW)?; let cf_r1cs = R1CSVar::< @@ -641,17 +643,18 @@ pub mod tests { let ivc_v = nova.clone(); let verifier_params = VerifierParams:: { poseidon_config: poseidon_config.clone(), - r1cs: ivc_v.r1cs, - cf_r1cs: ivc_v.cf_r1cs, + r1cs: ivc_v.clone().r1cs, + cf_r1cs: ivc_v.clone().cf_r1cs, }; + let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances(); NOVA::verify( verifier_params, z_0, ivc_v.z_i, Fr::one(), - (ivc_v.U_i, ivc_v.W_i), - (ivc_v.u_i, ivc_v.w_i), - (ivc_v.cf_U_i, ivc_v.cf_W_i), + running_instance, + incoming_instance, + cyclefold_instance, ) .unwrap(); diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index d7c21bc..13066ee 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -289,17 +289,19 @@ where /// Implements IVC.P of Nova+CycleFold fn prove_step(&mut self) -> Result<(), Error> { let augmented_F_circuit: AugmentedFCircuit; - let cf_circuit: CycleFoldCircuit; + let cfW_circuit: CycleFoldCircuit; + let cfE_circuit: CycleFoldCircuit; let z_i1 = self.F.step_native(self.z_i.clone())?; // compute T and cmT for AugmentedFCircuit let (T, cmT) = self.compute_cmT()?; + // r_bits is the r used to the RLC of the F' instances let r_bits = ChallengeGadget::::get_challenge_native( &self.poseidon_config, - self.u_i.clone(), self.U_i.clone(), + self.u_i.clone(), cmT, )?; let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) @@ -307,7 +309,7 @@ where // fold Nova instances let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::fold_instances( - r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT, + r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &T, cmT, )?; // folded instance output (public input, x) @@ -333,75 +335,65 @@ where cmT: Some(cmT), F: self.F, x: Some(u_i1_x), - cf_u_i: None, + cf1_u_i: None, + cf2_u_i: None, cf_U_i: None, + cf1_U_i1: None, cf_U_i1: None, - cf_cmT: None, - cf_r_nonnat: None, + cf1_cmT: None, + cf2_cmT: None, + cf1_r_nonnat: None, + cf2_r_nonnat: None, }; #[cfg(test)] - NIFS::::verify_folded_instance(r_Fr, &self.u_i, &self.U_i, &U_i1, &cmT)?; + NIFS::::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?; } else { // CycleFold part: // get the vector used as public inputs 'x' in the CycleFold circuit - let cf_u_i_x = [ - get_committed_instance_coordinates(&self.u_i), - get_committed_instance_coordinates(&self.U_i), - get_committed_instance_coordinates(&U_i1), + // cyclefold circuit for cmW + let cfW_u_i_x = [ + get_cm_coordinates(&self.U_i.cmW), + get_cm_coordinates(&self.u_i.cmW), + get_cm_coordinates(&U_i1.cmW), + ] + .concat(); + // cyclefold circuit for cmE + let cfE_u_i_x = [ + get_cm_coordinates(&self.U_i.cmE), + get_cm_coordinates(&self.u_i.cmE), + get_cm_coordinates(&U_i1.cmE), ] .concat(); - cf_circuit = CycleFoldCircuit:: { + cfW_circuit = CycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(r_bits.clone()), - cmT: Some(cmT), - u_i: Some(self.u_i.clone()), - U_i: Some(self.U_i.clone()), - U_i1: Some(U_i1.clone()), - x: Some(cf_u_i_x.clone()), + p1: Some(self.U_i.clone().cmW), + p2: Some(self.u_i.clone().cmW), + p3: Some(U_i1.clone().cmW), + x: Some(cfW_u_i_x.clone()), + }; + cfE_circuit = CycleFoldCircuit:: { + _gc: PhantomData, + r_bits: Some(r_bits.clone()), + p1: Some(self.U_i.clone().cmE), + p2: Some(cmT), + p3: Some(U_i1.clone().cmE), + x: Some(cfE_u_i_x.clone()), }; - let cs2 = ConstraintSystem::::new_ref(); - cf_circuit.generate_constraints(cs2.clone())?; - - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); - if cf_x_i != cf_u_i_x { - return Err(Error::NotEqual); - } - - #[cfg(test)] - if cf_x_i.len() != CF_IO_LEN { - return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN)); - } - - // fold cyclefold instances - let cf_w_i = Witness::::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows); - let cf_u_i: CommittedInstance = - cf_w_i.commit::(&self.cf_cm_params, cf_x_i.clone())?; - - // compute T* and cmT* for CycleFoldCircuit - let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i)?; - - let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( - &self.poseidon_config, - cf_u_i.clone(), - self.cf_U_i.clone(), - cf_cmT, - )?; - let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) - .ok_or(Error::OutOfBounds)?; - - let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( - cf_r_Fq, - &self.cf_W_i, - &self.cf_U_i, - &cf_w_i, - &cf_u_i, - &cf_T, - cf_cmT, - )?; + // fold self.cf_U_i + cfW_U -> folded running with cfW + let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, cfW_r1_Fq) = self + .fold_cyclefold_circuit( + self.cf_W_i.clone(), // CycleFold running instance witness + self.cf_U_i.clone(), // CycleFold running instance + cfW_u_i_x, + cfW_circuit, + )?; + // fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE + let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r2_Fq) = + self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?; augmented_F_circuit = AugmentedFCircuit:: { _gc2: PhantomData, @@ -416,11 +408,15 @@ where F: self.F, x: Some(u_i1_x), // cyclefold values - cf_u_i: Some(cf_u_i.clone()), + cf1_u_i: Some(cfW_u_i.clone()), + cf2_u_i: Some(cfE_u_i.clone()), cf_U_i: Some(self.cf_U_i.clone()), + cf1_U_i1: Some(cfW_U_i1.clone()), cf_U_i1: Some(cf_U_i1.clone()), - cf_cmT: Some(cf_cmT), - cf_r_nonnat: Some(cf_r_Fq), + cf1_cmT: Some(cfW_cmT), + cf2_cmT: Some(cf_cmT), + cf1_r_nonnat: Some(cfW_r1_Fq), + cf2_r_nonnat: Some(cf_r2_Fq), }; self.cf_W_i = cf_W_i1.clone(); @@ -428,7 +424,8 @@ where #[cfg(test)] { - self.cf_r1cs.check_instance_relation(&cf_w_i, &cf_u_i)?; + self.cf_r1cs.check_instance_relation(&_cfW_w_i, &cfW_u_i)?; + self.cf_r1cs.check_instance_relation(&_cfE_w_i, &cfE_u_i)?; self.cf_r1cs .check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?; } @@ -557,18 +554,94 @@ where &self, cf_w_i: &Witness, cf_u_i: &CommittedInstance, + cf_W_i: &Witness, + cf_U_i: &CommittedInstance, ) -> Result<(Vec, C2), Error> { NIFS::::compute_cyclefold_cmT( &self.cf_cm_params, &self.cf_r1cs, cf_w_i, cf_u_i, - &self.cf_W_i, - &self.cf_U_i, + cf_W_i, + cf_U_i, ) } } +impl Nova +where + C1: CurveGroup, + GC1: CurveVar>, + C2: CurveGroup, + GC2: CurveVar>, + FC: FCircuit, + CP1: CommitmentProver, + CP2: CommitmentProver, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + // folds the given cyclefold circuit and its instances + #[allow(clippy::type_complexity)] + fn fold_cyclefold_circuit( + &self, + cf_W_i: Witness, // witness of the running instance + cf_U_i: CommittedInstance, // running instance + cf_u_i_x: Vec, + cf_circuit: CycleFoldCircuit, + ) -> Result< + ( + Witness, + CommittedInstance, // u_i + Witness, // W_i1 + CommittedInstance, // U_i1 + C2, // cmT + C2::ScalarField, // r_Fq + ), + Error, + > { + let cs2 = ConstraintSystem::::new_ref(); + cf_circuit.generate_constraints(cs2.clone())?; + + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); + if cf_x_i != cf_u_i_x { + return Err(Error::NotEqual); + } + + #[cfg(test)] + if cf_x_i.len() != CF_IO_LEN { + return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN)); + } + + // fold cyclefold instances + let cf_w_i = Witness::::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows); + let cf_u_i: CommittedInstance = + cf_w_i.commit::(&self.cf_cm_params, cf_x_i.clone())?; + + // compute T* and cmT* for CycleFoldCircuit + let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i, &cf_W_i, &cf_U_i)?; + + let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( + &self.poseidon_config, + cf_U_i.clone(), + cf_u_i.clone(), + cf_cmT, + )?; + let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) + .ok_or(Error::OutOfBounds)?; + + let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( + cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, + )?; + Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq)) + } +} + /// helper method to get the r1cs from the ConstraintSynthesizer pub fn get_r1cs_from_cs( circuit: impl ConstraintSynthesizer, @@ -633,17 +706,11 @@ where Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows)) } -pub(crate) fn get_committed_instance_coordinates( - u: &CommittedInstance, -) -> Vec { +pub(crate) fn get_cm_coordinates(cm: &C) -> Vec { let zero = (&C::BaseField::zero(), &C::BaseField::one()); - - let cmE = u.cmE.into_affine(); - let (cmE_x, cmE_y) = cmE.xy().unwrap_or(zero); - - let cmW = u.cmW.into_affine(); - let (cmW_x, cmW_y) = cmW.xy().unwrap_or(zero); - vec![*cmE_x, *cmE_y, *cmW_x, *cmW_y] + let cm = cm.into_affine(); + let (cm_x, cm_y) = cm.xy().unwrap_or(zero); + vec![*cm_x, *cm_y] } #[cfg(test)] @@ -656,6 +723,8 @@ pub mod tests { use crate::frontend::tests::CubicFCircuit; use crate::transcript::poseidon::poseidon_test_config; + /// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the + /// AugmentedFCircuit #[test] fn test_ivc() { type NOVA = Nova< @@ -700,17 +769,18 @@ pub mod tests { let verifier_params = VerifierParams:: { poseidon_config, - r1cs: nova.r1cs, - cf_r1cs: nova.cf_r1cs, + r1cs: nova.clone().r1cs, + cf_r1cs: nova.clone().cf_r1cs, }; + let (running_instance, incoming_instance, cyclefold_instance) = nova.instances(); NOVA::verify( verifier_params, z_0, nova.z_i, nova.i, - (nova.U_i, nova.W_i), - (nova.u_i, nova.w_i), - (nova.cf_U_i, nova.cf_W_i), + running_instance, + incoming_instance, + cyclefold_instance, ) .unwrap(); } diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs.rs index 28b8fa9..aae042b 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs.rs @@ -68,8 +68,8 @@ where pub fn fold_committed_instance( r: C::ScalarField, - ci1: &CommittedInstance, - ci2: &CommittedInstance, + ci1: &CommittedInstance, // U_i + ci2: &CommittedInstance, // u_i cmT: &C, ) -> CommittedInstance { let r2 = r * r; diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index 4e1d6de..9d1d12e 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -104,7 +104,8 @@ where // returns the state at the current step fn state(&self) -> Vec; - // returns the instances at the current step + // returns the instances at the current step, in the following order: + // (running_instance, incoming_instance, cyclefold_instance) fn instances( &self, ) -> ( diff --git a/folding-schemes/src/utils/gadgets.rs b/folding-schemes/src/utils/gadgets.rs index dc0db11..dc6aa9c 100644 --- a/folding-schemes/src/utils/gadgets.rs +++ b/folding-schemes/src/utils/gadgets.rs @@ -15,6 +15,11 @@ pub fn mat_vec_mul_sparse>( let mut res = vec![FV::zero(); m.n_rows]; for (row_i, row) in m.coeffs.iter().enumerate() { for (value, col_i) in row.iter() { + if value.value().unwrap() == F::one() { + // no need to multiply by 1 + res[row_i] += v[*col_i].clone(); + continue; + } res[row_i] += value.clone().mul(&v[*col_i].clone()); } }