From ecaecd483c511ba19ab743840989b07ebcce6940 Mon Sep 17 00:00:00 2001 From: winderica Date: Mon, 5 Aug 2024 11:11:49 +0100 Subject: [PATCH] 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 --- folding-schemes/src/constants.rs | 2 +- .../src/folding/circuits/cyclefold.rs | 312 +++++++++++------- folding-schemes/src/folding/hypernova/cccs.rs | 2 +- .../src/folding/hypernova/circuits.rs | 280 ++++++++-------- .../src/folding/hypernova/lcccs.rs | 2 +- folding-schemes/src/folding/hypernova/mod.rs | 204 ++++++------ .../src/folding/hypernova/nimfs.rs | 22 +- folding-schemes/src/folding/nova/circuits.rs | 58 ++-- .../src/folding/nova/decider_eth_circuit.rs | 33 +- folding-schemes/src/folding/nova/mod.rs | 104 ++---- folding-schemes/src/folding/nova/nifs.rs | 9 +- folding-schemes/src/folding/nova/serialize.rs | 14 +- folding-schemes/src/transcript/poseidon.rs | 2 +- 13 files changed, 556 insertions(+), 488 deletions(-) diff --git a/folding-schemes/src/constants.rs b/folding-schemes/src/constants.rs index e2d7f28..e256638 100644 --- a/folding-schemes/src/constants.rs +++ b/folding-schemes/src/constants.rs @@ -2,4 +2,4 @@ // 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; diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 288c957..285b36a 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -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>> where @@ -49,14 +46,14 @@ where pub cmW: GC, pub x: Vec>>, } -impl AllocVar, CF2> for CycleFoldCommittedInstanceVar +impl AllocVar, CF2> for CycleFoldCommittedInstanceVar where C: CurveGroup, GC: CurveVar>, ::BaseField: ark_ff::PrimeField, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - fn new_variable>>( + fn new_variable>>( cs: impl Into>>, f: impl FnOnce() -> Result, mode: AllocationMode, @@ -74,6 +71,29 @@ where } } +impl AbsorbNonNative for CycleFoldCommittedInstance +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) { + [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 AbsorbNonNativeGadget for CycleFoldCommittedInstanceVar where C: CurveGroup, @@ -107,6 +127,25 @@ where } } +impl CycleFoldCommittedInstance +where + ::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>( + &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 CycleFoldCommittedInstanceVar where C: CurveGroup, @@ -114,11 +153,13 @@ where ::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>>( self, @@ -147,13 +188,14 @@ where pub cmW: GC, } -impl AllocVar, CF2> for CommittedInstanceInCycleFoldVar +impl AllocVar, CF2> + for CommittedInstanceInCycleFoldVar where C: CurveGroup, GC: CurveVar>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - fn new_variable>>( + fn new_variable>>( cs: impl Into>>, f: impl FnOnce() -> Result, mode: AllocationMode, @@ -189,25 +231,29 @@ where 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>>, - r_nonnat: NonNativeUintVar>, cmT: GC, ci1: CycleFoldCommittedInstanceVar, // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) ci2: CycleFoldCommittedInstanceVar, ) -> Result, 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::::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::()?, + u: ci1.u.add_no_align(&r_nonnat).modulo::>()?, x: ci1 .x .iter() .zip(ci2.x) .map(|(a, b)| { a.add_no_align(&r_nonnat.mul_no_align(&b)?) - .modulo::() + .modulo::>() }) .collect::, _>>()?, }) @@ -216,14 +262,13 @@ where pub fn verify( // assumes that r_bits is equal to r_nonnat just that in a different format r_bits: Vec>>, - r_nonnat: NonNativeUintVar>, cmT: GC, ci1: CycleFoldCommittedInstanceVar, // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method) ci2: CycleFoldCommittedInstanceVar, ci3: CycleFoldCommittedInstanceVar, ) -> 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 @@ where pub fn get_challenge_native>( transcript: &mut T, pp_hash: C::BaseField, // public params hash - U_i: CommittedInstance, - u_i: CommittedInstance, + U_i: CycleFoldCommittedInstance, + u_i: CycleFoldCommittedInstance, cmT: C, ) -> Vec { 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 @@ where 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::::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; +} + /// 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>> { +pub struct CycleFoldCircuit> { pub _gc: PhantomData, - /// 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>>, /// points to be folded in the CycleFoldCircuit - pub points: Option>, - pub x: Option>>, // public inputs (cf_u_{i+1}.x) + pub points: Option>, + /// public inputs (cf_u_{i+1}.x) + pub x: Option>, } -impl>> CycleFoldCircuit { + +impl> CycleFoldCircuit { /// 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 ConstraintSynthesizer> for CycleFoldCircuit + +impl> ConstraintSynthesizer + for CycleFoldCircuit where - C: CurveGroup, - GC: CurveVar> + ToConstraintFieldGadget>, - ::BaseField: ark_ff::PrimeField, - for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, + GC: ToConstraintFieldGadget, + CFG::F: PrimeField, + for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>, { - fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { - let r_bits: Vec>>> = self + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let r_bits: Vec>> = 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::>>::new_witness(cs.clone(), || Ok(r_bits_i.clone())) + Vec::>::new_witness(cs.clone(), || Ok(r_bits_i.clone())) }) - .collect::>>>, SynthesisError>>()?; + .collect::>()?; let points = Vec::::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 @@ where 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::>>::new_input(cs.clone(), || { - Ok(self - .x - .unwrap_or(vec![CF2::::zero(); cf_io_len(self.n_points)])) + let x = Vec::>::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>> = [ - r_bits - .iter() - .map(|r_bits_i| Boolean::le_bits_to_fp_var(r_bits_i)) - .collect::>>, SynthesisError>>()?, - points - .iter() - .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())) - .collect::>>>, SynthesisError>>()? - .concat(), - p_folded.to_constraint_field()?[..2].to_vec(), - ] - .concat(); + let computed_x: Vec> = r_bits + .iter() + .map(|r_bits_i| { + r_bits_i + .chunks(CFG::FIELD_CAPACITY) + .map(Boolean::le_bits_to_fp_var) + .collect::, _>>() + }) + .chain( + points + .iter() + .chain(&[p_folded]) + .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())), + ) + .collect::, _>>()? + .concat(); computed_x.enforce_equal(&x)?; Ok(()) @@ -383,35 +467,33 @@ where /// 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( - _n_points: usize, +pub fn fold_cyclefold_circuit( transcript: &mut impl Transcript, cf_r1cs: R1CS, cf_cs_params: CS2::ProverParams, - pp_hash: C1::ScalarField, // public params hash - cf_W_i: Witness, // witness of the running instance - cf_U_i: CommittedInstance, // running instance + pp_hash: C1::ScalarField, // public params hash + cf_W_i: CycleFoldWitness, // witness of the running instance + cf_U_i: CycleFoldCommittedInstance, // running instance cf_u_i_x: Vec, - cf_circuit: CycleFoldCircuit, + cf_circuit: CycleFoldCircuit, mut rng: impl RngCore, ) -> Result< ( - Witness, - CommittedInstance, // u_i - Witness, // W_i1 - CommittedInstance, // U_i1 - C2, // cmT - C2::ScalarField, // r_Fq + CycleFoldWitness, + CycleFoldCommittedInstance, // u_i + CycleFoldWitness, // W_i1 + CycleFoldCommittedInstance, // U_i1 + C2, // cmT + C2::ScalarField, // r_Fq ), Error, > where + CFG: CycleFoldConfig>, C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, - FC: FCircuit, - CS1: CommitmentScheme, CS2: CommitmentScheme, ::BaseField: PrimeField, ::BaseField: PrimeField, @@ -431,11 +513,12 @@ where } #[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::::new::(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng); - let cf_u_i: CommittedInstance = cf_w_i.commit::(&cf_cs_params, cf_x_i.clone())?; + let cf_w_i = CycleFoldWitness::::new::(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng); + let cf_u_i: CycleFoldCommittedInstance = + cf_w_i.commit::(&cf_cs_params, cf_x_i.clone())?; // compute T* and cmT* for CycleFoldCircuit let (cf_T, cf_cmT) = NIFS::::compute_cyclefold_cmT( @@ -478,11 +561,22 @@ pub mod tests { use crate::transcript::poseidon::poseidon_canonical_config; use crate::utils::get_cm_coordinates; + struct TestCycleFoldConfig { + _c: PhantomData, + } + + impl CycleFoldConfig for TestCycleFoldConfig { + 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:: { + let ci = CycleFoldCommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), @@ -516,9 +610,8 @@ pub mod tests { get_cm_coordinates(&ci3.cmW), ] .concat(); - let cfW_circuit = CycleFoldCircuit:: { + let cfW_circuit = CycleFoldCircuit::, 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 @@ pub mod tests { get_cm_coordinates(&ci3.cmE), ] .concat(); - let cfE_circuit = CycleFoldCircuit:: { + let cfE_circuit = CycleFoldCircuit::, 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 @@ pub mod tests { #[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::::new_ref(); - let r_nonnatVar = NonNativeUintVar::::new_witness(cs.clone(), || Ok(r_Fr)).unwrap(); let r_bitsVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); let ci1Var = @@ -572,15 +663,8 @@ pub mod tests { .unwrap(); let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap(); - NIFSFullGadget::::verify( - r_bitsVar, - r_nonnatVar, - cmTVar, - ci1Var, - ci2Var, - ci3Var, - ) - .unwrap(); + NIFSFullGadget::::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var) + .unwrap(); assert!(cs.is_satisfied().unwrap()); } @@ -590,20 +674,20 @@ pub mod tests { let poseidon_config = poseidon_canonical_config::(); let mut transcript = PoseidonSponge::::new(&poseidon_config); - let u_i = CommittedInstance:: { + let u_i = CycleFoldCommittedInstance:: { 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::::IO_LEN) .collect(), }; - let U_i = CommittedInstance:: { + let U_i = CycleFoldCommittedInstance:: { 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::::IO_LEN) .collect(), }; let cmT = Projective::rand(&mut rng); @@ -657,12 +741,12 @@ pub mod tests { let poseidon_config = poseidon_canonical_config::(); let sponge = PoseidonSponge::::new(&poseidon_config); - let U_i = CommittedInstance:: { + let U_i = CycleFoldCommittedInstance:: { 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::::IO_LEN) .collect(), }; let pp_hash = Fq::from(42u32); // only for test diff --git a/folding-schemes/src/folding/hypernova/cccs.rs b/folding-schemes/src/folding/hypernova/cccs.rs index 8de9ca7..de5c281 100644 --- a/folding-schemes/src/folding/hypernova/cccs.rs +++ b/folding-schemes/src/folding/hypernova/cccs.rs @@ -26,7 +26,7 @@ pub struct CCCS { } impl CCS { - pub fn to_cccs, const H: bool>( + pub fn to_cccs, const H: bool>( &self, rng: &mut R, cs_params: &CS::ProverParams, diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index 004f49f..107a089 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -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 @@ where let rho_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"rho"); let rho_scalar: FpVar> = FpVar::>::new_constant(cs.clone(), rho_scalar_raw)?; transcript.absorb(&rho_scalar)?; - let rho_bits: Vec>> = transcript.get_challenge_nbits(N_BITS_RO)?; + let rho_bits: Vec>> = 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 @@ where let mut v_folded: Vec>> = vec![FpVar::zero(); sigmas[0].len()]; let mut rho_vec: Vec>>> = - 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>; @@ -381,12 +382,12 @@ where // 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 @@ fn compute_c_gadget( 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>, FC: FCircuit>, + const MU: usize, + const NU: usize, > where for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { @@ -470,8 +491,6 @@ pub struct AugmentedFCircuit< pub poseidon_config: PoseidonConfig>, pub ccs: CCS, // CCS of the AugmentedFCircuit pub pp_hash: Option>, - 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>, pub i_usize: Option, pub z_0: Option>, @@ -487,13 +506,13 @@ pub struct AugmentedFCircuit< pub nimfs_proof: Option>, // cyclefold verifier on C1 - pub cf_u_i_cmW: Option, // input, cf_u_i.cmW - pub cf_U_i: Option>, // input, RelaxedR1CS CycleFold instance - pub cf_x: Option>, // public input (cf_u_{i+1}.x[1]) + pub cf_u_i_cmW: Option, // input, cf_u_i.cmW + pub cf_U_i: Option>, // input, RelaxedR1CS CycleFold instance + pub cf_x: Option>, // public input (cf_u_{i+1}.x[1]) pub cf_cmT: Option, } -impl AugmentedFCircuit +impl AugmentedFCircuit where C1: CurveGroup, C2: CurveGroup, @@ -510,10 +529,8 @@ where poseidon_config: &PoseidonConfig>, F_circuit: FC, ccs: CCS, - mu: usize, - nu: usize, ) -> Result { - if mu < 1 || nu < 1 { + if MU < 1 || NU < 1 { return Err(Error::CantBeZero("mu,nu".to_string())); } Ok(Self { @@ -522,8 +539,6 @@ where poseidon_config: poseidon_config.clone(), ccs, pp_hash: None, - mu, - nu, i: None, i_usize: None, z_0: None, @@ -548,8 +563,6 @@ where poseidon_config: &PoseidonConfig>, F: FC, // FCircuit ccs: Option>, - mu: usize, - nu: usize, ) -> Result { let initial_ccs = CCS { // m, n, s, s_prime and M will be overwritten by the `upper_bound_ccs' method @@ -565,7 +578,7 @@ where 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 @@ where 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 @@ where 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 @@ where } } -impl ConstraintSynthesizer> for AugmentedFCircuit +impl ConstraintSynthesizer> + for AugmentedFCircuit where C1: CurveGroup, C2: CurveGroup, @@ -719,20 +731,21 @@ where let U_i = LCCCSVar::::new_witness(cs.clone(), || Ok(self.U_i.unwrap_or(U_dummy.clone())))?; let Us = Vec::>::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::>::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::::dummy(&self.ccs, self.mu, self.nu); + let nimfs_proof_dummy = NIMFSProof::::dummy(&self.ccs, MU, NU); let nimfs_proof = ProofVar::::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::::IO_LEN); let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?; @@ -861,20 +874,9 @@ where 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::::fold_committed_instance( - cf_r_bits, - cf_r_nonnat, - cf_cmT, - cf_U_i, - cf_u_i, - )?; + let cf_U_i1 = + NIFSFullGadget::::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 @@ mod tests { }, 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 @@ mod tests { let poseidon_config = poseidon_canonical_config::(); let sponge = PoseidonSponge::::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::::new(()).unwrap(); - let mut augmented_f_circuit = AugmentedFCircuit::< - Projective, - Projective2, - GVar2, - CubicFCircuit, - >::empty(&poseidon_config, F_circuit, None, mu, nu) - .unwrap(); + let mut augmented_f_circuit = + AugmentedFCircuit::, 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::::new_ref(); - let cf_circuit = CycleFoldCircuit::::empty(mu + nu); + let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); cf_circuit.generate_constraints(cs2.clone()).unwrap(); cs2.finalize(); let cs2 = cs2 @@ -1224,8 +1229,10 @@ mod tests { let U_dummy = LCCCS::::dummy(ccs.l, ccs.t, ccs.s); let w_dummy = W_dummy.clone(); let u_dummy = CCCS::::dummy(ccs.l); - let (cf_W_dummy, cf_U_dummy): (NovaWitness, CommittedInstance) = - cf_r1cs.dummy_instance(); + let (cf_W_dummy, cf_U_dummy): ( + CycleFoldWitness, + CycleFoldCommittedInstance, + ) = cf_r1cs.dummy_instance(); // set the initial dummy instances let mut W_i = W_dummy.clone(); @@ -1245,10 +1252,10 @@ mod tests { 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 @@ mod tests { // input in the AugmentedFCircuit let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash); - augmented_f_circuit = - AugmentedFCircuit::> { - _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, - - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_x: Some(cf_u_i1_x), - cf_cmT: None, - }; + augmented_f_circuit = AugmentedFCircuit::< + Projective, + Projective2, + GVar2, + CubicFCircuit, + 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, + }; } else { let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config.clone()); @@ -1328,7 +1339,7 @@ mod tests { .collect(); let rho_powers_bits: Vec> = 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 @@ mod tests { ] .concat(); - let cf_circuit = CycleFoldCircuit:: { + let cf_circuit = HyperNovaCycleFoldCircuit:: { _gc: PhantomData, - n_points: mu + nu, r_bits: Some(rho_powers_bits.clone()), points: Some( [ @@ -1367,24 +1377,22 @@ mod tests { // ensure that the CycleFoldCircuit is well defined assert_eq!( cf_circuit.r_bits.clone().unwrap().len(), - cf_circuit.n_points - 1 + HyperNovaCycleFoldConfig::::N_INPUT_POINTS - 1 ); assert_eq!( cf_circuit.points.clone().unwrap().len(), - cf_circuit.n_points + HyperNovaCycleFoldConfig::::N_INPUT_POINTS ); let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::< + HyperNovaCycleFoldConfig, Projective, GVar, Projective2, GVar2, - CubicFCircuit, - Pedersen, Pedersen, false, >( - mu + nu, &mut transcript_p, cf_r1cs.clone(), cf_pedersen_params.clone(), @@ -1401,35 +1409,39 @@ mod tests { // AugmentedFCircuit let cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash); - augmented_f_circuit = - AugmentedFCircuit::> { - _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), - - // 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), - }; + augmented_f_circuit = AugmentedFCircuit::< + Projective, + Projective2, + GVar2, + CubicFCircuit, + 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), + }; // assign the next round instances cf_W_i = cf_W_i1; diff --git a/folding-schemes/src/folding/hypernova/lcccs.rs b/folding-schemes/src/folding/hypernova/lcccs.rs index 1969a4b..8cca4bc 100644 --- a/folding-schemes/src/folding/hypernova/lcccs.rs +++ b/folding-schemes/src/folding/hypernova/lcccs.rs @@ -30,7 +30,7 @@ pub struct LCCCS { } impl CCS { - pub fn to_lcccs, const H: bool>( + pub fn to_lcccs, const H: bool>( &self, rng: &mut R, cs_params: &CS::ProverParams, diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index 8cc2707..4f1c054 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -6,31 +6,29 @@ use ark_crypto_primitives::sponge::{ 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 @@ use crate::{ FoldingScheme, MultiFolding, }; +struct HyperNovaCycleFoldConfig { + _c: PhantomData, +} + +impl CycleFoldConfig + for HyperNovaCycleFoldConfig +{ + 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 = + CycleFoldCircuit, 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 { @@ -73,8 +87,6 @@ where 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>, - pub mu: usize, - pub nu: usize, } #[derive(Debug, Clone)] @@ -114,9 +126,23 @@ where /// 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 -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> + ToConstraintFieldGadget>, C2: CurveGroup, @@ -142,8 +168,6 @@ where 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, @@ -156,12 +180,12 @@ where pub u_i: CCCS, /// CycleFold running instance - pub cf_W_i: NovaWitness, - pub cf_U_i: CommittedInstance, + pub cf_W_i: CycleFoldWitness, + pub cf_U_i: CycleFoldCommittedInstance, } -impl MultiFolding - for HyperNova +impl + MultiFolding for HyperNova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, @@ -227,7 +251,8 @@ where } } -impl HyperNova +impl + HyperNova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, @@ -254,7 +279,8 @@ where // prepare the initial dummy instances let U_i = LCCCS::::dummy(self.ccs.l, self.ccs.t, self.ccs.s); let mut u_i = CCCS::::dummy(self.ccs.l); - let (_, cf_U_i): (NovaWitness, CommittedInstance) = self.cf_r1cs.dummy_instance(); + let (_, cf_U_i): (CycleFoldWitness, CycleFoldCommittedInstance) = + self.cf_r1cs.dummy_instance(); let sponge = PoseidonSponge::::new(&self.poseidon_config); @@ -268,7 +294,7 @@ where ), 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 @@ where ); let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, self.pp_hash); - let augmented_f_circuit = AugmentedFCircuit:: { + let augmented_f_circuit = AugmentedFCircuit:: { _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 @@ where } } -impl FoldingScheme - for HyperNova +impl + FoldingScheme for HyperNova where C1: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, @@ -352,37 +376,32 @@ where 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, usize, usize); + /// Reuse Nova's PreprocessorParam. + type PreprocessorParam = PreprocessorParam; type ProverParam = ProverParams; type VerifierParam = VerifierParams; type RunningInstance = (LCCCS, Witness); type IncomingInstance = (CCCS, Witness); type MultiCommittedInstanceWithWitness = (Vec, Vec); - type CFInstance = (CommittedInstance, NovaWitness); + type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); 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::::empty( + let augmented_f_circuit = AugmentedFCircuit::::empty( &prep_param.poseidon_config, prep_param.F.clone(), None, - *mu, - *nu, )?; let ccs = augmented_f_circuit.ccs.clone(); - let cf_circuit = CycleFoldCircuit::::empty(mu + nu); + let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; // if cs params exist, use them, if not, generate new ones @@ -409,8 +428,6 @@ where cs_params: cs_pp.clone(), cf_cs_params: cf_cs_pp.clone(), ccs: Some(ccs.clone()), - mu: *mu, - nu: *nu, }; let vp = VerifierParams:: { poseidon_config: prep_param.poseidon_config.clone(), @@ -429,7 +446,7 @@ where z_0: Vec, ) -> Result { 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 @@ where // prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS // and R1CS respectively - let augmented_f_circuit = AugmentedFCircuit::::empty( + let augmented_f_circuit = AugmentedFCircuit::::empty( &pp.poseidon_config, F.clone(), pp.ccs.clone(), - pp.mu, - pp.nu, )?; let ccs = augmented_f_circuit.ccs.clone(); - let cf_circuit = CycleFoldCircuit::::empty(pp.mu + pp.nu); + let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; // compute the public params hash @@ -458,7 +473,7 @@ where let U_dummy = LCCCS::::dummy(ccs.l, ccs.t, ccs.s); let w_dummy = W_dummy.clone(); let mut u_dummy = CCCS::::dummy(ccs.l); - let (cf_W_dummy, cf_U_dummy): (NovaWitness, CommittedInstance) = + let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness, CycleFoldCommittedInstance) = cf_r1cs.dummy_instance(); u_dummy.x = vec![ U_dummy.hash( @@ -484,8 +499,6 @@ where 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 @@ where // 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>, Vec>) = lcccs.into_iter().unzip(); let (us, ws): (Vec>, Vec>) = cccs.into_iter().unzip(); - let augmented_f_circuit: AugmentedFCircuit; + let augmented_f_circuit: AugmentedFCircuit; if self.z_i.len() != self.F.state_len() { return Err(Error::NotSameLength( @@ -608,14 +621,12 @@ where // input in the AugmentedFCircuit cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash); - augmented_f_circuit = AugmentedFCircuit:: { + augmented_f_circuit = AugmentedFCircuit:: { _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 @@ where .collect(); let rho_powers_bits: Vec> = 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 @@ where ] .concat(); - let cf_circuit = CycleFoldCircuit:: { + let cf_circuit = HyperNovaCycleFoldCircuit:: { _gc: PhantomData, - n_points: self.mu + self.nu, r_bits: Some(rho_powers_bits.clone()), points: Some( [ @@ -714,30 +724,34 @@ where 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::( - 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, + 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:: { + augmented_f_circuit = AugmentedFCircuit:: { _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 @@ mod tests { ) { let mut rng = ark_std::test_rng(); - let (mu, nu) = (2, 3); + const MU: usize = 2; + const NU: usize = 3; + + type HN = + HyperNova, CS1, CS2, MU, NU, H>; let prep_param = PreprocessorParam::, CS1, CS2, H>::new( poseidon_config.clone(), F_circuit, ); - let hypernova_params = HyperNova::< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit, - 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, - 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 @@ mod tests { 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 @@ mod tests { 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 @@ mod tests { assert_eq!(Fr::from(num_steps as u32), hypernova.i); let (running_instance, incoming_instance, cyclefold_instance) = hypernova.instances(); - HyperNova::, CS1, CS2, H>::verify( + HN::verify( hypernova_params.1, // verifier_params z_0, hypernova.z_i, diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs index 15318eb..37eb647 100644 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ b/folding-schemes/src/folding/hypernova/nimfs.rs @@ -12,7 +12,7 @@ use super::{ 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 @@ where // 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 @@ where // 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 @@ where // 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 = transcript.get_challenge_nbits(N_BITS_RO); + let rho_bits: Vec = 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 @@ where // 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 = transcript.get_challenge_nbits(N_BITS_RO); + let rho_bits: Vec = transcript.get_challenge_nbits(NOVA_N_BITS_RO); let rho: C::ScalarField = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index 49532f3..23c2dff 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -20,11 +20,12 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, 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 @@ where 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 @@ where 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 @@ pub struct AugmentedFCircuit< // 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, // input - pub cf2_u_i_cmW: Option, // input - pub cf_U_i: Option>, // input + pub cf1_u_i_cmW: Option, // input + pub cf2_u_i_cmW: Option, // input + pub cf_U_i: Option>, // input pub cf1_cmT: Option, pub cf2_cmT: Option, pub cf_x: Option>, // public input (u_{i+1}.x[1]) @@ -337,7 +347,7 @@ where 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::::IO_LEN); let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?; @@ -481,19 +491,9 @@ where 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::::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 @@ where 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::::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 @@ pub mod tests { 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] diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 090026b..eafe183 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -22,14 +22,18 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, 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 @@ where } } -/// 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 { @@ -166,12 +170,12 @@ pub struct CycleFoldWitnessVar { pub rW: NonNativeUintVar>, } -impl AllocVar, CF2> for CycleFoldWitnessVar +impl AllocVar, CF2> for CycleFoldWitnessVar where C: CurveGroup, ::BaseField: PrimeField, { - fn new_variable>>( + fn new_variable>>( cs: impl Into>>, f: impl FnOnce() -> Result, mode: AllocationMode, @@ -237,8 +241,8 @@ where pub cmT: Option, pub r: Option, /// CycleFold running instance - pub cf_U_i: Option>, - pub cf_W_i: Option>, + pub cf_U_i: Option>, + pub cf_W_i: Option>, /// KZG challenges pub kzg_c_W: Option, @@ -447,14 +451,19 @@ where { // 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::::dummy(cf_io_len(NOVA_CF_N_POINTS)); - let w_dummy_native = - Witness::::dummy(self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l, self.cf_E_len); + let cf_u_dummy_native = + CycleFoldCommittedInstance::::dummy(NovaCycleFoldConfig::::IO_LEN); + let w_dummy_native = CycleFoldWitness::::dummy( + self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l, + self.cf_E_len, + ); let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 5d3571f..96f3d57 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -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: PhantomData, +} + +impl CycleFoldConfig for NovaCycleFoldConfig { + const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; + const N_INPUT_POINTS: usize = 2; + type C = C; + type F = C::BaseField; +} + +type NovaCycleFoldCircuit = CycleFoldCircuit, GC>; #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance { @@ -84,30 +98,6 @@ where } } -impl AbsorbNonNative for CommittedInstance -where - ::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) { - [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 CommittedInstance where ::ScalarField: Absorb, @@ -135,25 +125,6 @@ where } } -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, - 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 { pub E: Vec, @@ -342,8 +313,8 @@ where pub U_i: CommittedInstance, /// CycleFold running instance - pub cf_W_i: Witness, - pub cf_U_i: CommittedInstance, + pub cf_W_i: CycleFoldWitness, + pub cf_U_i: CycleFoldCommittedInstance, } impl FoldingScheme @@ -370,7 +341,7 @@ where type RunningInstance = (CommittedInstance, Witness); type IncomingInstance = (CommittedInstance, Witness); type MultiCommittedInstanceWithWitness = (); - type CFInstance = (CommittedInstance, Witness); + type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); fn preprocess( mut rng: impl RngCore, @@ -428,7 +399,7 @@ where let augmented_F_circuit = AugmentedFCircuit::::empty(&pp.poseidon_config, F.clone()); - let cf_circuit = CycleFoldCircuit::::empty(NOVA_CF_N_POINTS); + let cf_circuit = NovaCycleFoldCircuit::::empty(); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize(); @@ -619,16 +590,14 @@ where ] .concat(); - let cfW_circuit = CycleFoldCircuit:: { + let cfW_circuit = NovaCycleFoldCircuit:: { _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:: { + let cfE_circuit = NovaCycleFoldCircuit:: { _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 @@ where fn fold_cyclefold_circuit>( &self, transcript: &mut T, - cf_W_i: Witness, // witness of the running instance - cf_U_i: CommittedInstance, // running instance + cf_W_i: CycleFoldWitness, // witness of the running instance + cf_U_i: CycleFoldCommittedInstance, // running instance cf_u_i_x: Vec, - cf_circuit: CycleFoldCircuit, + cf_circuit: NovaCycleFoldCircuit, rng: &mut impl RngCore, ) -> Result< ( - Witness, - CommittedInstance, // u_i - Witness, // W_i1 - CommittedInstance, // U_i1 - C2, // cmT - C2::ScalarField, // r_Fq + CycleFoldWitness, + CycleFoldCommittedInstance, // u_i + CycleFoldWitness, // W_i1 + CycleFoldCommittedInstance, // U_i1 + C2, // cmT + C2::ScalarField, // r_Fq ), Error, > { - fold_cyclefold_circuit::( - NOVA_CF_N_POINTS, + fold_cyclefold_circuit::, C1, GC1, C2, GC2, CS2, H>( transcript, self.cf_r1cs.clone(), self.cf_cs_pp.clone(), @@ -920,7 +888,7 @@ where { let augmented_F_circuit = AugmentedFCircuit::::empty(poseidon_config, F_circuit); - let cf_circuit = CycleFoldCircuit::::empty(NOVA_CF_N_POINTS); + let cf_circuit = NovaCycleFoldCircuit::::empty(); let r1cs = get_r1cs_from_cs::(augmented_F_circuit)?; let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; Ok((r1cs, cf_r1cs)) diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs.rs index 7eb4900..2f4152c 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs.rs @@ -6,6 +6,7 @@ use std::marker::PhantomData; 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 @@ where pub fn compute_cyclefold_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) - w1: &Witness, - ci1: &CommittedInstance, - w2: &Witness, - ci2: &CommittedInstance, + w1: &CycleFoldWitness, + ci1: &CycleFoldCommittedInstance, + w2: &CycleFoldWitness, + ci2: &CycleFoldCommittedInstance, ) -> Result<(Vec, C), Error> where ::BaseField: ark_ff::PrimeField, diff --git a/folding-schemes/src/folding/nova/serialize.rs b/folding-schemes/src/folding/nova/serialize.rs index f87b348..fdb070d 100644 --- a/folding-schemes/src/folding/nova/serialize.rs +++ b/folding-schemes/src/folding/nova/serialize.rs @@ -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 @@ where let cs2 = ConstraintSystem::::new_ref(); let augmented_F_circuit = AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone()); - let cf_circuit = CycleFoldCircuit::::empty(NOVA_CF_N_POINTS); + let cf_circuit = NovaCycleFoldCircuit::::empty(); augmented_F_circuit .generate_constraints(cs.clone()) diff --git a/folding-schemes/src/transcript/poseidon.rs b/folding-schemes/src/transcript/poseidon.rs index 0496a17..3813109 100644 --- a/folding-schemes/src/transcript/poseidon.rs +++ b/folding-schemes/src/transcript/poseidon.rs @@ -226,7 +226,7 @@ pub mod tests { #[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::();