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()); } }