HyperNova: add multi-instances folding to AugmentedFCircuit & IVC (#119) - Adds the logic to support multi-instances folding in HyperNova's AugmentedFCircuit & IVC. - Adds also methods to generate new LCCCS & CCCS instances that don't depend on the main folding chain, to be folded in in the next step - Updates CycleFold circuit & methods to work other folding schemes than Nova, adapting it to fold multiple points per circuit (instead of 2-to-1 as till now) - Handle multi-instances folding in the FoldingScheme trait interface, which expects 'None' in Nova, and 'Some' in HyperNova & other multi-folding schemes.
jump to
@@ -21,10 +21,10 @@ Folding schemes implemented:
- [Nova: Recursive Zero-Knowledge Arguments from Folding Schemes](https://eprint.iacr.org/2021/370.pdf), Abhiram Kothapalli, Srinath Setty, Ioanna Tzialla. 2021 - [CycleFold: Folding-scheme-based recursive arguments over a cycle of elliptic curves](https://eprint.iacr.org/2023/1192.pdf), Abhiram Kothapalli, Srinath Setty. 2023 +- [HyperNova: Recursive arguments for customizable constraint systems](https://eprint.iacr.org/2023/573.pdf), Abhiram Kothapalli, Srinath Setty. 2023 Work in progress: -- [HyperNova: Recursive arguments for customizable constraint systems](https://eprint.iacr.org/2023/573.pdf), Abhiram Kothapalli, Srinath Setty. 2023 - [ProtoGalaxy: Efficient ProtoStar-style folding of multiple instances](https://eprint.iacr.org/2023/1106.pdf), Liam Eagen, Ariel Gabizon. 2023 ## Available frontends
@@ -85,7 +85,7 @@ let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(nova_params.clone(), f_circuit.clone(), z_0).unwrap(); + let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); // prepare the Decider prover & verifier params let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();@@ -93,7 +93,7 @@
// run n steps of the folding iteration for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { let start = Instant::now(); - nova.prove_step(rng, external_inputs_at_step.clone()) + nova.prove_step(rng, external_inputs_at_step.clone(), None) .unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); }
@@ -190,14 +190,13 @@ let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit.clone());
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); println!("Initialize FoldingScheme"); - let mut folding_scheme = - N::init(nova_params.clone(), F_circuit, initial_state.clone()).unwrap(); + let mut folding_scheme = N::init(&nova_params, F_circuit, initial_state.clone()).unwrap(); // compute a step of the IVC for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { let start = Instant::now(); folding_scheme - .prove_step(rng, external_inputs_at_step.clone()) + .prove_step(rng, external_inputs_at_step.clone(), None) .unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); }
@@ -102,7 +102,7 @@ let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit);
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(nova_params.clone(), f_circuit, z_0).unwrap(); + let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap(); // prepare the Decider prover & verifier params let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();@@ -110,7 +110,7 @@
// run n steps of the folding iteration for i in 0..n_steps { let start = Instant::now(); - nova.prove_step(rng, vec![]).unwrap(); + nova.prove_step(rng, vec![], None).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); }
@@ -144,13 +144,12 @@ let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); println!("Initialize FoldingScheme"); - let mut folding_scheme = - N::init(nova_params.clone(), F_circuit, initial_state.clone()).unwrap(); + let mut folding_scheme = N::init(&nova_params, F_circuit, initial_state.clone()).unwrap(); // compute a step of the IVC for i in 0..num_steps { let start = Instant::now(); - folding_scheme.prove_step(rng, vec![]).unwrap(); + folding_scheme.prove_step(rng, vec![], None).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); }
@@ -86,7 +86,7 @@ let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); // initialize the folding scheme engine, in our case we use Nova - let mut nova = N::init(nova_params.clone(), f_circuit.clone(), z_0).unwrap(); + let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); // prepare the Decider prover & verifier params let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();@@ -94,7 +94,7 @@
// run n steps of the folding iteration for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { let start = Instant::now(); - nova.prove_step(rng, external_inputs_at_step.clone()) + nova.prove_step(rng, external_inputs_at_step.clone(), None) .unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); }
@@ -129,12 +129,11 @@ let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); println!("Initialize FoldingScheme"); - let mut folding_scheme = - N::init(nova_params.clone(), F_circuit, initial_state.clone()).unwrap(); + let mut folding_scheme = N::init(&nova_params, F_circuit, initial_state.clone()).unwrap(); // compute a step of the IVC for i in 0..num_steps { let start = Instant::now(); - folding_scheme.prove_step(rng, vec![]).unwrap(); + folding_scheme.prove_step(rng, vec![], None).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); }
@@ -67,7 +67,7 @@
Ok(()) } - fn params_to_bytes(&self) -> Vec<u8> { + fn params_to_le_bytes(&self) -> Vec<u8> { [ self.l.to_le_bytes(), self.m.to_le_bytes(),
@@ -11,5 +11,5 @@ fn check_relation(&self, z: &[F]) -> Result<(), Error>;
/// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of /// public inputs, etc, without the matrices/polynomials values. - fn params_to_bytes(&self) -> Vec<u8>; + fn params_to_le_bytes(&self) -> Vec<u8>; }
@@ -28,7 +28,7 @@ }
Ok(()) } - fn params_to_bytes(&self) -> Vec<u8> { + fn params_to_le_bytes(&self) -> Vec<u8> { [ self.l.to_le_bytes(), self.A.n_rows.to_le_bytes(),
@@ -28,8 +28,13 @@ use crate::frontend::FCircuit;
use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar}; use crate::Error; -/// Public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]| -pub(crate) const CF_IO_LEN: usize = 7; +/// 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 +} /// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova /// circuit.@@ -280,18 +285,24 @@ /// (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 _gc: PhantomData<GC>, - pub r_bits: Option<Vec<bool>>, - pub p1: Option<C>, - pub p2: Option<C>, + /// 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) } impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> { - pub fn empty() -> Self { + /// n_points indicates the number of points being folded in the CycleFoldCircuit + pub fn empty(n_points: usize) -> Self { Self { _gc: PhantomData, + n_points, r_bits: None, - p1: None, - p2: None, + points: None, x: None, } }@@ -304,33 +315,64 @@ <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> { - let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || { - Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO])) + let r_bits: Vec<Vec<Boolean<CF2<C>>>> = 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]) + .iter() + .map(|r_bits_i| { + Vec::<Boolean<CF2<C>>>::new_witness(cs.clone(), || Ok(r_bits_i.clone())) + }) + .collect::<Result<Vec<Vec<Boolean<CF2<C>>>>, SynthesisError>>()?; + let points = Vec::<GC>::new_witness(cs.clone(), || { + Ok(self.points.unwrap_or(vec![C::zero(); self.n_points])) })?; - let p1 = GC::new_witness(cs.clone(), || Ok(self.p1.unwrap_or(C::zero())))?; - let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?; - // Fold the original Nova instances natively in CycleFold - // For the cmW we're computing: U_i1.cmW = U_i.cmW + r * u_i.cmW - // For the cmE we're computing: U_i1.cmE = U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE + + #[cfg(test)] + { + assert_eq!(self.n_points, points.len()); + assert_eq!(self.n_points - 1, r_bits.len()); + } + + // Fold the original points of the instances natively in CycleFold. + // In Nova, + // - for the cmW we're computing: U_i1.cmW = U_i.cmW + r * u_i.cmW + // - for the cmE we're computing: U_i1.cmE = U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE // is assumed to be 0, so, U_i1.cmE = U_i.cmE + r * cmT - let p3 = &p1 + p2.scalar_mul_le(r_bits.iter())?; + 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 { + 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])) + Ok(self + .x + .unwrap_or(vec![CF2::<C>::zero(); cf_io_len(self.n_points)])) })?; #[cfg(test)] - assert_eq!(x.len(), CF_IO_LEN); // non-constrained sanity check + assert_eq!(x.len(), cf_io_len(self.n_points)); // non-constrained sanity check - // check that the points coordinates are placed as the public input x: x == [r, p1, p2, p3] - let r: FpVar<CF2<C>> = Boolean::le_bits_to_fp_var(&r_bits)?; - let points_coords: Vec<FpVar<CF2<C>>> = [ - vec![r], - p1.to_constraint_field()?[..2].to_vec(), - p2.to_constraint_field()?[..2].to_vec(), - p3.to_constraint_field()?[..2].to_vec(), + // 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(); - points_coords.enforce_equal(&x)?; + computed_x.enforce_equal(&x)?; Ok(()) }@@ -341,6 +383,7 @@ /// 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>( + _n_points: usize, transcript: &mut impl Transcript<C1::ScalarField>, cf_r1cs: R1CS<C2::ScalarField>, cf_cs_params: CS2::ProverParams,@@ -386,7 +429,7 @@ return Err(Error::NotEqual);
} #[cfg(test)] - assert_eq!(cf_x_i.len(), CF_IO_LEN); + assert_eq!(cf_x_i.len(), cf_io_len(_n_points)); // fold cyclefold instances let cf_w_i = Witness::<C2>::new(cf_w_i.clone(), cf_r1cs.A.n_rows);@@ -473,9 +516,9 @@ ]
.concat(); let cfW_circuit = CycleFoldCircuit::<Projective, GVar> { _gc: PhantomData, - r_bits: Some(r_bits.clone()), - p1: Some(ci1.clone().cmW), - p2: Some(ci2.clone().cmW), + 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()), }; cfW_circuit.generate_constraints(cs.clone()).unwrap();@@ -492,9 +535,9 @@ ]
.concat(); let cfE_circuit = CycleFoldCircuit::<Projective, GVar> { _gc: PhantomData, - r_bits: Some(r_bits.clone()), - p1: Some(ci1.clone().cmE), - p2: Some(cmT), + n_points: 2, + r_bits: Some(vec![r_bits.clone()]), + points: Some(vec![ci1.clone().cmE, cmT]), x: Some(cfE_u_i_x.clone()), }; cfE_circuit.generate_constraints(cs.clone()).unwrap();@@ -550,7 +593,7 @@ 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(CF_IO_LEN) + .take(7) // 7 = cf_io_len .collect(), }; let U_i = CommittedInstance::<Projective> {@@ -558,7 +601,7 @@ 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(CF_IO_LEN) + .take(7) // 7 = cf_io_len .collect(), }; let cmT = Projective::rand(&mut rng);@@ -617,7 +660,7 @@ 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(CF_IO_LEN) + .take(7) // 7 = cf_io_len in Nova .collect(), }; let pp_hash = Fq::from(42u32); // only for test
@@ -14,7 +14,7 @@ eq::EqGadget,
fields::{fp::FpVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, - R1CSVar, ToConstraintFieldGadget, + R1CSVar, ToBitsGadget, ToConstraintFieldGadget, }; use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError,@@ -31,7 +31,7 @@ };
use crate::constants::N_BITS_RO; use crate::folding::{ circuits::cyclefold::{ - CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN, + cf_io_len, CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, }, circuits::{ nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},@@ -221,18 +221,19 @@ where
<C as CurveGroup>::BaseField: PrimeField, { /// Runs (in-circuit) the NIMFS.V, which outputs the new folded LCCCS instance together with - /// the rho_bits, which will be used in other parts of the AugmentedFCircuit + /// the rho_powers, which will be used in other parts of the AugmentedFCircuit #[allow(clippy::type_complexity)] pub fn verify<S: CryptographicSponge, T: TranscriptVar<C::ScalarField, S>>( cs: ConstraintSystemRef<CF1<C>>, // only used the CCS params, not the matrices ccs: &CCS<C::ScalarField>, transcript: &mut T, - running_instances: &[LCCCSVar<C>], - new_instances: &[CCCSVar<C>], + + running_instances: &[LCCCSVar<C>], // U + new_instances: &[CCCSVar<C>], // u proof: ProofVar<C>, enabled: Boolean<C::ScalarField>, - ) -> Result<(LCCCSVar<C>, Vec<Boolean<CF1<C>>>), SynthesisError> { + ) -> Result<(LCCCSVar<C>, Vec<Vec<Boolean<CF1<C>>>>), SynthesisError> { // absorb instances to transcript for U_i in running_instances { let v = [@@ -315,18 +316,15 @@ transcript.absorb(&rho_scalar)?;
let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(N_BITS_RO)?; let rho = Boolean::le_bits_to_fp_var(&rho_bits)?; - // return the folded instance, together with the rho_bits so they can be used in other - // parts of the AugmentedFCircuit - Ok(( - Self::fold( - running_instances, - new_instances, - proof.sigmas_thetas, - r_x_prime, - rho, - )?, - rho_bits, - )) + // Self::fold will return the folded instance, together with the rho's powers vector so + // they can be used in other parts of the AugmentedFCircuit + Self::fold( + running_instances, + new_instances, + proof.sigmas_thetas, + r_x_prime, + rho, + ) } /// Runs (in-circuit) the verifier side of the fold, computing the new folded LCCCS instance@@ -337,12 +335,14 @@ cccs: &[CCCSVar<C>],
sigmas_thetas: (Vec<Vec<FpVar<CF1<C>>>>, Vec<Vec<FpVar<CF1<C>>>>), r_x_prime: Vec<FpVar<CF1<C>>>, rho: FpVar<CF1<C>>, - ) -> Result<LCCCSVar<C>, SynthesisError> { + ) -> Result<(LCCCSVar<C>, Vec<Vec<Boolean<CF1<C>>>>), SynthesisError> { let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); let mut u_folded: FpVar<CF1<C>> = FpVar::zero(); 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]; let mut rho_i = FpVar::one(); for i in 0..(lcccs.len() + cccs.len()) { let u: FpVar<CF1<C>>;@@ -379,16 +379,30 @@ )
.map(|(a_i, b_i)| a_i + b_i) .collect(); + // compute the next power of rho rho_i *= rho.clone(); + // crop the size of rho_i to 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])?; + 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(); + } } - Ok(LCCCSVar::<C> { - C: lcccs[0].C.clone(), // WIP this will come from the cyclefold circuit - u: u_folded, - x: x_folded, - r_x: r_x_prime, - v: v_folded, - }) + // return the folded instance, together with the rho's powers vector so they can be used in + // other parts of the AugmentedFCircuit + Ok(( + LCCCSVar::<C> { + // C this is later overwritten by the U_{i+1}.C value checked by the cyclefold circuit + C: lcccs[0].C.clone(), + u: u_folded, + x: x_folded, + r_x: r_x_prime, + v: v_folded, + }, + rho_vec, + )) } }@@ -456,16 +470,20 @@ 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>>, pub z_i: Option<Vec<C1::ScalarField>>, pub external_inputs: Option<Vec<C1::ScalarField>>, - pub u_i_C: Option<C1>, // u_i.C pub U_i: Option<LCCCS<C1>>, - pub U_i1_C: Option<C1>, // U_{i+1}.C - pub F: FC, // F circuit - pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0]) + pub Us: Option<Vec<LCCCS<C1>>>, // other U_i's to be folded that are not the main running instance + pub u_i_C: Option<C1>, // u_i.C + pub us: Option<Vec<CCCS<C1>>>, // other u_i's to be folded that are not the main incoming instance + pub U_i1_C: Option<C1>, // U_{i+1}.C + pub F: FC, // F circuit + pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0]) pub nimfs_proof: Option<NIMFSProof<C1>>, // cyclefold verifier on C1@@ -492,20 +510,29 @@ pub fn default(
poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC, ccs: CCS<C1::ScalarField>, - ) -> Self { - Self { + mu: usize, + nu: usize, + ) -> Result<Self, Error> { + if mu < 1 || nu < 1 { + return Err(Error::CantBeZero("mu,nu".to_string())); + } + Ok(Self { _c2: PhantomData, _gc2: PhantomData, poseidon_config: poseidon_config.clone(), ccs, pp_hash: None, + mu, + nu, i: None, i_usize: None, z_0: None, z_i: None, external_inputs: None, - u_i_C: None, U_i: None, + Us: None, + u_i_C: None, + us: None, U_i1_C: None, F: F_circuit, x: None,@@ -514,13 +541,15 @@ cf_u_i_cmW: None,
cf_U_i: None, cf_x: None, cf_cmT: None, - } + }) } pub fn empty( poseidon_config: &PoseidonConfig<CF1<C1>>, - F_circuit: FC, + 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@@ -536,7 +565,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_circuit, initial_ccs); + let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs, mu, nu)?; if ccs.is_some() { augmented_f_circuit.ccs = ccs.unwrap(); } else {@@ -551,51 +580,85 @@ /// For a stable FCircuit circuit, the CCS parameters can be computed in advance and can be
/// feed in as parameter for the AugmentedFCircuit::empty method to avoid computing them there. pub fn upper_bound_ccs(&self) -> Result<CCS<C1::ScalarField>, Error> { let r1cs = get_r1cs_from_cs::<CF1<C1>>(self.clone()).unwrap(); - let ccs = CCS::from_r1cs(r1cs.clone()); + let mut ccs = CCS::from_r1cs(r1cs.clone()); let z_0 = vec![C1::ScalarField::zero(); self.F.state_len()]; - let W_i = Witness::<C1::ScalarField>::dummy(&ccs); - let U_i = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s); - let w_i = W_i.clone(); - let u_i = CCCS::<C1>::dummy(ccs.l); + let mut W_i = Witness::<C1::ScalarField>::dummy(&ccs); + let mut U_i = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s); + let mut w_i = W_i.clone(); + let mut u_i = CCCS::<C1>::dummy(ccs.l); - let mut transcript_p: PoseidonSponge<C1::ScalarField> = - PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config.clone()); - // since this is only for the number of constraints, no need to absorb the pp_hash here - let (nimfs_proof, U_i1, _, _) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove( - &mut transcript_p, - &ccs, - &[U_i.clone()], - &[u_i.clone()], - &[W_i.clone()], - &[w_i.clone()], - )?; + 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 augmented_f_circuit = Self { - _c2: PhantomData, - _gc2: PhantomData, - poseidon_config: self.poseidon_config.clone(), - ccs: ccs.clone(), - pp_hash: Some(C1::ScalarField::zero()), - i: Some(C1::ScalarField::zero()), - i_usize: Some(0), - z_0: Some(z_0.clone()), - z_i: Some(z_0.clone()), - external_inputs: Some(vec![]), - u_i_C: Some(u_i.C), - U_i: Some(U_i.clone()), - U_i1_C: Some(U_i1.C), - F: self.F.clone(), - x: Some(C1::ScalarField::zero()), - nimfs_proof: Some(nimfs_proof), - // cyclefold values - cf_u_i_cmW: None, - cf_U_i: None, - cf_x: None, - cf_cmT: None, - }; + 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(); + let all_ws = [vec![w_i.clone()], ws].concat(); + + let mut transcript_p: PoseidonSponge<C1::ScalarField> = + PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config.clone()); + // since this is only for the number of constraints, no need to absorb the pp_hash here + let (nimfs_proof, U_i1, _, _) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove( + &mut transcript_p, + &ccs, + &all_Us, + &all_us, + &all_Ws, + &all_ws, + )?; - Ok(augmented_f_circuit.compute_cs_ccs()?.1) + let augmented_f_circuit = Self { + _c2: PhantomData, + _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()), + z_i: Some(z_0.clone()), + external_inputs: Some(vec![C1::ScalarField::zero(); self.F.external_inputs_len()]), + U_i: Some(U_i.clone()), + Us: Some(Us), + u_i_C: Some(u_i.C), + us: Some(us), + U_i1_C: Some(U_i1.C), + F: self.F.clone(), + x: Some(C1::ScalarField::zero()), + nimfs_proof: Some(nimfs_proof), + // cyclefold values + cf_u_i_cmW: None, + cf_U_i: None, + cf_x: None, + cf_cmT: None, + }; + + let cs: ConstraintSystem<C1::ScalarField>; + (cs, ccs) = augmented_f_circuit.compute_cs_ccs()?; + // prepare instances for next loop iteration + use crate::arith::r1cs::extract_w_x; + let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::<C1::ScalarField>(&cs); + u_i = CCCS::<C1> { + C: u_i.C, + x: r1cs_x_i1, + }; + w_i = Witness::<C1::ScalarField> { + w: r1cs_w_i1.clone(), + r_w: C1::ScalarField::one(), + }; + W_i = Witness::<C1::ScalarField>::dummy(&ccs); + U_i = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s); + } + Ok(ccs) + + // Ok(augmented_f_circuit.compute_cs_ccs()?.1) } /// Returns the cs (ConstraintSystem) and the CCS out of the AugmentedFCircuit@@ -651,20 +714,25 @@ .unwrap_or(vec![CF1::<C1>::zero(); self.F.external_inputs_len()]))
})?; let U_dummy = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s); + let u_dummy = CCCS::<C1>::dummy(self.ccs.l); 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])) + })?; + let us = Vec::<CCCSVar<C1>>::new_witness(cs.clone(), || { + Ok(self.us.unwrap_or(vec![u_dummy.clone(); self.mu - 1])) + })?; let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || { Ok(self.U_i1_C.unwrap_or_else(C1::zero)) })?; - let mu = 1; // Note: at this commit, only 2-to-1 instance fold is supported - let nu = 1; - let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, mu, nu); + let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, self.mu, self.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); + let cf_u_dummy = CommittedInstance::dummy(cf_io_len(self.mu + self.nu)); let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?;@@ -704,18 +772,21 @@ // u_i.x is computed in step 1
x: vec![u_i_x, cf_u_i_x], }; + let all_Us = [vec![U_i.clone()], Us].concat(); + let all_us = [vec![u_i.clone()], us].concat(); + // P.3. NIMFS.verify, obtains U_{i+1} by folding [U_i] & [u_i]. // Notice that NIMFSGadget::fold_committed_instance does not fold C. We set `U_i1.C` to // unconstrained witnesses `U_i1_C` respectively. Its correctness will be checked on the // other curve. let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); transcript.absorb(&pp_hash)?; - let (mut U_i1, rho_bits) = NIMFSGadget::<C1>::verify( + let (mut U_i1, rho_vec) = NIMFSGadget::<C1>::verify( cs.clone(), &self.ccs.clone(), &mut transcript, - &[U_i.clone()], - &[u_i.clone()], + &all_Us, + &all_us, nimfs_proof, is_not_basecase.clone(), )?;@@ -739,23 +810,36 @@ )?;
let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; - // convert rho_bits to a `NonNativeFieldVar` - let rho_nonnat = { - let mut bits = rho_bits; - bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); - NonNativeUintVar::from(&bits) - }; + // convert rho_bits of the rho_vec to a `NonNativeFieldVar` + let rho_vec_nonnat = rho_vec + .iter() + .map(|rho_i| { + let mut bits = rho_i.clone(); + bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); + NonNativeUintVar::from(&bits) + }) + .collect(); // CycleFold part // C.1. Compute cf1_u_i.x and cf2_u_i.x - let cf_x = vec![ - rho_nonnat, U_i.C.x, U_i.C.y, u_i.C.x, u_i.C.y, U_i1.C.x, U_i1.C.y, - ]; + let cf_x: Vec<NonNativeUintVar<CF2<C2>>> = [ + rho_vec_nonnat, + all_Us + .iter() + .flat_map(|U| vec![U.C.x.clone(), U.C.y.clone()]) + .collect(), + all_us + .iter() + .flat_map(|u| vec![u.C.x.clone(), u.C.y.clone()]) + .collect(), + vec![U_i1.C.x, U_i1.C.y], + ] + .concat(); // ensure that cf_u has as public inputs the C from main instances U_i, u_i, U_i+1 // coordinates of the commitments. // C.2. Construct `cf_u_i` - let cf_u_i = CycleFoldCommittedInstanceVar { + let cf_u_i = CycleFoldCommittedInstanceVar::<C2, GC2> { // cf1_u_i.cmE = 0. Notice that we enforce cmE to be equal to 0 since it is allocated // as 0. cmE: GC2::zero(),@@ -1091,6 +1175,9 @@ 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; + let start = Instant::now(); let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); let mut augmented_f_circuit = AugmentedFCircuit::<@@ -1098,7 +1185,7 @@ Projective,
Projective2, GVar2, CubicFCircuit<Fr>, - >::empty(&poseidon_config, F_circuit, None) + >::empty(&poseidon_config, F_circuit, None, mu, nu) .unwrap(); let ccs = augmented_f_circuit.ccs.clone(); println!("AugmentedFCircuit & CCS generation: {:?}", start.elapsed());@@ -1106,7 +1193,7 @@ 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(); + let cf_circuit = CycleFoldCircuit::<Projective, GVar>::empty(mu + nu); cf_circuit.generate_constraints(cs2.clone()).unwrap(); cs2.finalize(); let cs2 = cs2@@ -1114,6 +1201,7 @@ .into_inner()
.ok_or(Error::NoInnerConstraintSystem) .unwrap(); let cf_r1cs = extract_r1cs::<Fq>(&cs2); + println!("CF m x n: {} x {}", cf_r1cs.A.n_rows, cf_r1cs.A.n_cols); let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();@@ -1152,6 +1240,16 @@ let mut iFr = Fr::zero();
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 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(); + let all_ws = [vec![w_i.clone()], ws].concat(); + let z_i1 = F_circuit.step_native(i, z_i.clone(), vec![]).unwrap(); let (U_i1, W_i1);@@ -1173,13 +1271,17 @@ _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), - U_i: Some(U_i.clone()), + us: Some(us.clone()), U_i1_C: Some(U_i1.C), F: F_circuit, x: Some(u_i1_x),@@ -1195,15 +1297,15 @@ } else {
let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config.clone()); transcript_p.absorb(&pp_hash); - let (rho_bits, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho_bits) = + let (rho_powers, nimfs_proof); + (nimfs_proof, U_i1, W_i1, rho_powers) = NIMFS::<Projective, PoseidonSponge<Fr>>::prove( &mut transcript_p, &ccs, - &[U_i.clone()], - &[u_i.clone()], - &[W_i.clone()], - &[w_i.clone()], + &all_Us, + &all_us, + &all_Ws, + &all_ws, ) .unwrap();@@ -1213,26 +1315,61 @@
let u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); - let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); + let rho_powers_Fq: Vec<Fq> = rho_powers + .iter() + .map(|rho_i| { + Fq::from_bigint(BigInteger::from_bits_le(&rho_i.into_bigint().to_bits_le())) + .unwrap() + }) + .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()) + .collect(); + // CycleFold part: // get the vector used as public inputs 'x' in the CycleFold circuit - // cyclefold circuit for cmW let cf_u_i_x = [ - vec![rho_Fq], + // all values for multiple instances + rho_powers_Fq, get_cm_coordinates(&U_i.C), + Us.iter() + .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) + .collect(), get_cm_coordinates(&u_i.C), + us.iter() + .flat_map(|us_i| get_cm_coordinates(&us_i.C)) + .collect(), get_cm_coordinates(&U_i1.C), ] .concat(); let cf_circuit = CycleFoldCircuit::<Projective, GVar> { _gc: PhantomData, - r_bits: Some(rho_bits.clone()), - p1: Some(U_i.clone().C), - p2: Some(u_i.clone().C), + n_points: mu + nu, + r_bits: Some(rho_powers_bits.clone()), + points: Some( + [ + vec![U_i.clone().C], + Us.iter().map(|Us_i| Us_i.C).collect(), + vec![u_i.clone().C], + us.iter().map(|us_i| us_i.C).collect(), + ] + .concat(), + ), x: Some(cf_u_i_x.clone()), }; + // ensure that the CycleFoldCircuit is well defined + assert_eq!( + cf_circuit.r_bits.clone().unwrap().len(), + cf_circuit.n_points - 1 + ); + assert_eq!( + cf_circuit.points.clone().unwrap().len(), + cf_circuit.n_points + ); + let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::< Projective, GVar,@@ -1242,6 +1379,7 @@ CubicFCircuit<Fr>,
Pedersen<Projective>, Pedersen<Projective2>, >( + mu + nu, &mut transcript_p, cf_r1cs.clone(), cf_pedersen_params.clone(),@@ -1264,13 +1402,17 @@ _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_C: Some(u_i.C), 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),
@@ -21,11 +21,8 @@ use cccs::CCCS;
use lcccs::LCCCS; use nimfs::NIMFS; -use crate::arith::{ - ccs::CCS, - r1cs::{extract_w_x, R1CS}, -}; use crate::commitment::CommitmentScheme; +use crate::constants::N_BITS_RO; use crate::folding::circuits::{ cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}, CF2,@@ -37,7 +34,13 @@ };
use crate::frontend::FCircuit; use crate::utils::{get_cm_coordinates, pp_hash}; use crate::Error; -use crate::FoldingScheme; +use crate::{ + arith::{ + ccs::CCS, + r1cs::{extract_w_x, R1CS}, + }, + FoldingScheme, MultiFolding, +}; /// 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)]@@ -70,6 +73,8 @@ 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)]@@ -136,6 +141,8 @@ /// 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>,@@ -152,6 +159,180 @@ pub cf_W_i: NovaWitness<C2>,
pub cf_U_i: CommittedInstance<C2>, } +impl<C1, GC1, C2, GC2, FC, CS1, CS2> MultiFolding<C1, C2, FC> + for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2> +where + 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>, + CS2: CommitmentScheme<C2>, + <C1 as CurveGroup>::BaseField: PrimeField, + <C2 as CurveGroup>::BaseField: PrimeField, + <C1 as Group>::ScalarField: Absorb, + <C2 as Group>::ScalarField: Absorb, + C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + type RunningInstance = (LCCCS<C1>, Witness<C1::ScalarField>); + type IncomingInstance = (CCCS<C1>, Witness<C1::ScalarField>); + type MultiInstance = (Vec<Self::RunningInstance>, Vec<Self::IncomingInstance>); + + /// Creates a new LCCS instance for the given state, which satisfies the HyperNova.CCS. This + /// method can be used to generate the 'other' LCCS instances to be folded in the multi-folding + /// step. + fn new_running_instance( + &self, + mut rng: impl RngCore, + state: Vec<C1::ScalarField>, + external_inputs: Vec<C1::ScalarField>, + ) -> Result<Self::RunningInstance, Error> { + let r1cs_z = self.new_instance_generic(state, external_inputs)?; + // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we + // assign them directly to w_i, u_i. + let (U_i, W_i) = self + .ccs + .to_lcccs::<_, _, CS1>(&mut rng, &self.cs_params, &r1cs_z)?; + + #[cfg(test)] + U_i.check_relation(&self.ccs, &W_i)?; + + Ok((U_i, W_i)) + } + + /// Creates a new CCCS instance for the given state, which satisfies the HyperNova.CCS. This + /// method can be used to generate the 'other' CCCS instances to be folded in the multi-folding + /// step. + fn new_incoming_instance( + &self, + mut rng: impl RngCore, + state: Vec<C1::ScalarField>, + external_inputs: Vec<C1::ScalarField>, + ) -> Result<Self::IncomingInstance, Error> { + let r1cs_z = self.new_instance_generic(state, external_inputs)?; + // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we + // assign them directly to w_i, u_i. + let (u_i, w_i) = self + .ccs + .to_cccs::<_, _, CS1>(&mut rng, &self.cs_params, &r1cs_z)?; + + #[cfg(test)] + u_i.check_relation(&self.ccs, &w_i)?; + + Ok((u_i, w_i)) + } +} + +impl<C1, GC1, C2, GC2, FC, CS1, CS2> HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2> +where + 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>, + CS2: CommitmentScheme<C2>, + <C1 as CurveGroup>::BaseField: PrimeField, + <C2 as CurveGroup>::BaseField: PrimeField, + <C1 as Group>::ScalarField: Absorb, + <C2 as Group>::ScalarField: Absorb, + C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + /// internal helper for new_running_instance & new_incoming_instance methods, returns the R1CS + /// z=[u,x,w] vector to be used to create the LCCCS & CCCS fresh instances. + fn new_instance_generic( + &self, + state: Vec<C1::ScalarField>, + external_inputs: Vec<C1::ScalarField>, + ) -> 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 sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); + + u_i.x = vec![ + U_i.hash( + &sponge, + self.pp_hash, + C1::ScalarField::zero(), // i + self.z_0.clone(), + state.clone(), + ), + cf_U_i.hash_cyclefold(&sponge, self.pp_hash), + ]; + let us = vec![u_i.clone(); self.nu - 1]; + + let z_i1 = self + .F + .step_native(0, state.clone(), external_inputs.clone())?; + + // compute u_{i+1}.x + let U_i1 = LCCCS::dummy(self.ccs.l, self.ccs.t, self.ccs.s); + let u_i1_x = U_i1.hash( + &sponge, + self.pp_hash, + C1::ScalarField::one(), // i+1, where i=0 + self.z_0.clone(), + 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> { + _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()), + z_i: Some(state.clone()), + external_inputs: Some(external_inputs), + U_i: Some(U_i.clone()), + Us: None, + u_i_C: Some(u_i.C), + us: Some(us), + U_i1_C: Some(U_i1.C), + F: self.F.clone(), + 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, + }; + + let (cs, _) = augmented_f_circuit.compute_cs_ccs()?; + + #[cfg(test)] + assert!(cs.is_satisfied()?); + + let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::<C1::ScalarField>(&cs); // includes 1 and public inputs + + #[cfg(test)] + assert_eq!(r1cs_x_i1[0], augmented_f_circuit.x.unwrap()); + + let r1cs_z = [ + vec![C1::ScalarField::one()], + r1cs_x_i1.clone(), + r1cs_w_i1.clone(), + ] + .concat(); + Ok(r1cs_z) + } +} + impl<C1, GC1, C2, GC2, FC, CS1, CS2> FoldingScheme<C1, C2, FC> for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2> where@@ -170,25 +351,37 @@ C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { - type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2>; + /// 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>, usize, usize); type ProverParam = ProverParams<C1, C2, CS1, CS2>; type VerifierParam = VerifierParams<C1, C2, CS1, CS2>; 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>); 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 { + return Err(Error::CantBeZero("mu,nu".to_string())); + } + let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::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(); + let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(mu + nu); let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?; // if cs params exist, use them, if not, generate new ones@@ -215,6 +408,8 @@ 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> { poseidon_config: prep_param.poseidon_config.clone(),@@ -228,11 +423,15 @@ }
/// Initializes the HyperNova+CycleFold's IVC for the given parameters and initial state `z_0`. fn init( - params: (Self::ProverParam, Self::VerifierParam), + params: &(Self::ProverParam, Self::VerifierParam), F: FC, z_0: Vec<C1::ScalarField>, ) -> Result<Self, Error> { let (pp, vp) = params; + if pp.mu < 1 || pp.nu < 1 { + return Err(Error::CantBeZero("mu,nu".to_string())); + } + // `sponge` is for digest computation. let sponge = PoseidonSponge::<C1::ScalarField>::new(&pp.poseidon_config);@@ -242,10 +441,12 @@ let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::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(); + let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(pp.mu + pp.nu); let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?; // compute the public params hash@@ -282,6 +483,8 @@ 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,@@ -300,10 +503,42 @@ fn prove_step(
&mut self, mut rng: impl RngCore, external_inputs: Vec<C1::ScalarField>, + other_instances: Option<Self::MultiCommittedInstanceWithWitness>, ) -> Result<(), Error> { // `sponge` is for digest computation. let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); + let other_instances = other_instances.ok_or(Error::MissingOtherInstances)?; + + #[allow(clippy::type_complexity)] + let (lcccs, cccs): ( + Vec<(LCCCS<C1>, Witness<C1::ScalarField>)>, + Vec<(CCCS<C1>, Witness<C1::ScalarField>)>, + ) = other_instances; + + // 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 { + return Err(Error::NotSameLength( + "other_instances.lcccs.len()".to_string(), + lcccs.len(), + "hypernova.mu".to_string(), + self.mu, + )); + } + if cccs.len() + 1 != self.nu { + return Err(Error::NotSameLength( + "other_instances.cccs.len()".to_string(), + cccs.len(), + "hypernova.nu".to_string(), + self.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>; if self.z_i.len() != self.F.state_len() {@@ -361,13 +596,17 @@ _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()), z_i: Some(self.z_i.clone()), external_inputs: Some(external_inputs.clone()), - u_i_C: Some(self.u_i.C), U_i: Some(self.U_i.clone()), + Us: Some(Us.clone()), + u_i_C: Some(self.u_i.C), + us: Some(us.clone()), U_i1_C: Some(U_i1.C), F: self.F.clone(), x: Some(u_i1_x),@@ -383,15 +622,15 @@ } else {
let mut transcript_p: PoseidonSponge<C1::ScalarField> = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); transcript_p.absorb(&self.pp_hash); - let (rho_bits, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho_bits) = + let (rho_powers, nimfs_proof); + (nimfs_proof, U_i1, W_i1, rho_powers) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove( &mut transcript_p, &self.ccs, - &[self.U_i.clone()], - &[self.u_i.clone()], - &[self.W_i.clone()], - &[self.w_i.clone()], + &[vec![self.U_i.clone()], Us.clone()].concat(), + &[vec![self.u_i.clone()], us.clone()].concat(), + &[vec![self.W_i.clone()], Ws].concat(), + &[vec![self.w_i.clone()], ws].concat(), )?; // sanity check: check the folded instance relation@@ -406,29 +645,60 @@ self.z_0.clone(),
z_i1.clone(), ); - let rho_Fq = C2::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)) - .ok_or(Error::OutOfBounds)?; + let rho_powers_Fq: Vec<C1::BaseField> = rho_powers + .iter() + .map(|rho_i| { + C1::BaseField::from_bigint(BigInteger::from_bits_le( + &rho_i.into_bigint().to_bits_le(), + )) + .unwrap() + }) + .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()) + .collect(); + // CycleFold part: - // get the vector used as public inputs 'x' in the CycleFold circuit - // cyclefold circuit for cmW + // get the vector used as public inputs 'x' in the CycleFold circuit. + // Place the random values and the points coordinates as the public input x: + // In Nova, this is: x == [r, p1, p2, p3]. + // 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], + // where each p_i is in fact p_i.to_constraint_field() let cf_u_i_x = [ - vec![rho_Fq], + rho_powers_Fq, get_cm_coordinates(&self.U_i.C), + Us.iter() + .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) + .collect(), get_cm_coordinates(&self.u_i.C), + us.iter() + .flat_map(|us_i| get_cm_coordinates(&us_i.C)) + .collect(), get_cm_coordinates(&U_i1.C), ] .concat(); let cf_circuit = CycleFoldCircuit::<C1, GC1> { _gc: PhantomData, - r_bits: Some(rho_bits.clone()), - p1: Some(self.U_i.clone().C), - p2: Some(self.u_i.clone().C), + n_points: self.mu + self.nu, + r_bits: Some(rho_powers_bits.clone()), + points: Some( + [ + vec![self.U_i.clone().C], + Us.iter().map(|Us_i| Us_i.C).collect(), + vec![self.u_i.clone().C], + us.iter().map(|us_i| us_i.C).collect(), + ] + .concat(), + ), 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>( + self.mu + self.nu, &mut transcript_p, self.cf_r1cs.clone(), self.cf_cs_params.clone(),@@ -447,13 +717,17 @@ _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()), z_i: Some(self.z_i.clone()), external_inputs: Some(external_inputs), - u_i_C: Some(self.u_i.C), U_i: Some(self.U_i.clone()), + Us: Some(Us.clone()), + u_i_C: Some(self.u_i.C), + us: Some(us.clone()), U_i1_C: Some(U_i1.C), F: self.F.clone(), x: Some(u_i1_x),@@ -618,31 +892,48 @@ let mut rng = ark_std::test_rng();
type HN<CS1, CS2> = HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2>; + let (mu, nu) = (2, 3); let prep_param = PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2>::new( poseidon_config.clone(), F_circuit, ); - let (prover_params, verifier_params) = HN::preprocess(&mut rng, &prep_param).unwrap(); + let hypernova_params = HN::preprocess(&mut rng, &(prep_param, mu, nu)).unwrap(); let z_0 = vec![Fr::from(3_u32)]; - let mut hypernova = HN::init( - (prover_params, verifier_params.clone()), - F_circuit, - z_0.clone(), - ) - .unwrap(); + let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap(); let num_steps: usize = 3; for _ in 0..num_steps { - hypernova.prove_step(&mut rng, vec![]).unwrap(); + // prepare some new instances to fold in the multifolding step + let mut lcccs = vec![]; + 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![]) + .unwrap(); + lcccs.push((U, W)); + } + let mut cccs = vec![]; + 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![]) + .unwrap(); + cccs.push((u, w)); + } + + dbg!(&hypernova.i); + hypernova + .prove_step(&mut rng, vec![], Some((lcccs, cccs))) + .unwrap(); } assert_eq!(Fr::from(num_steps as u32), hypernova.i); let (running_instance, incoming_instance, cyclefold_instance) = hypernova.instances(); HN::verify( - verifier_params, + hypernova_params.1, // verifier_params z_0, hypernova.z_i, hypernova.i,
@@ -73,7 +73,7 @@ cccs: &[CCCS<C>],
sigmas_thetas: &SigmasThetas<C::ScalarField>, r_x_prime: Vec<C::ScalarField>, rho: C::ScalarField, - ) -> LCCCS<C> { + ) -> (LCCCS<C>, Vec<C::ScalarField>) { let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); let mut C_folded = C::zero(); let mut u_folded = C::ScalarField::zero();@@ -81,6 +81,7 @@ let mut x_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); lcccs[0].x.len()];
let mut v_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); sigmas[0].len()]; let mut rho_i = C::ScalarField::one(); + let mut rho_powers = vec![C::ScalarField::zero(); lcccs.len() + cccs.len() - 1]; for i in 0..(lcccs.len() + cccs.len()) { let c: C; let u: C::ScalarField;@@ -120,16 +121,28 @@ )
.map(|(a_i, b_i)| *a_i + b_i) .collect(); + // compute the next power of rho rho_i *= rho; + // crop the size of rho_i to 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(); + if i < lcccs.len() + cccs.len() - 1 { + // store the cropped rho_i into the rho_powers vector + rho_powers[i] = rho_i; + } } - LCCCS::<C> { - C: C_folded, - u: u_folded, - x: x_folded, - r_x: r_x_prime, - v: v_folded, - } + ( + LCCCS::<C> { + C: C_folded, + u: u_folded, + x: x_folded, + r_x: r_x_prime, + v: v_folded, + }, + rho_powers, + ) } pub fn fold_witness(@@ -140,8 +153,9 @@ ) -> Witness<C::ScalarField> {
let mut w_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); w_lcccs[0].w.len()]; let mut r_w_folded = C::ScalarField::zero(); + let mut rho_i = C::ScalarField::one(); for i in 0..(w_lcccs.len() + w_cccs.len()) { - let rho_i = rho.pow([i as u64]); + // let rho_i = rho.pow([i as u64]); let w: Vec<C::ScalarField>; let r_w: C::ScalarField;@@ -164,6 +178,13 @@ .map(|(a_i, b_i)| *a_i + b_i)
.collect(); 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 + 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(); } Witness { w: w_folded,@@ -183,7 +204,16 @@ running_instances: &[LCCCS<C>],
new_instances: &[CCCS<C>], w_lcccs: &[Witness<C::ScalarField>], w_cccs: &[Witness<C::ScalarField>], - ) -> Result<(NIMFSProof<C>, LCCCS<C>, Witness<C::ScalarField>, Vec<bool>), Error> { + ) -> Result< + ( + NIMFSProof<C>, + LCCCS<C>, + Witness<C::ScalarField>, + // Vec<bool>, + Vec<C::ScalarField>, + ), + Error, + > { // absorb instances to transcript transcript.absorb(&running_instances); transcript.absorb(&new_instances);@@ -247,7 +277,7 @@ let rho: C::ScalarField =
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // Step 7: Create the folded instance - let folded_lcccs = Self::fold( + let (folded_lcccs, rho_powers) = Self::fold( running_instances, new_instances, &sigmas_thetas,@@ -265,7 +295,7 @@ sigmas_thetas,
}, folded_lcccs, folded_witness, - rho_bits, + rho_powers, )) }@@ -372,7 +402,8 @@ new_instances,
&proof.sigmas_thetas, r_x_prime, rho, - )) + ) + .0) } }@@ -422,7 +453,7 @@
let mut rng = test_rng(); let rho = Fr::rand(&mut rng); - let folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold( + let (folded, _) = NIMFS::<Projective, PoseidonSponge<Fr>>::fold( &[lcccs], &[cccs], &sigmas_thetas,
@@ -20,11 +20,11 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{fmt::Debug, One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; -use super::CommittedInstance; +use super::{CommittedInstance, NOVA_CF_N_POINTS}; use crate::constants::N_BITS_RO; use crate::folding::circuits::{ cyclefold::{ - CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN, + cf_io_len, CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, }, nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, CF1, CF2,@@ -337,7 +337,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); + let cf_u_dummy = CommittedInstance::dummy(cf_io_len(NOVA_CF_N_POINTS)); let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) })?;
@@ -361,12 +361,12 @@ let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap(); let start = Instant::now(); - let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap(); + let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); println!("Nova initialized, {:?}", start.elapsed()); let start = Instant::now(); - nova.prove_step(&mut rng, vec![]).unwrap(); + nova.prove_step(&mut rng, vec![], None).unwrap(); println!("prove_step, {:?}", start.elapsed()); - nova.prove_step(&mut rng, vec![]).unwrap(); // do a 2nd step + nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step let mut rng = rand::rngs::OsRng;
@@ -447,11 +447,12 @@ #[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::{CycleFoldCommittedInstanceVar, CF_IO_LEN}; + use crate::folding::circuits::cyclefold::{cf_io_len, CycleFoldCommittedInstanceVar}; use ark_r1cs_std::ToBitsGadget; - let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN); + let cf_u_dummy_native = CommittedInstance::<C2>::dummy(cf_io_len(NOVA_CF_N_POINTS)); let w_dummy_native = Witness::<C2>::new( vec![C2::ScalarField::zero(); self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l], self.cf_E_len,@@ -796,20 +797,15 @@ CubicFCircuit<Fr>,
Pedersen<Projective>, Pedersen<Projective2>, >::new(poseidon_config, F_circuit); - let (prover_params, verifier_params) = N::preprocess(&mut rng, &prep_param).unwrap(); + let nova_params = N::preprocess(&mut rng, &prep_param).unwrap(); // generate a Nova instance and do a step of it - let mut nova = N::init( - (prover_params, verifier_params.clone()), - F_circuit, - z_0.clone(), - ) - .unwrap(); - nova.prove_step(&mut rng, vec![]).unwrap(); + let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); + nova.prove_step(&mut rng, vec![], None).unwrap(); let ivc_v = nova.clone(); let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances(); N::verify( - verifier_params, + nova_params.1, // verifier_params z_0, ivc_v.z_i, Fr::one(),
@@ -37,6 +37,10 @@ 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; + #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance<C: CurveGroup> { pub cmE: C,@@ -344,6 +348,7 @@ type ProverParam = ProverParams<C1, C2, CS1, CS2>;
type VerifierParam = VerifierParams<C1, C2, CS1, CS2>; type RunningInstance = (CommittedInstance<C1>, Witness<C1>); type IncomingInstance = (CommittedInstance<C1>, Witness<C1>); + type MultiCommittedInstanceWithWitness = (); type CFInstance = (CommittedInstance<C2>, Witness<C2>); fn preprocess(@@ -390,7 +395,7 @@ }
/// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`. fn init( - params: (Self::ProverParam, Self::VerifierParam), + params: &(Self::ProverParam, Self::VerifierParam), F: FC, z_0: Vec<C1::ScalarField>, ) -> Result<Self, Error> {@@ -402,7 +407,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(); + let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS); augmented_F_circuit.generate_constraints(cs.clone())?; cs.finalize();@@ -452,6 +457,8 @@ fn prove_step(
&mut self, _rng: impl RngCore, external_inputs: Vec<C1::ScalarField>, + // Nova does not support multi-instances folding + _other_instances: Option<Self::MultiCommittedInstanceWithWitness>, ) -> Result<(), Error> { // `sponge` is for digest computation. let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);@@ -460,6 +467,11 @@ let mut transcript = sponge.clone();
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; + // Nova does not support (by design) multi-instances folding + if _other_instances.is_some() { + return Err(Error::NoMultiInstances); + } + if self.z_i.len() != self.F.state_len() { return Err(Error::NotSameLength( "z_i.len()".to_string(),@@ -572,16 +584,16 @@ .concat();
let cfW_circuit = CycleFoldCircuit::<C1, GC1> { _gc: PhantomData, - r_bits: Some(r_bits.clone()), - p1: Some(self.U_i.clone().cmW), - p2: Some(self.u_i.clone().cmW), + 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> { _gc: PhantomData, - r_bits: Some(r_bits.clone()), - p1: Some(self.U_i.clone().cmE), - p2: Some(cmT), + 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()), };@@ -820,6 +832,7 @@ ),
Error, > { fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2>( + NOVA_CF_N_POINTS, transcript, self.cf_r1cs.clone(), self.cf_cs_pp.clone(),@@ -866,7 +879,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(); + let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS); 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))@@ -944,11 +957,11 @@ };
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap(); let z_0 = vec![Fr::from(3_u32)]; - let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap(); + let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); let num_steps: usize = 3; for _ in 0..num_steps { - nova.prove_step(&mut rng, vec![]).unwrap(); + nova.prove_step(&mut rng, vec![], None).unwrap(); } assert_eq!(Fr::from(num_steps as u32), nova.i);
@@ -12,7 +12,10 @@ use std::marker::PhantomData;
use super::{circuits::AugmentedFCircuit, Nova, ProverParams}; use super::{CommittedInstance, Witness}; -use crate::folding::circuits::{cyclefold::CycleFoldCircuit, CF2}; +use crate::folding::{ + circuits::{cyclefold::CycleFoldCircuit, CF2}, + nova::NOVA_CF_N_POINTS, +}; use crate::{ arith::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1, frontend::FCircuit,@@ -134,7 +137,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(); + let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS); augmented_F_circuit .generate_constraints(cs.clone())@@ -209,11 +212,11 @@ let prep_param = PreprocessorParam::new(poseidon_config.clone(), F_circuit);
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap(); let z_0 = vec![Fr::from(3_u32)]; - let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap(); + let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); let num_steps: usize = 3; for _ in 0..num_steps { - nova.prove_step(&mut rng, vec![]).unwrap(); + nova.prove_step(&mut rng, vec![], None).unwrap(); } let mut writer = vec![];@@ -252,8 +255,10 @@ assert_eq!(nova.i, deserialized_nova.i);
let num_steps: usize = 3; for _ in 0..num_steps { - deserialized_nova.prove_step(&mut rng, vec![]).unwrap(); - nova.prove_step(&mut rng, vec![]).unwrap(); + deserialized_nova + .prove_step(&mut rng, vec![], None) + .unwrap(); + nova.prove_step(&mut rng, vec![], None).unwrap(); } assert_eq!(deserialized_nova.w_i, nova.w_i);
@@ -69,6 +69,8 @@ #[error("The number of folded steps must be greater than 1")]
NotEnoughSteps, #[error("Evaluation failed")] EvaluationFail, + #[error("{0} can not be zero")] + CantBeZero(String), // Commitment errors #[error("Pedersen parameters length is not sufficient (generators.len={0} < vector.len={1} unsatisfied)")]@@ -97,6 +99,10 @@ #[error("BigInt to PrimeField conversion error: {0}")]
BigIntConversionError(String), #[error("Failed to serde: {0}")] JSONSerdeError(String), + #[error("Multi instances folding not supported in this scheme")] + NoMultiInstances, + #[error("Missing 'other' instances, since this is a multi-instances folding scheme")] + MissingOtherInstances, } /// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined@@ -116,6 +122,7 @@ type ProverParam: Debug + Clone;
type VerifierParam: Debug + Clone; type RunningInstance: Debug; // contains the CommittedInstance + Witness type IncomingInstance: Debug; // contains the CommittedInstance + Witness + type MultiCommittedInstanceWithWitness: Debug; // type used for the extra instances in the multi-instance folding setting type CFInstance: Debug; // CycleFold CommittedInstance & Witness fn preprocess(@@ -124,7 +131,7 @@ prep_param: &Self::PreprocessorParam,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error>; fn init( - params: (Self::ProverParam, Self::VerifierParam), + params: &(Self::ProverParam, Self::VerifierParam), step_circuit: FC, z_0: Vec<C1::ScalarField>, // initial state ) -> Result<Self, Error>;@@ -133,6 +140,7 @@ fn prove_step(
&mut self, rng: impl RngCore, external_inputs: Vec<C1::ScalarField>, + other_instances: Option<Self::MultiCommittedInstanceWithWitness>, ) -> Result<(), Error>; // returns the state at the current step@@ -158,6 +166,35 @@ running_instance: Self::RunningInstance,
incoming_instance: Self::IncomingInstance, cyclefold_instance: Self::CFInstance, ) -> Result<(), Error>; +} + +/// Trait with auxiliary methods for multi-folding schemes (ie. HyperNova, ProtoGalaxy, etc), +/// allowing to create new instances for the multifold. +pub trait MultiFolding<C1: CurveGroup, C2: CurveGroup, FC>: Clone + Debug +where + C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>, + C2::BaseField: PrimeField, + FC: FCircuit<C1::ScalarField>, +{ + type RunningInstance: Debug; + type IncomingInstance: Debug; + type MultiInstance: Debug; + + /// Creates a new RunningInstance for the given state, to be folded in the multi-folding step. + fn new_running_instance( + &self, + rng: impl RngCore, + state: Vec<C1::ScalarField>, + external_inputs: Vec<C1::ScalarField>, + ) -> Result<Self::RunningInstance, Error>; + + /// Creates a new IncomingInstance for the given state, to be folded in the multi-folding step. + fn new_incoming_instance( + &self, + rng: impl RngCore, + state: Vec<C1::ScalarField>, + external_inputs: Vec<C1::ScalarField>, + ) -> Result<Self::IncomingInstance, Error>; } pub trait Decider<
@@ -60,9 +60,9 @@ // Fr & Fq modulus bit size
hasher.update(C1::ScalarField::MODULUS_BIT_SIZE.to_le_bytes()); hasher.update(C2::ScalarField::MODULUS_BIT_SIZE.to_le_bytes()); // AugmentedFCircuit Arith params - hasher.update(arith.params_to_bytes()); + hasher.update(arith.params_to_le_bytes()); // CycleFold Circuit Arith params - hasher.update(cf_arith.params_to_bytes()); + hasher.update(cf_arith.params_to_le_bytes()); // cs_vp & cf_cs_vp (commitments setup) let mut cs_vp_bytes = Vec::new(); cs_vp.serialize_uncompressed(&mut cs_vp_bytes)?;
@@ -324,7 +324,7 @@ f_circuit.clone(),
); let nova_params = NOVA::preprocess(&mut rng, &prep_param).unwrap(); let nova = NOVA::init( - nova_params.clone(), + &nova_params, f_circuit.clone(), vec![Fr::zero(); f_circuit.state_len()].clone(), )@@ -358,9 +358,9 @@ NovaCycleFoldVerifierKey::from((decider_vp.clone(), f_circuit.state_len()));
let mut rng = rand::rngs::OsRng; - let mut nova = NOVA::<FC>::init(fs_params, f_circuit, z_0).unwrap(); + let mut nova = NOVA::<FC>::init(&fs_params, f_circuit, z_0).unwrap(); for _ in 0..n_steps { - nova.prove_step(&mut rng, vec![]).unwrap(); + nova.prove_step(&mut rng, vec![], None).unwrap(); } let start = Instant::now();