From 7097c001fc876578be2229d3590d506858bc0069 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 20 Aug 2024 04:05:24 +0200 Subject: [PATCH] Optimize CycleFold circuit MSM approach (#143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In CycleFold we want to compute $P_{folded} = P_0 + r ⋅ P_1 + r^2 ⋅ P_2 + r^3 ⋅ P_3 + ... + r^{n-2} ⋅ P_{n-2} + r^{n-1} ⋅ P_{n-1}$, since the scalars follow the pattern r^i Youssef El Housni (@yelhousni) proposed to update the approach of the CycleFold circuit to reduce the number of constraints needed, by computing $P_{folded} = (((P_{n-1} ⋅ r + P_{n-2}) ⋅ r + P_{n-3})... ) ⋅ r + P_0$. By itself, this update reduces the number of constraints as the number of points being folded in the CycleFold circuit grows. But it also has impact at the HyperNova circuit, where it removes the need of using the bit representations of the powers of the random value, substancially reducing the amount of constraints used by the HyperNova AugmentedFCircuit. The number of constraints difference in the CycleFold circuit and in the HyperNova's AugmentedFCircuit: - CycleFold circuit: | num points* | old | new | diff | |-------------|-----------|-----------|----------| | 2 | 1_354 | 1_354 | 0 | | 3 | 2_683 | 2_554 | -129 | | 4 | 4_012 | 3_754 | -258 | | 8 | 9_328 | 8_554 | -744 | | 16 | 19_960 | 18_154 | -1_806 | | 32 | 41_224 | 37_354 | -3_870 | | 64 | 83_752 | 75_754 | -7_998 | | 128 | 168_808 | 152_554 | -16_254 | | 1024 | 1_359_592 | 1_227_754 | -131_838 | *num points: number of points being folded by the CycleFold circuit. - HyperNova AugmentedFCircuit circuit | folded instances* | old | new | diff | |-------------------|---------|---------|----------| | 5 | 90_285 | 80_150 | -10_135 | | 10 | 144_894 | 117_655 | -27_239 | | 20 | 249_839 | 192_949 | -56_890 | | 40 | 463_078 | 344_448 | -118_630 | *folded instances: folded instances per step, half of them being LCCCS and the other half CCCS. Co-authored-by: Youssef El Housni --- .../src/folding/circuits/cyclefold.rs | 153 ++++++++---------- .../src/folding/hypernova/circuits.rs | 103 +++++------- .../folding/hypernova/decider_eth_circuit.rs | 50 ++---- folding-schemes/src/folding/hypernova/mod.rs | 41 ++--- .../src/folding/hypernova/nimfs.rs | 48 ++---- folding-schemes/src/folding/nova/mod.rs | 4 +- 6 files changed, 152 insertions(+), 247 deletions(-) diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 27bb75e..36ba7d0 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -376,14 +376,12 @@ pub trait CycleFoldConfig { /// 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]|`. + /// * In general, `|[r, (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` + /// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY + 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 + Self::RANDOMNESS_BIT_LENGTH.div_ceil(Self::FIELD_CAPACITY) + 2 * Self::N_INPUT_POINTS + 2 }; type F: Field; @@ -396,10 +394,9 @@ pub trait CycleFoldConfig { #[derive(Debug, Clone)] pub struct CycleFoldCircuit> { pub _gc: PhantomData, - /// 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>>, + /// r_bits is the bit representation of the r whose powers are used in the + /// random-linear-combination inside the CycleFoldCircuit + pub r_bits: Option>, /// points to be folded in the CycleFoldCircuit pub points: Option>, /// public inputs (cf_u_{i+1}.x) @@ -426,18 +423,11 @@ where for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>, { 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; CFG::RANDOMNESS_BIT_LENGTH]; - CFG::N_INPUT_POINTS - 1 - ]) - .iter() - .map(|r_bits_i| { - Vec::>::new_witness(cs.clone(), || Ok(r_bits_i.clone())) - }) - .collect::>()?; + let r_bits = Vec::>::new_witness(cs.clone(), || { + Ok(self + .r_bits + .unwrap_or(vec![false; CFG::RANDOMNESS_BIT_LENGTH])) + })?; let points = Vec::::new_witness(cs.clone(), || { Ok(self .points @@ -447,10 +437,7 @@ where #[cfg(test)] { 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); - } + assert_eq!(CFG::RANDOMNESS_BIT_LENGTH, r_bits.len()); } // Fold the original points of the instances natively in CycleFold. @@ -458,11 +445,13 @@ where // - 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 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..CFG::N_INPUT_POINTS - 1 { - p_folded += points[i + 1].scalar_mul_le(r_bits[i].iter())?; + // We want to compute + // P_folded = p_0 + r * P_1 + r^2 * P_2 + r^3 * P_3 + ... + r^{n-2} * P_{n-2} + r^{n-1} * P_{n-1} + // so in order to do it more efficiently (less constraints) we do + // P_folded = (((P_{n-1} * r + P_{n-2}) * r + P_{n-3})... ) * r + P_0 + let mut p_folded: GC = points[CFG::N_INPUT_POINTS - 1].clone(); + for i in (0..CFG::N_INPUT_POINTS - 1).rev() { + p_folded = p_folded.scalar_mul_le(r_bits.iter())? + points[i].clone(); } let x = Vec::>::new_input(cs.clone(), || { @@ -474,24 +463,23 @@ where // 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], + // computed_x = [r, 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 + let r_fp = Boolean::le_bits_to_fp_var(&r_bits)?; + let points_aux: Vec> = points .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(); + .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())) + .collect::, SynthesisError>>()? + .into_iter() + .flatten() + .collect(); + + let computed_x: Vec> = [ + vec![r_fp], + points_aux, + p_folded.to_constraint_field()?[..2].to_vec(), + ] + .concat(); computed_x.enforce_equal(&x)?; Ok(()) @@ -596,13 +584,13 @@ pub mod tests { use crate::transcript::poseidon::poseidon_canonical_config; use crate::utils::get_cm_coordinates; - struct TestCycleFoldConfig { + struct TestCycleFoldConfig { _c: PhantomData, } - impl CycleFoldConfig for TestCycleFoldConfig { + impl CycleFoldConfig for TestCycleFoldConfig { const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; - const N_INPUT_POINTS: usize = 2; + const N_INPUT_POINTS: usize = N; type C = C; type F = C::BaseField; } @@ -630,46 +618,45 @@ pub mod tests { } #[test] - fn test_CycleFoldCircuit_constraints() { - let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs(); - let r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); + fn test_CycleFoldCircuit_n_points_constraints() { + const n: usize = 16; + let mut rng = ark_std::test_rng(); + + // points to random-linear-combine + let points: Vec = std::iter::repeat_with(|| Projective::rand(&mut rng)) + .take(n) + .collect(); + + use std::ops::Mul; + let rho_raw = Fq::rand(&mut rng); + let rho_bits = rho_raw.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); + let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); + let rho_Fr = Fr::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); + let mut res = Projective::zero(); + use ark_std::One; + let mut rho_i = Fr::one(); + for point_i in points.iter() { + res += point_i.mul(rho_i); + rho_i *= rho_Fr; + } // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field // (E1::Fq=E2::Fr) let cs = ConstraintSystem::::new_ref(); - let cfW_u_i_x: Vec = [ - vec![r_Fq], - get_cm_coordinates(&ci1.cmW), - get_cm_coordinates(&ci2.cmW), - get_cm_coordinates(&ci3.cmW), - ] - .concat(); - let cfW_circuit = CycleFoldCircuit::, GVar> { - _gc: PhantomData, - 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(); - assert!(cs.is_satisfied().unwrap()); - - // same for E: - let cs = ConstraintSystem::::new_ref(); - let cfE_u_i_x = [ - vec![r_Fq], - get_cm_coordinates(&ci1.cmE), - get_cm_coordinates(&cmT), - get_cm_coordinates(&ci3.cmE), + let x: Vec = [ + vec![rho_Fq], + points.iter().flat_map(get_cm_coordinates).collect(), + get_cm_coordinates(&res), ] .concat(); - let cfE_circuit = CycleFoldCircuit::, GVar> { + let cf_circuit = CycleFoldCircuit::, GVar> { _gc: PhantomData, - r_bits: Some(vec![r_bits.clone()]), - points: Some(vec![ci1.clone().cmE, cmT]), - x: Some(cfE_u_i_x.clone()), + r_bits: Some(rho_bits), + points: Some(points), + x: Some(x.clone()), }; - cfE_circuit.generate_constraints(cs.clone()).unwrap(); + cf_circuit.generate_constraints(cs.clone()).unwrap(); assert!(cs.is_satisfied().unwrap()); } @@ -714,7 +701,7 @@ pub mod tests { u: Fr::zero(), cmW: Projective::rand(&mut rng), x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(TestCycleFoldConfig::::IO_LEN) + .take(TestCycleFoldConfig::::IO_LEN) .collect(), }; let U_i = CycleFoldCommittedInstance:: { @@ -722,7 +709,7 @@ pub mod tests { u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(TestCycleFoldConfig::::IO_LEN) + .take(TestCycleFoldConfig::::IO_LEN) .collect(), }; let cmT = Projective::rand(&mut rng); @@ -781,7 +768,7 @@ pub mod tests { u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: std::iter::repeat_with(|| Fr::rand(&mut rng)) - .take(TestCycleFoldConfig::::IO_LEN) + .take(TestCycleFoldConfig::::IO_LEN) .collect(), }; let pp_hash = Fq::from(42u32); // only for test diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index 107a089..f4bb388 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -14,7 +14,7 @@ use ark_r1cs_std::{ fields::{fp::FpVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, - R1CSVar, ToBitsGadget, ToConstraintFieldGadget, + R1CSVar, ToConstraintFieldGadget, }; use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, @@ -234,7 +234,7 @@ where new_instances: &[CCCSVar], // u proof: ProofVar, enabled: Boolean, - ) -> Result<(LCCCSVar, Vec>>>), SynthesisError> { + ) -> Result<(LCCCSVar, Vec>>), SynthesisError> { // absorb instances to transcript for U_i in running_instances { let v = [ @@ -317,15 +317,16 @@ where 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 - // they can be used in other parts of the AugmentedFCircuit - Self::fold( + // Self::fold will return the folded instance + let folded_lcccs = Self::fold( running_instances, new_instances, proof.sigmas_thetas, r_x_prime, rho, - ) + )?; + // return the rho_bits so it can be used in other parts of the AugmentedFCircuit + Ok((folded_lcccs, rho_bits)) } /// Runs (in-circuit) the verifier side of the fold, computing the new folded LCCCS instance @@ -336,14 +337,12 @@ where sigmas_thetas: (Vec>>>, Vec>>>), r_x_prime: Vec>>, rho: FpVar>, - ) -> Result<(LCCCSVar, Vec>>>), SynthesisError> { + ) -> Result, SynthesisError> { let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); let mut u_folded: FpVar> = FpVar::zero(); let mut x_folded: Vec>> = vec![FpVar::zero(); lcccs[0].x.len()]; let mut v_folded: Vec>> = vec![FpVar::zero(); sigmas[0].len()]; - let mut rho_vec: Vec>>> = - 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>; @@ -382,28 +381,18 @@ where // compute the next power of rho rho_i *= rho.clone(); - // 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[..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[..NOVA_N_BITS_RO].to_vec(); - } } // 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 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, - )) + Ok(LCCCSVar:: { + // 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, + }) } } @@ -734,7 +723,7 @@ where 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(); MU - 1])) + Ok(self.us.unwrap_or(vec![u_dummy.clone(); NU - 1])) })?; let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || { Ok(self.U_i1_C.unwrap_or_else(C1::zero)) @@ -794,7 +783,7 @@ where // other curve. let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); transcript.absorb(&pp_hash)?; - let (mut U_i1, rho_vec) = NIMFSGadget::::verify( + let (mut U_i1, rho_bits) = NIMFSGadget::::verify( cs.clone(), &self.ccs.clone(), &mut transcript, @@ -824,19 +813,14 @@ where x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; // 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(); + let mut rho_bits_resized = rho_bits.clone(); + rho_bits_resized.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); + let rho_nonnat = NonNativeUintVar::from(&rho_bits_resized); // CycleFold part // C.1. Compute cf1_u_i.x and cf2_u_i.x let cf_x: Vec>> = [ - rho_vec_nonnat, + vec![rho_nonnat], all_Us .iter() .flat_map(|U| vec![U.C.x.clone(), U.C.y.clone()]) @@ -1312,17 +1296,16 @@ mod tests { let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config.clone()); transcript_p.absorb(&pp_hash); - let (rho_powers, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho_powers) = - NIMFS::>::prove( - &mut transcript_p, - &ccs, - &all_Us, - &all_us, - &all_Ws, - &all_ws, - ) - .unwrap(); + let (rho, nimfs_proof); + (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( + &mut transcript_p, + &ccs, + &all_Us, + &all_us, + &all_Ws, + &all_ws, + ) + .unwrap(); // sanity check: check the folded instance relation U_i1.check_relation(&ccs, &W_i1).unwrap(); @@ -1330,23 +1313,13 @@ mod tests { let u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); - let rho_powers_Fq: Vec = 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> = rho_powers - .iter() - .map(|rho_i| rho_i.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec()) - .collect(); + let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); + let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // CycleFold part: // get the vector used as public inputs 'x' in the CycleFold circuit let cf_u_i_x = [ - // all values for multiple instances - rho_powers_Fq, + vec![rho_Fq], get_cm_coordinates(&U_i.C), Us.iter() .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) @@ -1361,7 +1334,7 @@ mod tests { let cf_circuit = HyperNovaCycleFoldCircuit:: { _gc: PhantomData, - r_bits: Some(rho_powers_bits.clone()), + r_bits: Some(rho_bits.clone()), points: Some( [ vec![U_i.clone().C], @@ -1375,10 +1348,6 @@ mod tests { }; // ensure that the CycleFoldCircuit is well defined - assert_eq!( - cf_circuit.r_bits.clone().unwrap().len(), - HyperNovaCycleFoldConfig::::N_INPUT_POINTS - 1 - ); assert_eq!( cf_circuit.points.clone().unwrap().len(), HyperNovaCycleFoldConfig::::N_INPUT_POINTS diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs index 93ba6bb..ac95cbb 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs @@ -168,9 +168,8 @@ where pub U_i1: Option>, pub W_i1: Option>, pub nimfs_proof: Option>, - // rho_0 is the first and only rho in the 'rho_powers' array, since it comes from NIMFS-folding - // only 2 instances. - pub rho_0: Option, + // rho is the 'random' value used for the fold of the last 2 instances + pub rho: Option, /// CycleFold running instance pub cf_U_i: Option>, pub cf_W_i: Option>, @@ -199,15 +198,14 @@ where // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances let mut transcript = PoseidonSponge::::new(&hn.poseidon_config); transcript.absorb(&hn.pp_hash); - let (nimfs_proof, U_i1, W_i1, rho_powers) = - NIMFS::>::prove( - &mut transcript, - &hn.ccs, - &[hn.U_i.clone()], - &[hn.u_i.clone()], - &[hn.W_i.clone()], - &[hn.w_i.clone()], - )?; + let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( + &mut transcript, + &hn.ccs, + &[hn.U_i.clone()], + &[hn.u_i.clone()], + &[hn.W_i.clone()], + &[hn.w_i.clone()], + )?; // compute the KZG challenges used as inputs in the circuit let kzg_challenge = @@ -222,11 +220,6 @@ where let p_W = poly_from_vec(W.to_vec())?; let eval_W = p_W.evaluate(&kzg_challenge); - // ensure that we only have 1 element in rho_powers, since we only NIMFS-fold 2 instances - if rho_powers.len() != 1 { - return Err(Error::NotExpectedLength(rho_powers.len(), 1)); - } - Ok(Self { _c1: PhantomData, _gc1: PhantomData, @@ -251,7 +244,7 @@ where U_i1: Some(U_i1), W_i1: Some(W_i1), nimfs_proof: Some(nimfs_proof), - rho_0: Some(rho_powers[0]), + rho: Some(rho), cf_U_i: Some(hn.cf_U_i), cf_W_i: Some(hn.cf_W_i), kzg_challenge: Some(kzg_challenge), @@ -428,7 +421,7 @@ where // The following steps are in non-increasing order because the `computed_U_i1` is computed // at step 8, and later used at step 6. Notice that in Nova, we compute U_i1 outside of the // circuit, in the smart contract, but here we're computing it in-circuit, and we reuse the - // `rho_vec` computed along the way of computing `computed_U_i1` for the later `rho_powers` + // `rho_bits` computed along the way of computing `computed_U_i1` for the later `rho_powers` // check (6.b). // Check 7 is temporary disabled due @@ -445,7 +438,7 @@ where // Notice that the NIMFSGadget performs all the logic except of checking the fold of the // instances C parameter, which would require non-native arithmetic, henceforth we perform // that check outside the circuit. - let (computed_U_i1, rho_vec) = NIMFSGadget::::verify( + let (computed_U_i1, rho_bits) = NIMFSGadget::::verify( cs.clone(), &self.ccs.clone(), &mut transcript, @@ -471,20 +464,11 @@ where computed_U_i1.v.enforce_equal(&U_i1.v)?; // 8.b check that the in-circuit computed r is equal to the inputted r. - // Notice that rho_vec only contains one element, since at the final fold we are only - // folding 2-to-1 instances. - - // Ensure that rho_vec is of length 1, note that this is not enforced at the constraint - // level but more as a check for the prover. - if rho_vec.len() != 1 { - return Err(SynthesisError::Unsatisfiable); - } - let rho_0 = Boolean::le_bits_to_fp_var(&rho_vec[0])?; - let external_rho_0 = FpVar::>::new_input(cs.clone(), || { - Ok(self.rho_0.unwrap_or(CF1::::zero())) - })?; - rho_0.enforce_equal(&external_rho_0)?; + let rho = Boolean::le_bits_to_fp_var(&rho_bits)?; + let external_rho = + FpVar::>::new_input(cs.clone(), || Ok(self.rho.unwrap_or(CF1::::zero())))?; + rho.enforce_equal(&external_rho)?; Ok(()) } diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index 38d1ead..c2656a0 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -664,16 +664,15 @@ where let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&self.poseidon_config); transcript_p.absorb(&self.pp_hash); - let (rho_powers, nimfs_proof); - (nimfs_proof, U_i1, W_i1, rho_powers) = - NIMFS::>::prove( - &mut transcript_p, - &self.ccs, - &[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(), - )?; + let (rho, nimfs_proof); + (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( + &mut transcript_p, + &self.ccs, + &[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 #[cfg(test)] @@ -687,29 +686,18 @@ where z_i1.clone(), ); - let rho_powers_Fq: Vec = 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> = rho_powers - .iter() - .map(|rho_i| rho_i.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec()) - .collect(); + let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); + let rho_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // CycleFold part: // 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], + // computed_x = [r, 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 = [ - rho_powers_Fq, + vec![rho_Fq], get_cm_coordinates(&self.U_i.C), Us.iter() .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) @@ -724,7 +712,7 @@ where let cf_circuit = HyperNovaCycleFoldCircuit:: { _gc: PhantomData, - r_bits: Some(rho_powers_bits.clone()), + r_bits: Some(rho_bits.clone()), points: Some( [ vec![self.U_i.clone().C], @@ -991,7 +979,6 @@ mod tests { cccs.push((u, w)); } - dbg!(&hypernova.i); hypernova .prove_step(&mut rng, vec![], Some((lcccs, cccs))) .unwrap(); diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs index 37eb647..2a70b28 100644 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ b/folding-schemes/src/folding/hypernova/nimfs.rs @@ -73,7 +73,7 @@ where sigmas_thetas: &SigmasThetas, r_x_prime: Vec, rho: C::ScalarField, - ) -> (LCCCS, Vec) { + ) -> LCCCS { 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,7 +81,6 @@ where let mut v_folded: Vec = 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; @@ -123,28 +122,15 @@ where // compute the next power of rho rho_i *= rho; - // 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[..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; - } } - ( - LCCCS:: { - C: C_folded, - u: u_folded, - x: x_folded, - r_x: r_x_prime, - v: v_folded, - }, - rho_powers, - ) + LCCCS:: { + C: C_folded, + u: u_folded, + x: x_folded, + r_x: r_x_prime, + v: v_folded, + } } pub fn fold_witness( @@ -183,12 +169,6 @@ where // compute the next power of rho rho_i *= rho; - // 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[..NOVA_N_BITS_RO], - )) - .unwrap(); } Witness { w: w_folded, @@ -213,8 +193,7 @@ where NIMFSProof, LCCCS, Witness, - // Vec, - Vec, + C::ScalarField, // rho ), Error, > { @@ -281,7 +260,7 @@ where C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // Step 7: Create the folded instance - let (folded_lcccs, rho_powers) = Self::fold( + let folded_lcccs = Self::fold( running_instances, new_instances, &sigmas_thetas, @@ -299,7 +278,7 @@ where }, folded_lcccs, folded_witness, - rho_powers, + rho, )) } @@ -406,8 +385,7 @@ where &proof.sigmas_thetas, r_x_prime, rho, - ) - .0) + )) } } @@ -457,7 +435,7 @@ pub mod tests { let mut rng = test_rng(); let rho = Fr::rand(&mut rng); - let (folded, _) = NIMFS::>::fold( + let folded = NIMFS::>::fold( &[lcccs], &[cccs], &sigmas_thetas, diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 961a534..f0c07b1 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -608,13 +608,13 @@ where let cfW_circuit = NovaCycleFoldCircuit:: { _gc: PhantomData, - r_bits: Some(vec![r_bits.clone()]), + r_bits: Some(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 = NovaCycleFoldCircuit:: { _gc: PhantomData, - r_bits: Some(vec![r_bits.clone()]), + r_bits: Some(r_bits.clone()), points: Some(vec![self.U_i.clone().cmE, cmT]), x: Some(cfE_u_i_x.clone()), };