/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). use ark_crypto_primitives::{ crh::{poseidon::CRH, CRHScheme}, sponge::{poseidon::PoseidonConfig, Absorb}, }; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; use ark_std::fmt::Debug; use ark_std::{One, Zero}; use core::marker::PhantomData; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::nonnative::{ nonnative_affine_to_field_elements, nonnative_field_to_field_elements, }; use crate::frontend::FCircuit; use crate::utils::vec::is_zero_vec; use crate::Error; use crate::FoldingScheme; pub mod circuits; pub mod cyclefold; pub mod decider_eth; pub mod decider_eth_circuit; pub mod nifs; pub mod traits; use circuits::{AugmentedFCircuit, ChallengeGadget, CF2}; use cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit}; use nifs::NIFS; use traits::NovaR1CS; #[cfg(test)] use cyclefold::CF_IO_LEN; #[derive(Debug, Clone, Eq, PartialEq)] pub struct CommittedInstance { pub cmE: C, pub u: C::ScalarField, pub cmW: C, pub x: Vec, } impl CommittedInstance { pub fn dummy(io_len: usize) -> Self { Self { cmE: C::zero(), u: C::ScalarField::zero(), cmW: C::zero(), x: vec![C::ScalarField::zero(); io_len], } } } impl CommittedInstance where ::ScalarField: Absorb, ::BaseField: ark_ff::PrimeField, { /// hash implements the committed instance hash compatible with the gadget implemented in /// nova/circuits.rs::CommittedInstanceVar.hash. /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the /// `CommittedInstance`. pub fn hash( &self, poseidon_config: &PoseidonConfig, i: C::ScalarField, z_0: Vec, z_i: Vec, ) -> Result { let (cmE_x, cmE_y) = nonnative_affine_to_field_elements::(self.cmE)?; let (cmW_x, cmW_y) = nonnative_affine_to_field_elements::(self.cmW)?; CRH::::evaluate( poseidon_config, vec![ vec![i], z_0, z_i, vec![self.u], self.x.clone(), cmE_x, cmE_y, cmW_x, cmW_y, ] .concat(), ) .map_err(|e| Error::Other(e.to_string())) } } impl ToConstraintField for CommittedInstance where ::BaseField: ark_ff::PrimeField + Absorb, { fn to_field_elements(&self) -> Option> { let u = nonnative_field_to_field_elements(&self.u); let x = self .x .iter() .flat_map(nonnative_field_to_field_elements) .collect::>(); let (cmE_x, cmE_y, cmE_is_inf) = match self.cmE.into_affine().xy() { Some((&x, &y)) => (x, y, C::BaseField::zero()), None => ( C::BaseField::zero(), C::BaseField::zero(), C::BaseField::one(), ), }; let (cmW_x, cmW_y, cmW_is_inf) = match self.cmW.into_affine().xy() { Some((&x, &y)) => (x, y, C::BaseField::zero()), None => ( C::BaseField::zero(), C::BaseField::zero(), C::BaseField::one(), ), }; // Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate in the corresponding circuit let is_inf = cmE_is_inf.double() + cmW_is_inf; Some([u, x, vec![cmE_x, cmE_y, cmW_x, cmW_y, is_inf]].concat()) } } impl CommittedInstance where ::BaseField: ark_ff::PrimeField + Absorb, { /// hash_cyclefold implements the committed instance hash compatible with the gadget implemented in /// nova/cyclefold.rs::CycleFoldCommittedInstanceVar.hash. /// Returns `H(U_i)`, where `U_i` is the `CommittedInstance` for CycleFold. pub fn hash_cyclefold( &self, poseidon_config: &PoseidonConfig, ) -> Result { CRH::::evaluate(poseidon_config, self.to_field_elements().unwrap()) .map_err(|e| Error::Other(e.to_string())) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Witness { pub E: Vec, pub rE: C::ScalarField, pub W: Vec, pub rW: C::ScalarField, } impl Witness where ::ScalarField: Absorb, { pub fn new(w: Vec, e_len: usize) -> Self { // note: at the current version, we don't use the blinding factors and we set them to 0 // always. Self { E: vec![C::ScalarField::zero(); e_len], rE: C::ScalarField::zero(), W: w, rW: C::ScalarField::zero(), } } pub fn commit>( &self, params: &CS::ProverParams, x: Vec, ) -> Result, Error> { let mut cmE = C::zero(); if !is_zero_vec::(&self.E) { cmE = CS::commit(params, &self.E, &self.rE)?; } let cmW = CS::commit(params, &self.W, &self.rW)?; Ok(CommittedInstance { cmE, u: C::ScalarField::one(), cmW, x, }) } } #[derive(Debug, Clone)] pub struct ProverParams where C1: CurveGroup, C2: CurveGroup, CS1: CommitmentScheme, CS2: CommitmentScheme, { pub poseidon_config: PoseidonConfig, pub cs_params: CS1::ProverParams, pub cf_cs_params: CS2::ProverParams, } #[derive(Debug, Clone)] pub struct VerifierParams { pub poseidon_config: PoseidonConfig, pub r1cs: R1CS, pub cf_r1cs: R1CS, } /// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait #[derive(Clone, Debug)] pub struct Nova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit, CS1: CommitmentScheme, CS2: CommitmentScheme, { _gc1: PhantomData, _c2: PhantomData, _gc2: PhantomData, /// R1CS of the Augmented Function circuit pub r1cs: R1CS, /// R1CS of the CycleFold circuit pub cf_r1cs: R1CS, pub poseidon_config: PoseidonConfig, /// CommitmentScheme::ProverParams over C1 pub cs_params: CS1::ProverParams, /// CycleFold CommitmentScheme::ProverParams, over C2 pub cf_cs_params: CS2::ProverParams, /// F circuit, the circuit that is being folded pub F: FC, pub i: C1::ScalarField, /// initial state pub z_0: Vec, /// current i-th state pub z_i: Vec, /// Nova instances pub w_i: Witness, pub u_i: CommittedInstance, pub W_i: Witness, pub U_i: CommittedInstance, /// CycleFold running instance pub cf_W_i: Witness, pub cf_U_i: CommittedInstance, } impl FoldingScheme for Nova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, FC: FCircuit, CS1: CommitmentScheme, CS2: CommitmentScheme, ::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>, { type PreprocessorParam = (Self::ProverParam, FC); type ProverParam = ProverParams; type VerifierParam = VerifierParams; type CommittedInstanceWithWitness = (CommittedInstance, Witness); type CFCommittedInstanceWithWitness = (CommittedInstance, Witness); fn preprocess( prep_param: &Self::PreprocessorParam, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { let (prover_params, F_circuit) = prep_param; let (r1cs, cf_r1cs) = get_r1cs::(&prover_params.poseidon_config, F_circuit.clone())?; let verifier_params = VerifierParams:: { poseidon_config: prover_params.poseidon_config.clone(), r1cs, cf_r1cs, }; Ok((prover_params.clone(), verifier_params)) } /// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`. fn init(pp: &Self::ProverParam, F: FC, z_0: Vec) -> Result { // prepare the circuit to obtain its R1CS let cs = ConstraintSystem::::new_ref(); let cs2 = ConstraintSystem::::new_ref(); let augmented_F_circuit = AugmentedFCircuit::::empty(&pp.poseidon_config, F.clone()); let cf_circuit = CycleFoldCircuit::::empty(); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize(); let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let r1cs = extract_r1cs::(&cs); cf_circuit.generate_constraints(cs2.clone())?; cs2.finalize(); let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let cf_r1cs = extract_r1cs::(&cs2); // setup the dummy instances let (w_dummy, u_dummy) = r1cs.dummy_instance(); let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance(); // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the // R1CS that we're working with. Ok(Self { _gc1: PhantomData, _c2: PhantomData, _gc2: PhantomData, r1cs, cf_r1cs, poseidon_config: pp.poseidon_config.clone(), cs_params: pp.cs_params.clone(), cf_cs_params: pp.cf_cs_params.clone(), F, i: C1::ScalarField::zero(), z_0: z_0.clone(), z_i: z_0, w_i: w_dummy.clone(), u_i: u_dummy.clone(), W_i: w_dummy, U_i: u_dummy, // cyclefold running instance cf_W_i: cf_w_dummy.clone(), cf_U_i: cf_u_dummy.clone(), }) } /// Implements IVC.P of Nova+CycleFold fn prove_step(&mut self) -> Result<(), Error> { let augmented_F_circuit: AugmentedFCircuit; if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) { return Err(Error::MaxStep); } let mut i_bytes: [u8; 8] = [0; 8]; i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]); let i_usize: usize = usize::from_le_bytes(i_bytes); let z_i1 = self.F.step_native(i_usize, 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(), cmT, )?; let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; // 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, )?; // folded instance output (public input, x) // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1}) let u_i1_x = U_i1.hash( &self.poseidon_config, self.i + C1::ScalarField::one(), self.z_0.clone(), z_i1.clone(), )?; // u_{i+1}.x[1] = H(cf_U_{i+1}) let cf_u_i1_x: C1::ScalarField; if self.i == C1::ScalarField::zero() { cf_u_i1_x = self.cf_U_i.hash_cyclefold(&self.poseidon_config)?; // base case augmented_F_circuit = AugmentedFCircuit:: { _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), i: Some(C1::ScalarField::zero()), // = i=0 i_usize: Some(0), z_0: Some(self.z_0.clone()), // = z_i z_i: Some(self.z_i.clone()), u_i_cmW: Some(self.u_i.cmW), // = dummy U_i: Some(self.U_i.clone()), // = dummy U_i1_cmE: Some(U_i1.cmE), U_i1_cmW: Some(U_i1.cmW), cmT: Some(cmT), F: self.F.clone(), x: Some(u_i1_x), cf1_u_i_cmW: None, cf2_u_i_cmW: None, cf_U_i: None, cf1_cmT: None, cf2_cmT: None, cf_x: Some(cf_u_i1_x), }; #[cfg(test)] 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 // cyclefold circuit for cmW let cfW_u_i_x = [ vec![r_Fq], 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 = [ vec![r_Fq], get_cm_coordinates(&self.U_i.cmE), get_cm_coordinates(&cmT), get_cm_coordinates(&U_i1.cmE), ] .concat(); let cfW_circuit = CycleFoldCircuit:: { _gc: PhantomData, r_bits: Some(r_bits.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()), }; let 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()), }; // 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, _) = 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, _) = self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?; cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?; augmented_F_circuit = AugmentedFCircuit:: { _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), i: Some(self.i), i_usize: Some(i_usize), z_0: Some(self.z_0.clone()), z_i: Some(self.z_i.clone()), u_i_cmW: Some(self.u_i.cmW), U_i: Some(self.U_i.clone()), U_i1_cmE: Some(U_i1.cmE), U_i1_cmW: Some(U_i1.cmW), cmT: Some(cmT), F: self.F.clone(), x: Some(u_i1_x), // cyclefold values cf1_u_i_cmW: Some(cfW_u_i.cmW), cf2_u_i_cmW: Some(cfE_u_i.cmW), cf_U_i: Some(self.cf_U_i.clone()), cf1_cmT: Some(cfW_cmT), cf2_cmT: Some(cf_cmT), cf_x: Some(cf_u_i1_x), }; self.cf_W_i = cf_W_i1.clone(); self.cf_U_i = cf_U_i1.clone(); #[cfg(test)] { 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)?; } } let cs = ConstraintSystem::::new_ref(); augmented_F_circuit.generate_constraints(cs.clone())?; #[cfg(test)] assert!(cs.is_satisfied().unwrap()); let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let (w_i1, x_i1) = extract_w_x::(&cs); if x_i1[0] != u_i1_x || x_i1[1] != cf_u_i1_x { return Err(Error::NotEqual); } #[cfg(test)] if x_i1.len() != 2 { return Err(Error::NotExpectedLength(x_i1.len(), 2)); } // set values for next iteration self.i += C1::ScalarField::one(); self.z_i = z_i1; self.w_i = Witness::::new(w_i1, self.r1cs.A.n_rows); self.u_i = self.w_i.commit::(&self.cs_params, x_i1)?; self.W_i = W_i1; self.U_i = U_i1; #[cfg(test)] { self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?; self.r1cs .check_relaxed_instance_relation(&self.W_i, &self.U_i)?; } Ok(()) } fn state(&self) -> Vec { self.z_i.clone() } fn instances( &self, ) -> ( Self::CommittedInstanceWithWitness, Self::CommittedInstanceWithWitness, Self::CFCommittedInstanceWithWitness, ) { ( (self.U_i.clone(), self.W_i.clone()), (self.u_i.clone(), self.w_i.clone()), (self.cf_U_i.clone(), self.cf_W_i.clone()), ) } /// Implements IVC.V of Nova+CycleFold fn verify( vp: Self::VerifierParam, z_0: Vec, // initial state z_i: Vec, // last state num_steps: C1::ScalarField, running_instance: Self::CommittedInstanceWithWitness, incoming_instance: Self::CommittedInstanceWithWitness, cyclefold_instance: Self::CFCommittedInstanceWithWitness, ) -> Result<(), Error> { let (U_i, W_i) = running_instance; let (u_i, w_i) = incoming_instance; let (cf_U_i, cf_W_i) = cyclefold_instance; if u_i.x.len() != 2 || U_i.x.len() != 2 { return Err(Error::IVCVerificationFail); } // check that u_i's output points to the running instance // u_i.X[0] == H(i, z_0, z_i, U_i) let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?; if expected_u_i_x != u_i.x[0] { return Err(Error::IVCVerificationFail); } // u_i.X[1] == H(cf_U_i) let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config)?; if expected_cf_u_i_x != u_i.x[1] { return Err(Error::IVCVerificationFail); } // check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance) if !u_i.cmE.is_zero() || !u_i.u.is_one() { return Err(Error::IVCVerificationFail); } // check R1CS satisfiability vp.r1cs.check_instance_relation(&w_i, &u_i)?; // check RelaxedR1CS satisfiability vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?; // check CycleFold RelaxedR1CS satisfiability vp.cf_r1cs .check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?; Ok(()) } } impl Nova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit, CS1: CommitmentScheme, CS2: CommitmentScheme, ::BaseField: PrimeField, ::ScalarField: Absorb, ::ScalarField: Absorb, C1: CurveGroup, { // computes T and cmT for the AugmentedFCircuit fn compute_cmT(&self) -> Result<(Vec, C1), Error> { NIFS::::compute_cmT( &self.cs_params, &self.r1cs, &self.w_i, &self.u_i, &self.W_i, &self.U_i, ) } // computes T* and cmT* for the CycleFoldCircuit fn compute_cf_cmT( &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_cs_params, &self.cf_r1cs, cf_w_i, cf_u_i, cf_W_i, cf_U_i, ) } } impl Nova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, FC: FCircuit, CS1: CommitmentScheme, CS2: CommitmentScheme, ::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_cs_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, ) -> Result, Error> { let cs = ConstraintSystem::::new_ref(); circuit.generate_constraints(cs.clone())?; cs.finalize(); let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; let r1cs = extract_r1cs::(&cs); Ok(r1cs) } /// helper method to get the R1CS for both the AugmentedFCircuit and the CycleFold circuit #[allow(clippy::type_complexity)] pub fn get_r1cs( poseidon_config: &PoseidonConfig, F_circuit: FC, ) -> Result<(R1CS, R1CS), Error> where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, FC: FCircuit, ::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>, { let augmented_F_circuit = AugmentedFCircuit::::empty(poseidon_config, F_circuit); let cf_circuit = CycleFoldCircuit::::empty(); let r1cs = get_r1cs_from_cs::(augmented_F_circuit)?; let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; Ok((r1cs, cf_r1cs)) } /// helper method to get the pedersen params length for both the AugmentedFCircuit and the /// CycleFold circuit pub fn get_cs_params_len( poseidon_config: &PoseidonConfig, F_circuit: FC, ) -> Result<(usize, usize), Error> where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, FC: FCircuit, ::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>, { let (r1cs, cf_r1cs) = get_r1cs::(poseidon_config, F_circuit)?; Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows)) } /// returns the coordinates of a commitment point. This is compatible with the arkworks /// GC.to_constraint_field()[..2] pub(crate) fn get_cm_coordinates(cm: &C) -> Vec { let zero = (&C::BaseField::zero(), &C::BaseField::zero()); let cm = cm.into_affine(); let (cm_x, cm_y) = cm.xy().unwrap_or(zero); vec![*cm_x, *cm_y] } #[cfg(test)] pub mod tests { use super::*; use crate::commitment::kzg::{ProverKey as KZGProverKey, KZG}; use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; use crate::commitment::pedersen::Pedersen; 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() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); let F_circuit = CubicFCircuit::::new(()); let (cs_len, cf_cs_len) = get_cs_params_len::>( &poseidon_config, F_circuit, ) .unwrap(); let (kzg_pk, _): (KZGProverKey, KZGVerifierKey) = KZG::::setup(&mut rng, cs_len).unwrap(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, cs_len).unwrap(); let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cs_len).unwrap(); // run the test using Pedersen commitments on both sides of the curve cycle test_ivc_opt::, Pedersen>( poseidon_config.clone(), pedersen_params, cf_pedersen_params.clone(), F_circuit, ); // run the test using KZG for the commitments on the main curve, and Pedersen for the // commitments on the secondary curve test_ivc_opt::, Pedersen>( poseidon_config, kzg_pk, cf_pedersen_params, F_circuit, ); } // test_ivc allowing to choose the CommitmentSchemes fn test_ivc_opt, CS2: CommitmentScheme>( poseidon_config: PoseidonConfig, cs_params: CS1::ProverParams, cf_cs_params: CS2::ProverParams, F_circuit: CubicFCircuit, ) { type NOVA = Nova, CS1, CS2>; let prover_params = ProverParams:: { poseidon_config: poseidon_config.clone(), cs_params, cf_cs_params, }; let z_0 = vec![Fr::from(3_u32)]; let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); let num_steps: usize = 3; for _ in 0..num_steps { nova.prove_step().unwrap(); } assert_eq!(Fr::from(num_steps as u32), nova.i); let verifier_params = VerifierParams:: { poseidon_config, 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, running_instance, incoming_instance, cyclefold_instance, ) .unwrap(); } }