Generalized CycleFold (#120)
* Support randomness of arbitrary length
* Rename `N_BITS_RO` to `NOVA_N_BITS_RO`
* Compute `r_nonnat` inside `NIFSFullGadget::fold_committed_instance`
* Format
* Use `CycleFold{CommittedInstance, Witness}` in the context of cyclefold
* Format
* Fix the creation of dummy witness
* Make clippy happy
* Improve docs
jump to
@@ -2,4 +2,4 @@ // used for the RO challenges.
// From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness // error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this // case, we keep the size of S close to 2^128. -pub const N_BITS_RO: usize = 128; +pub const NOVA_N_BITS_RO: usize = 128;
@@ -1,8 +1,8 @@
/// Contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits and functions that /// are shared across the different folding schemes use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge}; -use ark_ec::{CurveGroup, Group}; -use ark_ff::{BigInteger, PrimeField}; +use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ff::{BigInteger, Field, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean,@@ -20,25 +20,22 @@ use ark_std::rand::RngCore;
use ark_std::Zero; use core::{borrow::Borrow, marker::PhantomData}; -use super::{nonnative::uint::NonNativeUintVar, CF2}; +use super::{nonnative::uint::NonNativeUintVar, CF1, CF2}; use crate::arith::r1cs::{extract_w_x, R1CS}; use crate::commitment::CommitmentScheme; -use crate::constants::N_BITS_RO; -use crate::folding::nova::{nifs::NIFS, CommittedInstance, Witness}; -use crate::frontend::FCircuit; -use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; +use crate::constants::NOVA_N_BITS_RO; +use crate::folding::nova::nifs::NIFS; +use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; use crate::Error; -/// Public inputs length for the CycleFoldCircuit: -/// For Nova this is: |[r, p1.x,y, p2.x,y, p3.x,y]| -/// In general, |[r * (n_points-1), (p_i.x,y)*n_points, p_folded.x,y]|, thus, io len is: -/// (n_points-1) + 2*n_points + 2 -pub fn cf_io_len(n_points: usize) -> usize { - (n_points - 1) + 2 * n_points + 2 -} +/// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and +/// witness as `CycleFoldWitness`, for clarity and consistency +pub use crate::folding::nova::{ + CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness, +}; -/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova -/// circuit. +/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance represented +/// in folding verifier circuit #[derive(Debug, Clone)] pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>> where@@ -49,14 +46,14 @@ pub u: NonNativeUintVar<CF2<C>>,
pub cmW: GC, pub x: Vec<NonNativeUintVar<CF2<C>>>, } -impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC> +impl<C, GC> AllocVar<CycleFoldCommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC> where C: CurveGroup, GC: CurveVar<C, CF2<C>>, <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - fn new_variable<T: Borrow<CommittedInstance<C>>>( + fn new_variable<T: Borrow<CycleFoldCommittedInstance<C>>>( cs: impl Into<Namespace<CF2<C>>>, f: impl FnOnce() -> Result<T, SynthesisError>, mode: AllocationMode,@@ -74,6 +71,29 @@ })
} } +impl<C: CurveGroup> AbsorbNonNative<C::BaseField> for CycleFoldCommittedInstance<C> +where + C::BaseField: PrimeField + Absorb, +{ + // Compatible with the in-circuit `CycleFoldCommittedInstanceVar::to_native_sponge_field_elements` + fn to_native_sponge_field_elements(&self, dest: &mut Vec<C::BaseField>) { + [self.u].to_native_sponge_field_elements(dest); + self.x.to_native_sponge_field_elements(dest); + let (cmE_x, cmE_y) = match self.cmE.into_affine().xy() { + Some((&x, &y)) => (x, y), + None => (C::BaseField::zero(), C::BaseField::zero()), + }; + let (cmW_x, cmW_y) = match self.cmW.into_affine().xy() { + Some((&x, &y)) => (x, y), + None => (C::BaseField::zero(), C::BaseField::zero()), + }; + cmE_x.to_sponge_field_elements(dest); + cmE_y.to_sponge_field_elements(dest); + cmW_x.to_sponge_field_elements(dest); + cmW_y.to_sponge_field_elements(dest); + } +} + impl<C, GC> AbsorbNonNativeGadget<C::BaseField> for CycleFoldCommittedInstanceVar<C, GC> where C: CurveGroup,@@ -107,6 +127,25 @@ .concat())
} } +impl<C: CurveGroup> CycleFoldCommittedInstance<C> +where + <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb, +{ + /// hash_cyclefold implements the committed instance hash compatible with the + /// in-circuit implementation `CycleFoldCommittedInstanceVar::hash`. + /// Returns `H(U_i)`, where `U_i` is a `CycleFoldCommittedInstance`. + pub fn hash_cyclefold<T: Transcript<C::BaseField>>( + &self, + sponge: &T, + pp_hash: C::BaseField, // public params hash + ) -> C::BaseField { + let mut sponge = sponge.clone(); + sponge.absorb(&pp_hash); + sponge.absorb_nonnative(self); + sponge.squeeze_field_elements(1)[0] + } +} + impl<C, GC> CycleFoldCommittedInstanceVar<C, GC> where C: CurveGroup,@@ -114,11 +153,13 @@ GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - /// hash implements the committed instance hash compatible with the native implementation from - /// CommittedInstance.hash_cyclefold. Returns `H(U_i)`, where `U` is the `CommittedInstance` - /// for CycleFold. 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. + /// hash implements the committed instance hash compatible with the native + /// implementation `CycleFoldCommittedInstance::hash_cyclefold`. + /// Returns `H(U_i)`, where `U` is a `CycleFoldCommittedInstanceVar`. + /// + /// Additionally it returns the vector of the field elements from the self + /// parameters, so they can be reused in other gadgets without recalculating + /// (reconstraining) them. #[allow(clippy::type_complexity)] pub fn hash<S: CryptographicSponge, T: TranscriptVar<CF2<C>, S>>( self,@@ -147,13 +188,14 @@ pub cmE: GC,
pub cmW: GC, } -impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceInCycleFoldVar<C, GC> +impl<C, GC> AllocVar<CycleFoldCommittedInstance<C>, CF2<C>> + for CommittedInstanceInCycleFoldVar<C, GC> where C: CurveGroup, GC: CurveVar<C, CF2<C>>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - fn new_variable<T: Borrow<CommittedInstance<C>>>( + fn new_variable<T: Borrow<CycleFoldCommittedInstance<C>>>( cs: impl Into<Namespace<CF2<C>>>, f: impl FnOnce() -> Result<T, SynthesisError>, mode: AllocationMode,@@ -189,25 +231,29 @@ <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { pub fn fold_committed_instance( - // assumes that r_bits is equal to r_nonnat just that in a different format r_bits: Vec<Boolean<CF2<C>>>, - r_nonnat: NonNativeUintVar<CF2<C>>, cmT: GC, ci1: CycleFoldCommittedInstanceVar<C, GC>, // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) ci2: CycleFoldCommittedInstanceVar<C, GC>, ) -> Result<CycleFoldCommittedInstanceVar<C, GC>, SynthesisError> { + // r_nonnat is equal to r_bits just that in a different format + let r_nonnat = { + let mut bits = r_bits.clone(); + bits.resize(CF1::<C>::MODULUS_BIT_SIZE as usize, Boolean::FALSE); + NonNativeUintVar::from(&bits) + }; Ok(CycleFoldCommittedInstanceVar { cmE: cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE, cmW: ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?, - u: ci1.u.add_no_align(&r_nonnat).modulo::<C::ScalarField>()?, + u: ci1.u.add_no_align(&r_nonnat).modulo::<CF1<C>>()?, x: ci1 .x .iter() .zip(ci2.x) .map(|(a, b)| { a.add_no_align(&r_nonnat.mul_no_align(&b)?) - .modulo::<C::ScalarField>() + .modulo::<CF1<C>>() }) .collect::<Result<Vec<_>, _>>()?, })@@ -216,14 +262,13 @@
pub fn verify( // assumes that r_bits is equal to r_nonnat just that in a different format r_bits: Vec<Boolean<CF2<C>>>, - r_nonnat: NonNativeUintVar<CF2<C>>, cmT: GC, ci1: CycleFoldCommittedInstanceVar<C, GC>, // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) ci2: CycleFoldCommittedInstanceVar<C, GC>, ci3: CycleFoldCommittedInstanceVar<C, GC>, ) -> Result<(), SynthesisError> { - let ci = Self::fold_committed_instance(r_bits, r_nonnat, cmT, ci1, ci2)?; + let ci = Self::fold_committed_instance(r_bits, cmT, ci1, ci2)?; ci.cmE.enforce_equal(&ci3.cmE)?; ci.u.enforce_equal_unaligned(&ci3.u)?;@@ -253,15 +298,15 @@ {
pub fn get_challenge_native<T: Transcript<C::BaseField>>( transcript: &mut T, pp_hash: C::BaseField, // public params hash - U_i: CommittedInstance<C>, - u_i: CommittedInstance<C>, + U_i: CycleFoldCommittedInstance<C>, + u_i: CycleFoldCommittedInstance<C>, cmT: C, ) -> Vec<bool> { transcript.absorb(&pp_hash); transcript.absorb_nonnative(&U_i); transcript.absorb_nonnative(&u_i); transcript.absorb_point(&cmT); - transcript.squeeze_bits(N_BITS_RO) + transcript.squeeze_bits(NOVA_N_BITS_RO) } // compatible with the native get_challenge_native@@ -276,63 +321,101 @@ transcript.absorb(&pp_hash)?;
transcript.absorb(&U_i_vec)?; transcript.absorb_nonnative(&u_i)?; transcript.absorb_point(&cmT)?; - transcript.squeeze_bits(N_BITS_RO) + transcript.squeeze_bits(NOVA_N_BITS_RO) } } +pub trait CycleFoldConfig { + /// `N_INPUT_POINTS` specifies the number of input points that are folded in + /// [`CycleFoldCircuit`] via random linear combinations. + const N_INPUT_POINTS: usize; + /// `RANDOMNESS_BIT_LENGTH` is the (maximum) bit length of randomness `r`. + const RANDOMNESS_BIT_LENGTH: usize; + /// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a + /// field element. + /// + /// E.g., given a randomness `r` with `RANDOMNESS_BIT_LENGTH` bits, we need + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent `r` + /// compactly in-circuit. + const FIELD_CAPACITY: usize = CF2::<Self::C>::MODULUS_BIT_SIZE as usize - 1; + + /// Public inputs length for the CycleFoldCircuit. + /// * For Nova this is: `|[r, p1.x,y, p2.x,y, p3.x,y]|` + /// * In general, `|[r * (n_points-1), (p_i.x,y)*n_points, p_folded.x,y]|`. + /// + /// Thus, `IO_LEN` is: + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY * (N_INPUT_POINTS - 1) + 2 * N_INPUT_POINTS + 2` + const IO_LEN: usize = { + Self::RANDOMNESS_BIT_LENGTH.div_ceil(Self::FIELD_CAPACITY) * (Self::N_INPUT_POINTS - 1) + + 2 * Self::N_INPUT_POINTS + + 2 + }; + + type F: Field; + type C: CurveGroup<BaseField = Self::F>; +} + /// CycleFoldCircuit contains the constraints that check the correct fold of the committed /// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve /// (Curve1) points of u_i, U_i leading to U_{i+1} #[derive(Debug, Clone)] -pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> { +pub struct CycleFoldCircuit<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> { pub _gc: PhantomData<GC>, - /// number of points being folded - pub n_points: usize, /// r_bits is a vector containing the r_bits, one for each point except for the first one. They /// are used for the scalar multiplication of the points. The r_bits are the bit /// representation of each power of r (in Fr, while the CycleFoldCircuit is in Fq). pub r_bits: Option<Vec<Vec<bool>>>, /// points to be folded in the CycleFoldCircuit - pub points: Option<Vec<C>>, - pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x) + pub points: Option<Vec<CFG::C>>, + /// public inputs (cf_u_{i+1}.x) + pub x: Option<Vec<CFG::F>>, } -impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> { + +impl<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> CycleFoldCircuit<CFG, GC> { /// n_points indicates the number of points being folded in the CycleFoldCircuit - pub fn empty(n_points: usize) -> Self { + pub fn empty() -> Self { Self { _gc: PhantomData, - n_points, r_bits: None, points: None, x: None, } } } -impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<C, GC> + +impl<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> ConstraintSynthesizer<CFG::F> + for CycleFoldCircuit<CFG, GC> where - C: CurveGroup, - GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>, - <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, - for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, + GC: ToConstraintFieldGadget<CFG::F>, + CFG::F: PrimeField, + for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>, { - fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> { - let r_bits: Vec<Vec<Boolean<CF2<C>>>> = self + fn generate_constraints(self, cs: ConstraintSystemRef<CFG::F>) -> Result<(), SynthesisError> { + let r_bits: Vec<Vec<Boolean<CFG::F>>> = self .r_bits // n_points-1, bcs is one for each point except for the first one - .unwrap_or(vec![vec![false; N_BITS_RO]; self.n_points - 1]) + .unwrap_or(vec![ + vec![false; CFG::RANDOMNESS_BIT_LENGTH]; + CFG::N_INPUT_POINTS - 1 + ]) .iter() .map(|r_bits_i| { - Vec::<Boolean<CF2<C>>>::new_witness(cs.clone(), || Ok(r_bits_i.clone())) + Vec::<Boolean<CFG::F>>::new_witness(cs.clone(), || Ok(r_bits_i.clone())) }) - .collect::<Result<Vec<Vec<Boolean<CF2<C>>>>, SynthesisError>>()?; + .collect::<Result<_, _>>()?; let points = Vec::<GC>::new_witness(cs.clone(), || { - Ok(self.points.unwrap_or(vec![C::zero(); self.n_points])) + Ok(self + .points + .unwrap_or(vec![CFG::C::zero(); CFG::N_INPUT_POINTS])) })?; #[cfg(test)] { - assert_eq!(self.n_points, points.len()); - assert_eq!(self.n_points - 1, r_bits.len()); + assert_eq!(CFG::N_INPUT_POINTS, points.len()); + assert_eq!(CFG::N_INPUT_POINTS - 1, r_bits.len()); + for r_bits_i in &r_bits { + assert_eq!(r_bits_i.len(), CFG::RANDOMNESS_BIT_LENGTH); + } } // Fold the original points of the instances natively in CycleFold.@@ -343,36 +426,37 @@ // is assumed to be 0, so, U_i1.cmE = U_i.cmE + r * cmT
let mut p_folded: GC = points[0].clone(); // iter over n_points-1 because the first point is not multiplied by r^i (it is multiplied // by r^0=1) - for i in 0..self.n_points - 1 { + for i in 0..CFG::N_INPUT_POINTS - 1 { p_folded += points[i + 1].scalar_mul_le(r_bits[i].iter())?; } - let x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || { - Ok(self - .x - .unwrap_or(vec![CF2::<C>::zero(); cf_io_len(self.n_points)])) + let x = Vec::<FpVar<CFG::F>>::new_input(cs.clone(), || { + Ok(self.x.unwrap_or(vec![CFG::F::zero(); CFG::IO_LEN])) })?; #[cfg(test)] - assert_eq!(x.len(), cf_io_len(self.n_points)); // non-constrained sanity check + assert_eq!(x.len(), CFG::IO_LEN); // non-constrained sanity check // Check that the points coordinates are placed as the public input x: // In Nova, this is: x == [r, p1, p2, p3] (wheere p3 is the p_folded). // In multifolding schemes such as HyperNova, this is: // computed_x = [r_0, r_1, r_2, ..., r_n, p_0, p_1, p_2, ..., p_n, p_folded], // where each p_i is in fact p_i.to_constraint_field() - let computed_x: Vec<FpVar<CF2<C>>> = [ - r_bits - .iter() - .map(|r_bits_i| Boolean::le_bits_to_fp_var(r_bits_i)) - .collect::<Result<Vec<FpVar<CF2<C>>>, SynthesisError>>()?, - points - .iter() - .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())) - .collect::<Result<Vec<Vec<FpVar<CF2<C>>>>, SynthesisError>>()? - .concat(), - p_folded.to_constraint_field()?[..2].to_vec(), - ] - .concat(); + let computed_x: Vec<FpVar<CFG::F>> = r_bits + .iter() + .map(|r_bits_i| { + r_bits_i + .chunks(CFG::FIELD_CAPACITY) + .map(Boolean::le_bits_to_fp_var) + .collect::<Result<Vec<_>, _>>() + }) + .chain( + points + .iter() + .chain(&[p_folded]) + .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())), + ) + .collect::<Result<Vec<_>, _>>()? + .concat(); computed_x.enforce_equal(&x)?; Ok(())@@ -383,35 +467,33 @@ /// Folds the given cyclefold circuit and its instances. This method is abstracted from any folding
/// scheme struct because it is used both by Nova & HyperNova's CycleFold. #[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] -pub fn fold_cyclefold_circuit<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool>( - _n_points: usize, +pub fn fold_cyclefold_circuit<CFG, C1, GC1, C2, GC2, CS2, const H: bool>( transcript: &mut impl Transcript<C1::ScalarField>, cf_r1cs: R1CS<C2::ScalarField>, cf_cs_params: CS2::ProverParams, - pp_hash: C1::ScalarField, // public params hash - cf_W_i: Witness<C2>, // witness of the running instance - cf_U_i: CommittedInstance<C2>, // running instance + pp_hash: C1::ScalarField, // public params hash + cf_W_i: CycleFoldWitness<C2>, // witness of the running instance + cf_U_i: CycleFoldCommittedInstance<C2>, // running instance cf_u_i_x: Vec<C2::ScalarField>, - cf_circuit: CycleFoldCircuit<C1, GC1>, + cf_circuit: CycleFoldCircuit<CFG, GC1>, mut rng: impl RngCore, ) -> Result< ( - Witness<C2>, - CommittedInstance<C2>, // u_i - Witness<C2>, // W_i1 - CommittedInstance<C2>, // U_i1 - C2, // cmT - C2::ScalarField, // r_Fq + CycleFoldWitness<C2>, + CycleFoldCommittedInstance<C2>, // u_i + CycleFoldWitness<C2>, // W_i1 + CycleFoldCommittedInstance<C2>, // U_i1 + C2, // cmT + C2::ScalarField, // r_Fq ), Error, > where + CFG: CycleFoldConfig<C = C1, F = CF2<C1>>, C1: CurveGroup, GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>, - FC: FCircuit<C1::ScalarField>, - CS1: CommitmentScheme<C1, H>, CS2: CommitmentScheme<C2, H>, <C1 as CurveGroup>::BaseField: PrimeField, <C2 as CurveGroup>::BaseField: PrimeField,@@ -431,11 +513,12 @@ return Err(Error::NotEqual);
} #[cfg(test)] - assert_eq!(cf_x_i.len(), cf_io_len(_n_points)); + assert_eq!(cf_x_i.len(), CFG::IO_LEN); // fold cyclefold instances - let cf_w_i = Witness::<C2>::new::<H>(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng); - let cf_u_i: CommittedInstance<C2> = cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?; + let cf_w_i = CycleFoldWitness::<C2>::new::<H>(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng); + let cf_u_i: CycleFoldCommittedInstance<C2> = + cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?; // compute T* and cmT* for CycleFoldCircuit let (cf_T, cf_cmT) = NIFS::<C2, CS2, H>::compute_cyclefold_cmT(@@ -478,11 +561,22 @@ use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::utils::get_cm_coordinates; + struct TestCycleFoldConfig<C: CurveGroup> { + _c: PhantomData<C>, + } + + impl<C: CurveGroup> CycleFoldConfig for TestCycleFoldConfig<C> { + const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; + const N_INPUT_POINTS: usize = 2; + type C = C; + type F = C::BaseField; + } + #[test] fn test_committed_instance_cyclefold_var() { let mut rng = ark_std::test_rng(); - let ci = CommittedInstance::<Projective> { + let ci = CycleFoldCommittedInstance::<Projective> { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng),@@ -516,9 +610,8 @@ get_cm_coordinates(&ci2.cmW),
get_cm_coordinates(&ci3.cmW), ] .concat(); - let cfW_circuit = CycleFoldCircuit::<Projective, GVar> { + let cfW_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> { _gc: PhantomData, - n_points: 2, r_bits: Some(vec![r_bits.clone()]), points: Some(vec![ci1.clone().cmW, ci2.clone().cmW]), x: Some(cfW_u_i_x.clone()),@@ -535,9 +628,8 @@ get_cm_coordinates(&cmT),
get_cm_coordinates(&ci3.cmE), ] .concat(); - let cfE_circuit = CycleFoldCircuit::<Projective, GVar> { + let cfE_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> { _gc: PhantomData, - n_points: 2, r_bits: Some(vec![r_bits.clone()]), points: Some(vec![ci1.clone().cmE, cmT]), x: Some(cfE_u_i_x.clone()),@@ -548,11 +640,10 @@ }
#[test] fn test_nifs_full_gadget() { - let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs(); + let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs(); let cs = ConstraintSystem::<Fq>::new_ref(); - let r_nonnatVar = NonNativeUintVar::<Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap(); let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); let ci1Var =@@ -572,15 +663,8 @@ })
.unwrap(); let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap(); - NIFSFullGadget::<Projective, GVar>::verify( - r_bitsVar, - r_nonnatVar, - cmTVar, - ci1Var, - ci2Var, - ci3Var, - ) - .unwrap(); + NIFSFullGadget::<Projective, GVar>::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var) + .unwrap(); assert!(cs.is_satisfied().unwrap()); }@@ -590,20 +674,20 @@ let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fq>(); let mut transcript = PoseidonSponge::<Fq>::new(&poseidon_config); - let u_i = CommittedInstance::<Projective> { + let u_i = CycleFoldCommittedInstance::<Projective> { cmE: Projective::zero(), // zero on purpose, so we test also the zero point case u: Fr::zero(), cmW: Projective::rand(&mut rng), x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(7) // 7 = cf_io_len + .take(TestCycleFoldConfig::<Projective>::IO_LEN) .collect(), }; - let U_i = CommittedInstance::<Projective> { + let U_i = CycleFoldCommittedInstance::<Projective> { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(7) // 7 = cf_io_len + .take(TestCycleFoldConfig::<Projective>::IO_LEN) .collect(), }; let cmT = Projective::rand(&mut rng);@@ -657,12 +741,12 @@ let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fq>(); let sponge = PoseidonSponge::<Fq>::new(&poseidon_config); - let U_i = CommittedInstance::<Projective> { + let U_i = CycleFoldCommittedInstance::<Projective> { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(7) // 7 = cf_io_len in Nova + .take(TestCycleFoldConfig::<Projective>::IO_LEN) .collect(), }; let pp_hash = Fq::from(42u32); // only for test
@@ -26,7 +26,7 @@ pub x: Vec<C::ScalarField>,
} impl<F: PrimeField> CCS<F> { - pub fn to_cccs<R: Rng, C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool>( + pub fn to_cccs<R: Rng, C, CS: CommitmentScheme<C, H>, const H: bool>( &self, rng: &mut R, cs_params: &CS::ProverParams,
@@ -26,20 +26,21 @@ use super::{
cccs::CCCS, lcccs::LCCCS, nimfs::{NIMFSProof, NIMFS}, - Witness, + HyperNovaCycleFoldConfig, Witness, }; -use crate::constants::N_BITS_RO; +use crate::constants::NOVA_N_BITS_RO; use crate::folding::{ - circuits::cyclefold::{ - cf_io_len, CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, - }, circuits::{ + cyclefold::{ + CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, + CycleFoldConfig, NIFSFullGadget, + }, nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, sum_check::{IOPProofVar, SumCheckVerifierGadget, VPAuxInfoVar}, utils::EqEvalGadget, CF1, CF2, }, - nova::{get_r1cs_from_cs, CommittedInstance}, + nova::get_r1cs_from_cs, }; use crate::frontend::FCircuit; use crate::utils::virtual_polynomial::VPAuxInfo;@@ -313,7 +314,7 @@ // get the folding challenge
let rho_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"rho"); let rho_scalar: FpVar<CF1<C>> = FpVar::<CF1<C>>::new_constant(cs.clone(), rho_scalar_raw)?; transcript.absorb(&rho_scalar)?; - let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(N_BITS_RO)?; + let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(NOVA_N_BITS_RO)?; let rho = Boolean::le_bits_to_fp_var(&rho_bits)?; // Self::fold will return the folded instance, together with the rho's powers vector so@@ -342,7 +343,7 @@ let mut x_folded: Vec<FpVar<CF1<C>>> = vec![FpVar::zero(); lcccs[0].x.len()];
let mut v_folded: Vec<FpVar<CF1<C>>> = vec![FpVar::zero(); sigmas[0].len()]; let mut rho_vec: Vec<Vec<Boolean<CF1<C>>>> = - vec![vec![Boolean::FALSE; N_BITS_RO]; lcccs.len() + cccs.len() - 1]; + vec![vec![Boolean::FALSE; NOVA_N_BITS_RO]; lcccs.len() + cccs.len() - 1]; let mut rho_i = FpVar::one(); for i in 0..(lcccs.len() + cccs.len()) { let u: FpVar<CF1<C>>;@@ -381,12 +382,12 @@ .collect();
// compute the next power of rho rho_i *= rho.clone(); - // crop the size of rho_i to N_BITS_RO + // crop the size of rho_i to NOVA_N_BITS_RO let rho_i_bits = rho_i.to_bits_le()?; - rho_i = Boolean::le_bits_to_fp_var(&rho_i_bits[..N_BITS_RO])?; + rho_i = Boolean::le_bits_to_fp_var(&rho_i_bits[..NOVA_N_BITS_RO])?; if i < lcccs.len() + cccs.len() - 1 { // store the cropped rho_i into the rho_vec - rho_vec[i] = rho_i_bits[..N_BITS_RO].to_vec(); + rho_vec[i] = rho_i_bits[..NOVA_N_BITS_RO].to_vec(); } }@@ -456,12 +457,32 @@ }
Ok(c) } +/// `AugmentedFCircuit` enhances the original step function `F`, so that it can +/// be used in recursive arguments such as IVC. +/// +/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in +/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not +/// only invokes `F`, but also adds additional constraints for verifying the +/// correct folding of primary instances (i.e., the instances over `C1`). +/// In the paper, the primary instances are Nova's `CommittedInstance`, but we +/// extend this method to support using HyperNova's `LCCCS` and `CCCS` instances +/// as primary instances. +/// +/// Furthermore, to reduce circuit size over `C2`, we implement the constraints +/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra +/// constraints verify the correct folding of CycleFold instances. +/// +/// For multi-instance folding, one needs to specify the const generics below: +/// * `MU` - the number of LCCCS instances to be folded +/// * `NU` - the number of CCCS instances to be folded #[derive(Debug, Clone)] pub struct AugmentedFCircuit< C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>, + const MU: usize, + const NU: usize, > where for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, {@@ -470,8 +491,6 @@ pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>, pub ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit pub pp_hash: Option<CF1<C1>>, - pub mu: usize, // max number of LCCCS instances to be folded - pub nu: usize, // max number of CCCS instances to be folded pub i: Option<CF1<C1>>, pub i_usize: Option<usize>, pub z_0: Option<Vec<C1::ScalarField>>,@@ -487,13 +506,13 @@ pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
pub nimfs_proof: Option<NIMFSProof<C1>>, // cyclefold verifier on C1 - pub cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW - pub cf_U_i: Option<CommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance - pub cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1]) + pub cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW + pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance + pub cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1]) pub cf_cmT: Option<C2>, } -impl<C1, C2, GC2, FC> AugmentedFCircuit<C1, C2, GC2, FC> +impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> AugmentedFCircuit<C1, C2, GC2, FC, MU, NU> where C1: CurveGroup, C2: CurveGroup,@@ -510,10 +529,8 @@ pub fn default(
poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC, ccs: CCS<C1::ScalarField>, - mu: usize, - nu: usize, ) -> Result<Self, Error> { - if mu < 1 || nu < 1 { + if MU < 1 || NU < 1 { return Err(Error::CantBeZero("mu,nu".to_string())); } Ok(Self {@@ -522,8 +539,6 @@ _gc2: PhantomData,
poseidon_config: poseidon_config.clone(), ccs, pp_hash: None, - mu, - nu, i: None, i_usize: None, z_0: None,@@ -548,8 +563,6 @@ pub fn empty(
poseidon_config: &PoseidonConfig<CF1<C1>>, F: FC, // FCircuit ccs: Option<CCS<C1::ScalarField>>, - mu: usize, - nu: usize, ) -> Result<Self, Error> { let initial_ccs = CCS { // m, n, s, s_prime and M will be overwritten by the `upper_bound_ccs' method@@ -565,7 +578,7 @@ S: vec![vec![0, 1], vec![2]],
c: vec![C1::ScalarField::one(), C1::ScalarField::one().neg()], M: vec![], }; - let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs, mu, nu)?; + let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs)?; if ccs.is_some() { augmented_f_circuit.ccs = ccs.unwrap(); } else {@@ -590,10 +603,10 @@ let mut u_i = CCCS::<C1>::dummy(ccs.l);
let n_iters = 2; for _ in 0..n_iters { - let Us = vec![U_i.clone(); self.mu - 1]; - let Ws = vec![W_i.clone(); self.mu - 1]; - let us = vec![u_i.clone(); self.nu - 1]; - let ws = vec![w_i.clone(); self.nu - 1]; + let Us = vec![U_i.clone(); MU - 1]; + let Ws = vec![W_i.clone(); MU - 1]; + let us = vec![u_i.clone(); NU - 1]; + let ws = vec![w_i.clone(); NU - 1]; let all_Us = [vec![U_i.clone()], Us.clone()].concat(); let all_us = [vec![u_i.clone()], us.clone()].concat();@@ -618,8 +631,6 @@ _gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(), ccs: ccs.clone(), pp_hash: Some(C1::ScalarField::zero()), - mu: self.mu, - nu: self.nu, i: Some(C1::ScalarField::zero()), i_usize: Some(0), z_0: Some(z_0.clone()),@@ -677,7 +688,8 @@ Ok((cs, ccs))
} } -impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC> +impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> ConstraintSynthesizer<CF1<C1>> + for AugmentedFCircuit<C1, C2, GC2, FC, MU, NU> where C1: CurveGroup, C2: CurveGroup,@@ -719,20 +731,21 @@
let U_i = LCCCSVar::<C1>::new_witness(cs.clone(), || Ok(self.U_i.unwrap_or(U_dummy.clone())))?; let Us = Vec::<LCCCSVar<C1>>::new_witness(cs.clone(), || { - Ok(self.Us.unwrap_or(vec![U_dummy.clone(); self.mu - 1])) + Ok(self.Us.unwrap_or(vec![U_dummy.clone(); MU - 1])) })?; let us = Vec::<CCCSVar<C1>>::new_witness(cs.clone(), || { - Ok(self.us.unwrap_or(vec![u_dummy.clone(); self.mu - 1])) + Ok(self.us.unwrap_or(vec![u_dummy.clone(); MU - 1])) })?; let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || { Ok(self.U_i1_C.unwrap_or_else(C1::zero)) })?; - let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, self.mu, self.nu); + let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, MU, NU); let nimfs_proof = ProofVar::<C1>::new_witness(cs.clone(), || { Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy)) })?; - let cf_u_dummy = CommittedInstance::dummy(cf_io_len(self.mu + self.nu)); + let cf_u_dummy = + CycleFoldCommittedInstance::dummy(HyperNovaCycleFoldConfig::<C1, MU, NU>::IO_LEN); let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?;@@ -861,20 +874,9 @@ cf_U_i_vec,
cf_u_i.clone(), cf_cmT.clone(), )?; - // Convert cf_r_bits to a `NonNativeFieldVar` - let cf_r_nonnat = { - let mut bits = cf_r_bits.clone(); - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }; // Fold cf1_u_i & cf_U_i into cf1_U_{i+1} - let cf_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance( - cf_r_bits, - cf_r_nonnat, - cf_cmT, - cf_U_i, - cf_u_i, - )?; + let cf_U_i1 = + NIFSFullGadget::<C2, GC2>::fold_committed_instance(cf_r_bits, cf_cmT, cf_U_i, cf_u_i)?; // Back to Primary Part // P.4.b compute and check the second output of F'@@ -909,9 +911,12 @@ r1cs::extract_w_x,
}, commitment::{pedersen::Pedersen, CommitmentScheme}, folding::{ - circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}, - hypernova::utils::{compute_c, compute_sigmas_thetas}, - nova::{traits::NovaR1CS, Witness as NovaWitness}, + circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldWitness}, + hypernova::{ + utils::{compute_c, compute_sigmas_thetas}, + HyperNovaCycleFoldCircuit, + }, + nova::traits::NovaR1CS, }, frontend::tests::CubicFCircuit, transcript::poseidon::poseidon_canonical_config,@@ -1179,25 +1184,25 @@ let mut rng = test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>(); let sponge = PoseidonSponge::<Fr>::new(&poseidon_config); - let mu = 3; - let nu = 3; + const MU: usize = 3; + const NU: usize = 3; let start = Instant::now(); let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); - let mut augmented_f_circuit = AugmentedFCircuit::< - Projective, - Projective2, - GVar2, - CubicFCircuit<Fr>, - >::empty(&poseidon_config, F_circuit, None, mu, nu) - .unwrap(); + let mut augmented_f_circuit = + AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>, MU, NU>::empty( + &poseidon_config, + F_circuit, + None, + ) + .unwrap(); let ccs = augmented_f_circuit.ccs.clone(); println!("AugmentedFCircuit & CCS generation: {:?}", start.elapsed()); println!("CCS m x n: {} x {}", ccs.m, ccs.n); // CycleFold circuit let cs2 = ConstraintSystem::<Fq>::new_ref(); - let cf_circuit = CycleFoldCircuit::<Projective, GVar>::empty(mu + nu); + let cf_circuit = HyperNovaCycleFoldCircuit::<Projective, GVar, MU, NU>::empty(); cf_circuit.generate_constraints(cs2.clone()).unwrap(); cs2.finalize(); let cs2 = cs2@@ -1224,8 +1229,10 @@ let W_dummy = Witness::<Fr>::dummy(&ccs);
let U_dummy = LCCCS::<Projective>::dummy(ccs.l, ccs.t, ccs.s); let w_dummy = W_dummy.clone(); let u_dummy = CCCS::<Projective>::dummy(ccs.l); - let (cf_W_dummy, cf_U_dummy): (NovaWitness<Projective2>, CommittedInstance<Projective2>) = - cf_r1cs.dummy_instance(); + let (cf_W_dummy, cf_U_dummy): ( + CycleFoldWitness<Projective2>, + CycleFoldCommittedInstance<Projective2>, + ) = cf_r1cs.dummy_instance(); // set the initial dummy instances let mut W_i = W_dummy.clone();@@ -1245,10 +1252,10 @@ for i in 0..n_steps {
let start = Instant::now(); // for this test, let Us & us be just an array of copies of the U_i & u_i respectively - let Us = vec![U_i.clone(); mu - 1]; - let Ws = vec![W_i.clone(); mu - 1]; - let us = vec![u_i.clone(); nu - 1]; - let ws = vec![w_i.clone(); nu - 1]; + let Us = vec![U_i.clone(); MU - 1]; + let Ws = vec![W_i.clone(); MU - 1]; + let us = vec![u_i.clone(); NU - 1]; + let ws = vec![w_i.clone(); NU - 1]; let all_Us = [vec![U_i.clone()], Us.clone()].concat(); let all_us = [vec![u_i.clone()], us.clone()].concat(); let all_Ws = [vec![W_i.clone()], Ws].concat();@@ -1268,35 +1275,39 @@ // hash the initial (dummy) CycleFold instance, which is used as the 2nd public
// input in the AugmentedFCircuit let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); - augmented_f_circuit = - AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> { - _c2: PhantomData, - _gc2: PhantomData, - poseidon_config: poseidon_config.clone(), - ccs: ccs.clone(), - pp_hash: Some(pp_hash), - mu, - nu, - i: Some(Fr::zero()), - i_usize: Some(0), - z_0: Some(z_0.clone()), - z_i: Some(z_i.clone()), - external_inputs: Some(vec![]), - U_i: Some(U_i.clone()), - Us: Some(Us.clone()), - u_i_C: Some(u_i.C), - us: Some(us.clone()), - U_i1_C: Some(U_i1.C), - F: F_circuit, - x: Some(u_i1_x), - nimfs_proof: None, + augmented_f_circuit = AugmentedFCircuit::< + Projective, + Projective2, + GVar2, + CubicFCircuit<Fr>, + MU, + NU, + > { + _c2: PhantomData, + _gc2: PhantomData, + poseidon_config: poseidon_config.clone(), + ccs: ccs.clone(), + pp_hash: Some(pp_hash), + i: Some(Fr::zero()), + i_usize: Some(0), + z_0: Some(z_0.clone()), + z_i: Some(z_i.clone()), + external_inputs: Some(vec![]), + U_i: Some(U_i.clone()), + Us: Some(Us.clone()), + u_i_C: Some(u_i.C), + us: Some(us.clone()), + U_i1_C: Some(U_i1.C), + F: F_circuit, + x: Some(u_i1_x), + nimfs_proof: None, - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_x: Some(cf_u_i1_x), - cf_cmT: None, - }; + // cyclefold values + cf_u_i_cmW: None, + cf_U_i: None, + cf_x: Some(cf_u_i1_x), + cf_cmT: None, + }; } else { let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config.clone());@@ -1328,7 +1339,7 @@ })
.collect(); let rho_powers_bits: Vec<Vec<bool>> = rho_powers .iter() - .map(|rho_i| rho_i.into_bigint().to_bits_le()[..N_BITS_RO].to_vec()) + .map(|rho_i| rho_i.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec()) .collect(); // CycleFold part:@@ -1348,9 +1359,8 @@ get_cm_coordinates(&U_i1.C),
] .concat(); - let cf_circuit = CycleFoldCircuit::<Projective, GVar> { + let cf_circuit = HyperNovaCycleFoldCircuit::<Projective, GVar, MU, NU> { _gc: PhantomData, - n_points: mu + nu, r_bits: Some(rho_powers_bits.clone()), points: Some( [@@ -1367,24 +1377,22 @@
// ensure that the CycleFoldCircuit is well defined assert_eq!( cf_circuit.r_bits.clone().unwrap().len(), - cf_circuit.n_points - 1 + HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS - 1 ); assert_eq!( cf_circuit.points.clone().unwrap().len(), - cf_circuit.n_points + HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS ); let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::< + HyperNovaCycleFoldConfig<Projective, MU, NU>, Projective, GVar, Projective2, GVar2, - CubicFCircuit<Fr>, - Pedersen<Projective>, Pedersen<Projective2>, false, >( - mu + nu, &mut transcript_p, cf_r1cs.clone(), cf_pedersen_params.clone(),@@ -1401,35 +1409,39 @@ // hash the CycleFold folded instance, which is used as the 2nd public input in the
// AugmentedFCircuit let cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash); - augmented_f_circuit = - AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> { - _c2: PhantomData, - _gc2: PhantomData, - poseidon_config: poseidon_config.clone(), - ccs: ccs.clone(), - pp_hash: Some(pp_hash), - mu, - nu, - i: Some(iFr), - i_usize: Some(i), - z_0: Some(z_0.clone()), - z_i: Some(z_i.clone()), - external_inputs: Some(vec![]), - U_i: Some(U_i.clone()), - Us: Some(Us.clone()), - u_i_C: Some(u_i.C), - us: Some(us.clone()), - U_i1_C: Some(U_i1.C), - F: F_circuit, - x: Some(u_i1_x), - nimfs_proof: Some(nimfs_proof), + augmented_f_circuit = AugmentedFCircuit::< + Projective, + Projective2, + GVar2, + CubicFCircuit<Fr>, + MU, + NU, + > { + _c2: PhantomData, + _gc2: PhantomData, + poseidon_config: poseidon_config.clone(), + ccs: ccs.clone(), + pp_hash: Some(pp_hash), + i: Some(iFr), + i_usize: Some(i), + z_0: Some(z_0.clone()), + z_i: Some(z_i.clone()), + external_inputs: Some(vec![]), + U_i: Some(U_i.clone()), + Us: Some(Us.clone()), + u_i_C: Some(u_i.C), + us: Some(us.clone()), + U_i1_C: Some(U_i1.C), + F: F_circuit, + x: Some(u_i1_x), + nimfs_proof: Some(nimfs_proof), - // cyclefold values - cf_u_i_cmW: Some(cf_u_i.cmW), - cf_U_i: Some(cf_U_i), - cf_x: Some(cf_u_i1_x), - cf_cmT: Some(cf_cmT), - }; + // cyclefold values + cf_u_i_cmW: Some(cf_u_i.cmW), + cf_U_i: Some(cf_U_i), + cf_x: Some(cf_u_i1_x), + cf_cmT: Some(cf_cmT), + }; // assign the next round instances cf_W_i = cf_W_i1;
@@ -30,7 +30,7 @@ pub v: Vec<C::ScalarField>,
} impl<F: PrimeField> CCS<F> { - pub fn to_lcccs<R: Rng, C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool>( + pub fn to_lcccs<R: Rng, C, CS: CommitmentScheme<C, H>, const H: bool>( &self, rng: &mut R, cs_params: &CS::ProverParams,
@@ -6,31 +6,29 @@ };
use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; -use ark_std::rand::RngCore; -use ark_std::{One, Zero}; -use core::marker::PhantomData; -use std::fmt::Debug; +use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero}; pub mod cccs; pub mod circuits; -use circuits::AugmentedFCircuit; pub mod lcccs; pub mod nimfs; pub mod utils; + use cccs::CCCS; +use circuits::AugmentedFCircuit; use lcccs::LCCCS; use nimfs::NIMFS; use crate::commitment::CommitmentScheme; -use crate::constants::N_BITS_RO; +use crate::constants::NOVA_N_BITS_RO; use crate::folding::circuits::{ - cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}, + cyclefold::{ + fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, + CycleFoldWitness, + }, CF2, }; -use crate::folding::nova::{ - get_r1cs_from_cs, traits::NovaR1CS, CommittedInstance, PreprocessorParam, - Witness as NovaWitness, -}; +use crate::folding::nova::{get_r1cs_from_cs, traits::NovaR1CS, PreprocessorParam}; use crate::frontend::FCircuit; use crate::utils::{get_cm_coordinates, pp_hash}; use crate::Error;@@ -42,6 +40,22 @@ },
FoldingScheme, MultiFolding, }; +struct HyperNovaCycleFoldConfig<C: CurveGroup, const MU: usize, const NU: usize> { + _c: PhantomData<C>, +} + +impl<C: CurveGroup, const MU: usize, const NU: usize> CycleFoldConfig + for HyperNovaCycleFoldConfig<C, MU, NU> +{ + const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; + const N_INPUT_POINTS: usize = MU + NU; + type C = C; + type F = C::BaseField; +} + +type HyperNovaCycleFoldCircuit<C, GC, const MU: usize, const NU: usize> = + CycleFoldCircuit<HyperNovaCycleFoldConfig<C, MU, NU>, GC>; + /// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Witness<F: PrimeField> {@@ -73,8 +87,6 @@ pub cs_params: CS1::ProverParams,
pub cf_cs_params: CS2::ProverParams, // if ccs is set, it will be used, if not, it will be computed at runtime pub ccs: Option<CCS<C1::ScalarField>>, - pub mu: usize, - pub nu: usize, } #[derive(Debug, Clone)]@@ -114,9 +126,23 @@
/// Implements HyperNova+CycleFold's IVC, described in /// [HyperNova](https://eprint.iacr.org/2023/573.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait +/// +/// For multi-instance folding, one needs to specify the const generics below: +/// * `MU` - the number of LCCCS instances to be folded +/// * `NU` - the number of CCCS instances to be folded #[derive(Clone, Debug)] -pub struct HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> -where +pub struct HyperNova< + C1, + GC1, + C2, + GC2, + FC, + CS1, + CS2, + const MU: usize, + const NU: usize, + const H: bool, +> where C1: CurveGroup, GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>, C2: CurveGroup,@@ -142,8 +168,6 @@ /// F circuit, the circuit that is being folded
pub F: FC, /// public params hash pub pp_hash: C1::ScalarField, - pub mu: usize, // number of LCCCS instances to be folded - pub nu: usize, // number of CCCS instances to be folded pub i: C1::ScalarField, /// initial state pub z_0: Vec<C1::ScalarField>,@@ -156,12 +180,12 @@ pub w_i: Witness<C1::ScalarField>,
pub u_i: CCCS<C1>, /// CycleFold running instance - pub cf_W_i: NovaWitness<C2>, - pub cf_U_i: CommittedInstance<C2>, + pub cf_W_i: CycleFoldWitness<C2>, + pub cf_U_i: CycleFoldCommittedInstance<C2>, } -impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> MultiFolding<C1, C2, FC> - for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, H> +impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool> + MultiFolding<C1, C2, FC> for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H> where C1: CurveGroup, GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,@@ -227,7 +251,8 @@ Ok((u_i, w_i))
} } -impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, H> +impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool> + HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H> where C1: CurveGroup, GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,@@ -254,7 +279,8 @@ ) -> Result<Vec<C1::ScalarField>, Error> {
// prepare the initial dummy instances let U_i = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s); let mut u_i = CCCS::<C1>::dummy(self.ccs.l); - let (_, cf_U_i): (NovaWitness<C2>, CommittedInstance<C2>) = self.cf_r1cs.dummy_instance(); + let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) = + self.cf_r1cs.dummy_instance(); let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);@@ -268,7 +294,7 @@ state.clone(),
), cf_U_i.hash_cyclefold(&sponge, self.pp_hash), ]; - let us = vec![u_i.clone(); self.nu - 1]; + let us = vec![u_i.clone(); NU - 1]; let z_i1 = self .F@@ -285,14 +311,12 @@ z_i1.clone(),
); let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, self.pp_hash); - let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { + let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> { _c2: PhantomData, _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), ccs: self.ccs.clone(), pp_hash: Some(self.pp_hash), - mu: self.mu, - nu: self.nu, i: Some(C1::ScalarField::zero()), i_usize: Some(0), z_0: Some(self.z_0.clone()),@@ -334,8 +358,8 @@ Ok(r1cs_z)
} } -impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> FoldingScheme<C1, C2, FC> - for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, H> +impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool> + FoldingScheme<C1, C2, FC> for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H> where C1: CurveGroup, GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,@@ -352,37 +376,32 @@ C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { - /// Reuse Nova's PreprocessorParam, together with two usize values, which are mu & nu - /// respectively, which indicate the amount of LCCCS & CCCS instances to be folded at each - /// folding step. - type PreprocessorParam = (PreprocessorParam<C1, C2, FC, CS1, CS2, H>, usize, usize); + /// Reuse Nova's PreprocessorParam. + type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2, H>; type ProverParam = ProverParams<C1, C2, CS1, CS2, H>; type VerifierParam = VerifierParams<C1, C2, CS1, CS2, H>; type RunningInstance = (LCCCS<C1>, Witness<C1::ScalarField>); type IncomingInstance = (CCCS<C1>, Witness<C1::ScalarField>); type MultiCommittedInstanceWithWitness = (Vec<Self::RunningInstance>, Vec<Self::IncomingInstance>); - type CFInstance = (CommittedInstance<C2>, NovaWitness<C2>); + type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>); fn preprocess( mut rng: impl RngCore, prep_param: &Self::PreprocessorParam, ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> { - let (prep_param, mu, nu) = prep_param; - if *mu < 1 || *nu < 1 { + if MU < 1 || NU < 1 { return Err(Error::CantBeZero("mu,nu".to_string())); } - let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty( + let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty( &prep_param.poseidon_config, prep_param.F.clone(), None, - *mu, - *nu, )?; let ccs = augmented_f_circuit.ccs.clone(); - let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(mu + nu); + let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty(); let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?; // if cs params exist, use them, if not, generate new ones@@ -409,8 +428,6 @@ poseidon_config: prep_param.poseidon_config.clone(),
cs_params: cs_pp.clone(), cf_cs_params: cf_cs_pp.clone(), ccs: Some(ccs.clone()), - mu: *mu, - nu: *nu, }; let vp = VerifierParams::<C1, C2, CS1, CS2, H> { poseidon_config: prep_param.poseidon_config.clone(),@@ -429,7 +446,7 @@ F: FC,
z_0: Vec<C1::ScalarField>, ) -> Result<Self, Error> { let (pp, vp) = params; - if pp.mu < 1 || pp.nu < 1 { + if MU < 1 || NU < 1 { return Err(Error::CantBeZero("mu,nu".to_string())); }@@ -438,16 +455,14 @@ let sponge = PoseidonSponge::<C1::ScalarField>::new(&pp.poseidon_config);
// prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS // and R1CS respectively - let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty( + let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty( &pp.poseidon_config, F.clone(), pp.ccs.clone(), - pp.mu, - pp.nu, )?; let ccs = augmented_f_circuit.ccs.clone(); - let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(pp.mu + pp.nu); + let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty(); let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?; // compute the public params hash@@ -458,7 +473,7 @@ let W_dummy = Witness::<C1::ScalarField>::dummy(&ccs);
let U_dummy = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s); let w_dummy = W_dummy.clone(); let mut u_dummy = CCCS::<C1>::dummy(ccs.l); - let (cf_W_dummy, cf_U_dummy): (NovaWitness<C2>, CommittedInstance<C2>) = + let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) = cf_r1cs.dummy_instance(); u_dummy.x = vec![ U_dummy.hash(@@ -484,8 +499,6 @@ cs_params: pp.cs_params.clone(),
cf_cs_params: pp.cf_cs_params.clone(), F, pp_hash, - mu: pp.mu, - nu: pp.nu, i: C1::ScalarField::zero(), z_0: z_0.clone(), z_i: z_0,@@ -536,27 +549,27 @@
// recall, mu & nu is the number of all the LCCCS & CCCS respectively, including the // running and incoming instances that are not part of the 'other_instances', hence the +1 // in the couple of following checks. - if lcccs.len() + 1 != self.mu { + if lcccs.len() + 1 != MU { return Err(Error::NotSameLength( "other_instances.lcccs.len()".to_string(), lcccs.len(), "hypernova.mu".to_string(), - self.mu, + MU, )); } - if cccs.len() + 1 != self.nu { + if cccs.len() + 1 != NU { return Err(Error::NotSameLength( "other_instances.cccs.len()".to_string(), cccs.len(), "hypernova.nu".to_string(), - self.nu, + NU, )); } let (Us, Ws): (Vec<LCCCS<C1>>, Vec<Witness<C1::ScalarField>>) = lcccs.into_iter().unzip(); let (us, ws): (Vec<CCCS<C1>>, Vec<Witness<C1::ScalarField>>) = cccs.into_iter().unzip(); - let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; + let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>; if self.z_i.len() != self.F.state_len() { return Err(Error::NotSameLength(@@ -608,14 +621,12 @@ // hash the initial (dummy) CycleFold instance, which is used as the 2nd public
// input in the AugmentedFCircuit cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); - augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { + augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> { _c2: PhantomData, _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), ccs: self.ccs.clone(), pp_hash: Some(self.pp_hash), - mu: self.mu, - nu: self.nu, i: Some(C1::ScalarField::zero()), i_usize: Some(0), z_0: Some(self.z_0.clone()),@@ -674,7 +685,7 @@ })
.collect(); let rho_powers_bits: Vec<Vec<bool>> = rho_powers .iter() - .map(|rho_i| rho_i.into_bigint().to_bits_le()[..N_BITS_RO].to_vec()) + .map(|rho_i| rho_i.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec()) .collect(); // CycleFold part:@@ -698,9 +709,8 @@ get_cm_coordinates(&U_i1.C),
] .concat(); - let cf_circuit = CycleFoldCircuit::<C1, GC1> { + let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU> { _gc: PhantomData, - n_points: self.mu + self.nu, r_bits: Some(rho_powers_bits.clone()), points: Some( [@@ -714,30 +724,34 @@ ),
x: Some(cf_u_i_x.clone()), }; - let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = - fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2, H>( - self.mu + self.nu, - &mut transcript_p, - self.cf_r1cs.clone(), - self.cf_cs_params.clone(), - self.pp_hash, - self.cf_W_i.clone(), // CycleFold running instance witness - self.cf_U_i.clone(), // CycleFold running instance - cf_u_i_x, - cf_circuit, - &mut rng, - )?; + let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::< + HyperNovaCycleFoldConfig<C1, MU, NU>, + C1, + GC1, + C2, + GC2, + CS2, + H, + >( + &mut transcript_p, + self.cf_r1cs.clone(), + self.cf_cs_params.clone(), + self.pp_hash, + self.cf_W_i.clone(), // CycleFold running instance witness + self.cf_U_i.clone(), // CycleFold running instance + cf_u_i_x, + cf_circuit, + &mut rng, + )?; cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash); - augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { + augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> { _c2: PhantomData, _gc2: PhantomData, poseidon_config: self.poseidon_config.clone(), ccs: self.ccs.clone(), pp_hash: Some(self.pp_hash), - mu: self.mu, - nu: self.nu, i: Some(self.i), i_usize: Some(i_usize), z_0: Some(self.z_0.clone()),@@ -920,37 +934,21 @@ F_circuit: CubicFCircuit<Fr>,
) { let mut rng = ark_std::test_rng(); - let (mu, nu) = (2, 3); + const MU: usize = 2; + const NU: usize = 3; + + type HN<CS1, CS2, const H: bool> = + HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, MU, NU, H>; let prep_param = PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2, H>::new( poseidon_config.clone(), F_circuit, ); - let hypernova_params = HyperNova::< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit<Fr>, - CS1, - CS2, - H, - >::preprocess(&mut rng, &(prep_param, mu, nu)) - .unwrap(); + let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap(); let z_0 = vec![Fr::from(3_u32)]; - let mut hypernova = HyperNova::< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit<Fr>, - CS1, - CS2, - H, - >::init(&hypernova_params, F_circuit, z_0.clone()) - .unwrap(); + let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap(); let (w_i_blinding, W_i_blinding) = if H { (Fr::rand(&mut rng), Fr::rand(&mut rng))@@ -964,7 +962,7 @@ let num_steps: usize = 3;
for _ in 0..num_steps { // prepare some new instances to fold in the multifolding step let mut lcccs = vec![]; - for j in 0..mu - 1 { + for j in 0..MU - 1 { let instance_state = vec![Fr::from(j as u32 + 85_u32)]; let (U, W) = hypernova .new_running_instance(&mut rng, instance_state, vec![])@@ -972,7 +970,7 @@ .unwrap();
lcccs.push((U, W)); } let mut cccs = vec![]; - for j in 0..nu - 1 { + for j in 0..NU - 1 { let instance_state = vec![Fr::from(j as u32 + 15_u32)]; let (u, w) = hypernova .new_incoming_instance(&mut rng, instance_state, vec![])@@ -988,7 +986,7 @@ }
assert_eq!(Fr::from(num_steps as u32), hypernova.i); let (running_instance, incoming_instance, cyclefold_instance) = hypernova.instances(); - HyperNova::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, H>::verify( + HN::verify( hypernova_params.1, // verifier_params z_0, hypernova.z_i,
@@ -12,7 +12,7 @@ utils::{compute_c, compute_g, compute_sigmas_thetas},
Witness, }; use crate::arith::ccs::CCS; -use crate::constants::N_BITS_RO; +use crate::constants::NOVA_N_BITS_RO; use crate::transcript::Transcript; use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage}; use crate::utils::sum_check::{IOPSumCheck, SumCheck};@@ -123,10 +123,12 @@ .collect();
// compute the next power of rho rho_i *= rho; - // crop the size of rho_i to N_BITS_RO + // crop the size of rho_i to NOVA_N_BITS_RO let rho_i_bits = rho_i.into_bigint().to_bits_le(); - rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_i_bits[..N_BITS_RO])) - .unwrap(); + rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le( + &rho_i_bits[..NOVA_N_BITS_RO], + )) + .unwrap(); if i < lcccs.len() + cccs.len() - 1 { // store the cropped rho_i into the rho_powers vector rho_powers[i] = rho_i;@@ -181,10 +183,12 @@ r_w_folded += rho_i * r_w;
// compute the next power of rho rho_i *= rho; - // crop the size of rho_i to N_BITS_RO + // crop the size of rho_i to NOVA_N_BITS_RO let rho_i_bits = rho_i.into_bigint().to_bits_le(); - rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_i_bits[..N_BITS_RO])) - .unwrap(); + rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le( + &rho_i_bits[..NOVA_N_BITS_RO], + )) + .unwrap(); } Witness { w: w_folded,@@ -272,7 +276,7 @@
// Step 6: Get the folding challenge let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho"); transcript.absorb(&rho_scalar); - let rho_bits: Vec<bool> = transcript.get_challenge_nbits(N_BITS_RO); + let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO); let rho: C::ScalarField = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();@@ -391,7 +395,7 @@
// Step 6: Get the folding challenge let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho"); transcript.absorb(&rho_scalar); - let rho_bits: Vec<bool> = transcript.get_challenge_nbits(N_BITS_RO); + let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO); let rho: C::ScalarField = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
@@ -20,11 +20,12 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{fmt::Debug, One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; -use super::{CommittedInstance, NOVA_CF_N_POINTS}; -use crate::constants::N_BITS_RO; +use super::{CommittedInstance, NovaCycleFoldConfig}; +use crate::constants::NOVA_N_BITS_RO; use crate::folding::circuits::{ cyclefold::{ - cf_io_len, CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, + CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, + CycleFoldConfig, NIFSFullGadget, }, nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, CF1, CF2,@@ -196,7 +197,7 @@ transcript.absorb(&pp_hash);
transcript.absorb(&U_i); transcript.absorb(&u_i); transcript.absorb_nonnative(&cmT); - transcript.squeeze_bits(N_BITS_RO) + transcript.squeeze_bits(NOVA_N_BITS_RO) } // compatible with the native get_challenge_native@@ -211,13 +212,22 @@ transcript.absorb(&pp_hash)?;
transcript.absorb(&U_i_vec)?; transcript.absorb(&u_i)?; transcript.absorb_nonnative(&cmT)?; - transcript.squeeze_bits(N_BITS_RO) + transcript.squeeze_bits(NOVA_N_BITS_RO) } } -/// AugmentedFCircuit implements the F' circuit (augmented F) defined in -/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in -/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). +/// `AugmentedFCircuit` enhances the original step function `F`, so that it can +/// be used in recursive arguments such as IVC. +/// +/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in +/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not +/// only invokes `F`, but also adds additional constraints for verifying the +/// correct folding of primary instances (i.e., Nova's `CommittedInstance`s over +/// `C1`). +/// +/// Furthermore, to reduce circuit size over `C2`, we implement the constraints +/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra +/// constraints verify the correct folding of CycleFold instances. #[derive(Debug, Clone)] pub struct AugmentedFCircuit< C1: CurveGroup,@@ -246,9 +256,9 @@
// cyclefold verifier on C1 // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and // cmE respectively - pub cf1_u_i_cmW: Option<C2>, // input - pub cf2_u_i_cmW: Option<C2>, // input - pub cf_U_i: Option<CommittedInstance<C2>>, // input + pub cf1_u_i_cmW: Option<C2>, // input + pub cf2_u_i_cmW: Option<C2>, // input + pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input pub cf1_cmT: Option<C2>, pub cf2_cmT: Option<C2>, pub cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])@@ -337,7 +347,7 @@
let cmT = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; - let cf_u_dummy = CommittedInstance::dummy(cf_io_len(NOVA_CF_N_POINTS)); + let cf_u_dummy = CycleFoldCommittedInstance::dummy(NovaCycleFoldConfig::<C1>::IO_LEN); let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?;@@ -481,19 +491,9 @@ cf_U_i_vec,
cf1_u_i.clone(), cf1_cmT.clone(), )?; - // Convert cf1_r_bits to a `NonNativeFieldVar` - let cf1_r_nonnat = { - let mut bits = cf1_r_bits.clone(); - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }; // Fold cf1_u_i & cf_U_i into cf1_U_{i+1} let cf1_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance( - cf1_r_bits, - cf1_r_nonnat, - cf1_cmT, - cf_U_i, - cf1_u_i, + cf1_r_bits, cf1_cmT, cf_U_i, cf1_u_i, )?; // same for cf2_r:@@ -504,16 +504,8 @@ cf1_U_i1.to_native_sponge_field_elements()?,
cf2_u_i.clone(), cf2_cmT.clone(), )?; - let cf2_r_nonnat = { - let mut bits = cf2_r_bits.clone(); - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }; let cf_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance( - cf2_r_bits, - cf2_r_nonnat, - cf2_cmT, - cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) + cf2_r_bits, cf2_cmT, cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) cf2_u_i, )?;@@ -566,7 +558,7 @@ assert_eq!(ciVar.u.value().unwrap(), ci.u);
assert_eq!(ciVar.x.value().unwrap(), ci.x); // the values cmE and cmW are checked in the CycleFold's circuit // CommittedInstanceInCycleFoldVar in - // nova::cyclefold::tests::test_committed_instance_cyclefold_var + // cyclefold::tests::test_committed_instance_cyclefold_var } #[test]
@@ -22,14 +22,18 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{log2, Zero}; use core::{borrow::Borrow, marker::PhantomData}; -use super::{circuits::ChallengeGadget, nifs::NIFS}; +use super::{ + circuits::{ChallengeGadget, CommittedInstanceVar}, + nifs::NIFS, + CommittedInstance, Nova, Witness, +}; use crate::arith::r1cs::R1CS; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::folding::circuits::{ + cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}, nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, CF1, CF2, }; -use crate::folding::nova::{circuits::CommittedInstanceVar, CommittedInstance, Nova, Witness}; use crate::frontend::FCircuit; use crate::transcript::{Transcript, TranscriptVar}; use crate::utils::{@@ -156,7 +160,7 @@ })
} } -/// In-circuit representation of the Witness associated to the CommittedInstance, but with +/// In-circuit representation of the Witness associated to the CycleFoldCommittedInstance, but with /// non-native representation, since it is used to represent the CycleFold witness. #[derive(Debug, Clone)] pub struct CycleFoldWitnessVar<C: CurveGroup> {@@ -166,12 +170,12 @@ pub W: Vec<NonNativeUintVar<CF2<C>>>,
pub rW: NonNativeUintVar<CF2<C>>, } -impl<C> AllocVar<Witness<C>, CF2<C>> for CycleFoldWitnessVar<C> +impl<C> AllocVar<CycleFoldWitness<C>, CF2<C>> for CycleFoldWitnessVar<C> where C: CurveGroup, <C as ark_ec::CurveGroup>::BaseField: PrimeField, { - fn new_variable<T: Borrow<Witness<C>>>( + fn new_variable<T: Borrow<CycleFoldWitness<C>>>( cs: impl Into<Namespace<CF2<C>>>, f: impl FnOnce() -> Result<T, SynthesisError>, mode: AllocationMode,@@ -237,8 +241,8 @@ pub W_i1: Option<Witness<C1>>,
pub cmT: Option<C1>, pub r: Option<C1::ScalarField>, /// CycleFold running instance - pub cf_U_i: Option<CommittedInstance<C2>>, - pub cf_W_i: Option<Witness<C2>>, + pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, + pub cf_W_i: Option<CycleFoldWitness<C2>>, /// KZG challenges pub kzg_c_W: Option<C1::ScalarField>,@@ -447,14 +451,19 @@ #[cfg(not(feature = "light-test"))]
{ // imports here instead of at the top of the file, so we avoid having multiple // `#[cfg(not(test))]` - use super::NOVA_CF_N_POINTS; use crate::commitment::pedersen::PedersenGadget; - use crate::folding::circuits::cyclefold::{cf_io_len, CycleFoldCommittedInstanceVar}; + use crate::folding::{ + circuits::cyclefold::{CycleFoldCommittedInstanceVar, CycleFoldConfig}, + nova::NovaCycleFoldConfig, + }; use ark_r1cs_std::ToBitsGadget; - let cf_u_dummy_native = CommittedInstance::<C2>::dummy(cf_io_len(NOVA_CF_N_POINTS)); - let w_dummy_native = - Witness::<C2>::dummy(self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l, self.cf_E_len); + let cf_u_dummy_native = + CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN); + let w_dummy_native = CycleFoldWitness::<C2>::dummy( + self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l, + self.cf_E_len, + ); let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?;
@@ -4,7 +4,7 @@ use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, }; -use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};@@ -15,7 +15,10 @@ use ark_std::{One, UniformRand, Zero};
use core::marker::PhantomData; use crate::commitment::CommitmentScheme; -use crate::folding::circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}; +use crate::folding::circuits::cyclefold::{ + fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, + CycleFoldWitness, +}; use crate::folding::circuits::CF2; use crate::frontend::FCircuit; use crate::transcript::{AbsorbNonNative, Transcript};@@ -24,6 +27,7 @@ use crate::Error;
use crate::FoldingScheme; use crate::{ arith::r1cs::{extract_r1cs, extract_w_x, R1CS}, + constants::NOVA_N_BITS_RO, utils::{get_cm_coordinates, pp_hash}, };@@ -33,13 +37,23 @@ pub mod decider_eth_circuit;
pub mod nifs; pub mod serialize; pub mod traits; + use circuits::{AugmentedFCircuit, ChallengeGadget}; use nifs::NIFS; use traits::NovaR1CS; -/// Number of points to be folded in the CycleFold circuit, in Nova's case, this is a fixed amount: -/// 2 points to be folded. -const NOVA_CF_N_POINTS: usize = 2_usize; +struct NovaCycleFoldConfig<C: CurveGroup> { + _c: PhantomData<C>, +} + +impl<C: CurveGroup> CycleFoldConfig for NovaCycleFoldConfig<C> { + const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; + const N_INPUT_POINTS: usize = 2; + type C = C; + type F = C::BaseField; +} + +type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>; #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance<C: CurveGroup> {@@ -84,30 +98,6 @@ .to_sponge_field_elements(dest);
} } -impl<C: CurveGroup> AbsorbNonNative<C::BaseField> for CommittedInstance<C> -where - <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb, -{ - // Compatible with the in-circuit `CycleFoldCommittedInstanceVar::to_native_sponge_field_elements` - // in `cyclefold.rs`. - fn to_native_sponge_field_elements(&self, dest: &mut Vec<C::BaseField>) { - [self.u].to_native_sponge_field_elements(dest); - self.x.to_native_sponge_field_elements(dest); - let (cmE_x, cmE_y) = match self.cmE.into_affine().xy() { - Some((&x, &y)) => (x, y), - None => (C::BaseField::zero(), C::BaseField::zero()), - }; - let (cmW_x, cmW_y) = match self.cmW.into_affine().xy() { - Some((&x, &y)) => (x, y), - None => (C::BaseField::zero(), C::BaseField::zero()), - }; - cmE_x.to_sponge_field_elements(dest); - cmE_y.to_sponge_field_elements(dest); - cmW_x.to_sponge_field_elements(dest); - cmW_y.to_sponge_field_elements(dest); - } -} - impl<C: CurveGroup> CommittedInstance<C> where <C as Group>::ScalarField: Absorb,@@ -135,25 +125,6 @@ sponge.squeeze_field_elements(1)[0]
} } -impl<C: CurveGroup> CommittedInstance<C> -where - <C as ark_ec::CurveGroup>::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<T: Transcript<C::BaseField>>( - &self, - sponge: &T, - pp_hash: C::BaseField, // public params hash - ) -> C::BaseField { - let mut sponge = sponge.clone(); - sponge.absorb(&pp_hash); - sponge.absorb_nonnative(self); - sponge.squeeze_field_elements(1)[0] - } -} - #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Witness<C: CurveGroup> { pub E: Vec<C::ScalarField>,@@ -342,8 +313,8 @@ pub W_i: Witness<C1>,
pub U_i: CommittedInstance<C1>, /// CycleFold running instance - pub cf_W_i: Witness<C2>, - pub cf_U_i: CommittedInstance<C2>, + pub cf_W_i: CycleFoldWitness<C2>, + pub cf_U_i: CycleFoldCommittedInstance<C2>, } impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> FoldingScheme<C1, C2, FC>@@ -370,7 +341,7 @@ type VerifierParam = VerifierParams<C1, C2, CS1, CS2, H>;
type RunningInstance = (CommittedInstance<C1>, Witness<C1>); type IncomingInstance = (CommittedInstance<C1>, Witness<C1>); type MultiCommittedInstanceWithWitness = (); - type CFInstance = (CommittedInstance<C2>, Witness<C2>); + type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>); fn preprocess( mut rng: impl RngCore,@@ -428,7 +399,7 @@ let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&pp.poseidon_config, F.clone()); - let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS); + let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty(); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize();@@ -619,16 +590,14 @@ get_cm_coordinates(&U_i1.cmE),
] .concat(); - let cfW_circuit = CycleFoldCircuit::<C1, GC1> { + let cfW_circuit = NovaCycleFoldCircuit::<C1, GC1> { _gc: PhantomData, - n_points: NOVA_CF_N_POINTS, r_bits: Some(vec![r_bits.clone()]), points: Some(vec![self.U_i.clone().cmW, self.u_i.clone().cmW]), x: Some(cfW_u_i_x.clone()), }; - let cfE_circuit = CycleFoldCircuit::<C1, GC1> { + let cfE_circuit = NovaCycleFoldCircuit::<C1, GC1> { _gc: PhantomData, - n_points: NOVA_CF_N_POINTS, r_bits: Some(vec![r_bits.clone()]), points: Some(vec![self.U_i.clone().cmE, cmT]), x: Some(cfE_u_i_x.clone()),@@ -855,24 +824,23 @@ #[allow(clippy::type_complexity)]
fn fold_cyclefold_circuit<T: Transcript<C1::ScalarField>>( &self, transcript: &mut T, - cf_W_i: Witness<C2>, // witness of the running instance - cf_U_i: CommittedInstance<C2>, // running instance + cf_W_i: CycleFoldWitness<C2>, // witness of the running instance + cf_U_i: CycleFoldCommittedInstance<C2>, // running instance cf_u_i_x: Vec<C2::ScalarField>, - cf_circuit: CycleFoldCircuit<C1, GC1>, + cf_circuit: NovaCycleFoldCircuit<C1, GC1>, rng: &mut impl RngCore, ) -> Result< ( - Witness<C2>, - CommittedInstance<C2>, // u_i - Witness<C2>, // W_i1 - CommittedInstance<C2>, // U_i1 - C2, // cmT - C2::ScalarField, // r_Fq + CycleFoldWitness<C2>, + CycleFoldCommittedInstance<C2>, // u_i + CycleFoldWitness<C2>, // W_i1 + CycleFoldCommittedInstance<C2>, // U_i1 + C2, // cmT + C2::ScalarField, // r_Fq ), Error, > { - fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2, H>( - NOVA_CF_N_POINTS, + fold_cyclefold_circuit::<NovaCycleFoldConfig<C1>, C1, GC1, C2, GC2, CS2, H>( transcript, self.cf_r1cs.clone(), self.cf_cs_pp.clone(),@@ -920,7 +888,7 @@ for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{ let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(poseidon_config, F_circuit); - let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS); + let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty(); let r1cs = get_r1cs_from_cs::<C1::ScalarField>(augmented_F_circuit)?; let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?; Ok((r1cs, cf_r1cs))
@@ -6,6 +6,7 @@
use super::{CommittedInstance, Witness}; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; +use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}; use crate::transcript::Transcript; use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; use crate::Error;@@ -110,10 +111,10 @@ }
pub fn compute_cyclefold_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2) - w1: &Witness<C>, - ci1: &CommittedInstance<C>, - w2: &Witness<C>, - ci2: &CommittedInstance<C>, + w1: &CycleFoldWitness<C>, + ci1: &CycleFoldCommittedInstance<C>, + w2: &CycleFoldWitness<C>, + ci2: &CycleFoldCommittedInstance<C>, ) -> Result<(Vec<C::ScalarField>, C), Error> where <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
@@ -10,14 +10,14 @@ use ark_relations::r1cs::ConstraintSystem;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError, Write}; use std::marker::PhantomData; -use super::{circuits::AugmentedFCircuit, Nova, ProverParams}; -use super::{CommittedInstance, Witness}; -use crate::folding::{ - circuits::{cyclefold::CycleFoldCircuit, CF2}, - nova::NOVA_CF_N_POINTS, +use super::{ + circuits::AugmentedFCircuit, CommittedInstance, Nova, NovaCycleFoldCircuit, ProverParams, + Witness, }; use crate::{ - arith::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1, + arith::r1cs::extract_r1cs, + commitment::CommitmentScheme, + folding::circuits::{CF1, CF2}, frontend::FCircuit, };@@ -138,7 +138,7 @@ let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref(); let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&poseidon_config, f_circuit.clone()); - let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS); + let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty(); augmented_F_circuit .generate_constraints(cs.clone())
@@ -226,7 +226,7 @@ }
#[test] fn test_transcript_and_transcriptvar_nbits() { - let nbits = crate::constants::N_BITS_RO; + let nbits = crate::constants::NOVA_N_BITS_RO; // use 'native' transcript let config = poseidon_canonical_config::<Fq>();