Browse Source

Optimize CycleFold circuit MSM approach (#143)

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 <youssef.housni21@gmail.com>
update-nifs-interface
arnaucube 2 months ago
committed by GitHub
parent
commit
7097c001fc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
6 changed files with 152 additions and 247 deletions
  1. +70
    -83
      folding-schemes/src/folding/circuits/cyclefold.rs
  2. +36
    -67
      folding-schemes/src/folding/hypernova/circuits.rs
  3. +17
    -33
      folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
  4. +14
    -27
      folding-schemes/src/folding/hypernova/mod.rs
  5. +13
    -35
      folding-schemes/src/folding/hypernova/nimfs.rs
  6. +2
    -2
      folding-schemes/src/folding/nova/mod.rs

+ 70
- 83
folding-schemes/src/folding/circuits/cyclefold.rs

@ -376,14 +376,12 @@ pub trait CycleFoldConfig {
/// Public inputs length for the CycleFoldCircuit. /// Public inputs length for the CycleFoldCircuit.
/// * For Nova this is: `|[r, p1.x,y, p2.x,y, p3.x,y]|` /// * 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: /// 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 = { 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; type F: Field;
@ -396,10 +394,9 @@ pub trait CycleFoldConfig {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CycleFoldCircuit<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> { pub struct CycleFoldCircuit<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> {
pub _gc: PhantomData<GC>, pub _gc: PhantomData<GC>,
/// 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>>>,
/// 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<Vec<bool>>,
/// points to be folded in the CycleFoldCircuit /// points to be folded in the CycleFoldCircuit
pub points: Option<Vec<CFG::C>>, pub points: Option<Vec<CFG::C>>,
/// public inputs (cf_u_{i+1}.x) /// public inputs (cf_u_{i+1}.x)
@ -426,18 +423,11 @@ where
for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>, for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>,
{ {
fn generate_constraints(self, cs: ConstraintSystemRef<CFG::F>) -> Result<(), SynthesisError> { fn generate_constraints(self, cs: ConstraintSystemRef<CFG::F>) -> Result<(), SynthesisError> {
let r_bits: Vec<Vec<Boolean<CFG::F>>> = self
.r_bits
// n_points-1, bcs is one for each point except for the first one
.unwrap_or(vec![
vec![false; CFG::RANDOMNESS_BIT_LENGTH];
CFG::N_INPUT_POINTS - 1
])
.iter()
.map(|r_bits_i| {
Vec::<Boolean<CFG::F>>::new_witness(cs.clone(), || Ok(r_bits_i.clone()))
})
.collect::<Result<_, _>>()?;
let r_bits = Vec::<Boolean<CFG::F>>::new_witness(cs.clone(), || {
Ok(self
.r_bits
.unwrap_or(vec![false; CFG::RANDOMNESS_BIT_LENGTH]))
})?;
let points = Vec::<GC>::new_witness(cs.clone(), || { let points = Vec::<GC>::new_witness(cs.clone(), || {
Ok(self Ok(self
.points .points
@ -447,10 +437,7 @@ where
#[cfg(test)] #[cfg(test)]
{ {
assert_eq!(CFG::N_INPUT_POINTS, points.len()); assert_eq!(CFG::N_INPUT_POINTS, points.len());
assert_eq!(CFG::N_INPUT_POINTS - 1, r_bits.len());
for r_bits_i in &r_bits {
assert_eq!(r_bits_i.len(), CFG::RANDOMNESS_BIT_LENGTH);
}
assert_eq!(CFG::RANDOMNESS_BIT_LENGTH, r_bits.len());
} }
// Fold the original points of the instances natively in CycleFold. // 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 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 // - 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 // 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::<FpVar<CFG::F>>::new_input(cs.clone(), || { let x = Vec::<FpVar<CFG::F>>::new_input(cs.clone(), || {
@ -474,24 +463,23 @@ where
// Check that the points coordinates are placed as the public input x: // 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 Nova, this is: x == [r, p1, p2, p3] (wheere p3 is the p_folded).
// In multifolding schemes such as HyperNova, this is: // 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() // where each p_i is in fact p_i.to_constraint_field()
let computed_x: Vec<FpVar<CFG::F>> = r_bits
let r_fp = Boolean::le_bits_to_fp_var(&r_bits)?;
let points_aux: Vec<FpVar<CFG::F>> = points
.iter() .iter()
.map(|r_bits_i| {
r_bits_i
.chunks(CFG::FIELD_CAPACITY)
.map(Boolean::le_bits_to_fp_var)
.collect::<Result<Vec<_>, _>>()
})
.chain(
points
.iter()
.chain(&[p_folded])
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())),
)
.collect::<Result<Vec<_>, _>>()?
.concat();
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec()))
.collect::<Result<Vec<_>, SynthesisError>>()?
.into_iter()
.flatten()
.collect();
let computed_x: Vec<FpVar<CFG::F>> = [
vec![r_fp],
points_aux,
p_folded.to_constraint_field()?[..2].to_vec(),
]
.concat();
computed_x.enforce_equal(&x)?; computed_x.enforce_equal(&x)?;
Ok(()) Ok(())
@ -596,13 +584,13 @@ pub mod tests {
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
use crate::utils::get_cm_coordinates; use crate::utils::get_cm_coordinates;
struct TestCycleFoldConfig<C: CurveGroup> {
struct TestCycleFoldConfig<C: CurveGroup, const N: usize> {
_c: PhantomData<C>, _c: PhantomData<C>,
} }
impl<C: CurveGroup> CycleFoldConfig for TestCycleFoldConfig<C> {
impl<C: CurveGroup, const N: usize> CycleFoldConfig for TestCycleFoldConfig<C, N> {
const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO; 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 C = C;
type F = C::BaseField; type F = C::BaseField;
} }
@ -630,46 +618,45 @@ pub mod tests {
} }
#[test] #[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<Projective> = 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 // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
// (E1::Fq=E2::Fr) // (E1::Fq=E2::Fr)
let cs = ConstraintSystem::<Fq>::new_ref(); let cs = ConstraintSystem::<Fq>::new_ref();
let cfW_u_i_x: Vec<Fq> = [
vec![r_Fq],
get_cm_coordinates(&ci1.cmW),
get_cm_coordinates(&ci2.cmW),
get_cm_coordinates(&ci3.cmW),
]
.concat();
let cfW_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, 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::<Fq>::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<Fq> = [
vec![rho_Fq],
points.iter().flat_map(get_cm_coordinates).collect(),
get_cm_coordinates(&res),
] ]
.concat(); .concat();
let cfE_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> {
let cf_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective, n>, GVar> {
_gc: PhantomData, _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()); assert!(cs.is_satisfied().unwrap());
} }
@ -714,7 +701,7 @@ pub mod tests {
u: Fr::zero(), u: Fr::zero(),
cmW: Projective::rand(&mut rng), cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng)) x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(TestCycleFoldConfig::<Projective>::IO_LEN)
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
.collect(), .collect(),
}; };
let U_i = CycleFoldCommittedInstance::<Projective> { let U_i = CycleFoldCommittedInstance::<Projective> {
@ -722,7 +709,7 @@ pub mod tests {
u: Fr::rand(&mut rng), u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng), cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng)) x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(TestCycleFoldConfig::<Projective>::IO_LEN)
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
.collect(), .collect(),
}; };
let cmT = Projective::rand(&mut rng); let cmT = Projective::rand(&mut rng);
@ -781,7 +768,7 @@ pub mod tests {
u: Fr::rand(&mut rng), u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng), cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng)) x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(TestCycleFoldConfig::<Projective>::IO_LEN)
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
.collect(), .collect(),
}; };
let pp_hash = Fq::from(42u32); // only for test let pp_hash = Fq::from(42u32); // only for test

+ 36
- 67
folding-schemes/src/folding/hypernova/circuits.rs

@ -14,7 +14,7 @@ use ark_r1cs_std::{
fields::{fp::FpVar, FieldVar}, fields::{fp::FpVar, FieldVar},
groups::GroupOpsBounds, groups::GroupOpsBounds,
prelude::CurveVar, prelude::CurveVar,
R1CSVar, ToBitsGadget, ToConstraintFieldGadget,
R1CSVar, ToConstraintFieldGadget,
}; };
use ark_relations::r1cs::{ use ark_relations::r1cs::{
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError,
@ -234,7 +234,7 @@ where
new_instances: &[CCCSVar<C>], // u new_instances: &[CCCSVar<C>], // u
proof: ProofVar<C>, proof: ProofVar<C>,
enabled: Boolean<C::ScalarField>, enabled: Boolean<C::ScalarField>,
) -> Result<(LCCCSVar<C>, Vec<Vec<Boolean<CF1<C>>>>), SynthesisError> {
) -> Result<(LCCCSVar<C>, Vec<Boolean<CF1<C>>>), SynthesisError> {
// absorb instances to transcript // absorb instances to transcript
for U_i in running_instances { for U_i in running_instances {
let v = [ let v = [
@ -317,15 +317,16 @@ where
let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(NOVA_N_BITS_RO)?; let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(NOVA_N_BITS_RO)?;
let rho = Boolean::le_bits_to_fp_var(&rho_bits)?; 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, running_instances,
new_instances, new_instances,
proof.sigmas_thetas, proof.sigmas_thetas,
r_x_prime, r_x_prime,
rho, 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 /// Runs (in-circuit) the verifier side of the fold, computing the new folded LCCCS instance
@ -336,14 +337,12 @@ where
sigmas_thetas: (Vec<Vec<FpVar<CF1<C>>>>, Vec<Vec<FpVar<CF1<C>>>>), sigmas_thetas: (Vec<Vec<FpVar<CF1<C>>>>, Vec<Vec<FpVar<CF1<C>>>>),
r_x_prime: Vec<FpVar<CF1<C>>>, r_x_prime: Vec<FpVar<CF1<C>>>,
rho: FpVar<CF1<C>>, rho: FpVar<CF1<C>>,
) -> Result<(LCCCSVar<C>, Vec<Vec<Boolean<CF1<C>>>>), SynthesisError> {
) -> Result<LCCCSVar<C>, SynthesisError> {
let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone());
let mut u_folded: FpVar<CF1<C>> = FpVar::zero(); 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 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 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; NOVA_N_BITS_RO]; lcccs.len() + cccs.len() - 1];
let mut rho_i = FpVar::one(); let mut rho_i = FpVar::one();
for i in 0..(lcccs.len() + cccs.len()) { for i in 0..(lcccs.len() + cccs.len()) {
let u: FpVar<CF1<C>>; let u: FpVar<CF1<C>>;
@ -382,28 +381,18 @@ where
// compute the next power of rho // compute the next power of rho
rho_i *= rho.clone(); 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 // return the folded instance, together with the rho's powers vector so they can be used in
// other parts of the AugmentedFCircuit // 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,
))
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,
})
} }
} }
@ -734,7 +723,7 @@ where
Ok(self.Us.unwrap_or(vec![U_dummy.clone(); MU - 1])) Ok(self.Us.unwrap_or(vec![U_dummy.clone(); MU - 1]))
})?; })?;
let us = Vec::<CCCSVar<C1>>::new_witness(cs.clone(), || { let us = Vec::<CCCSVar<C1>>::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(), || { let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || {
Ok(self.U_i1_C.unwrap_or_else(C1::zero)) Ok(self.U_i1_C.unwrap_or_else(C1::zero))
@ -794,7 +783,7 @@ where
// other curve. // other curve.
let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config); let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config);
transcript.absorb(&pp_hash)?; transcript.absorb(&pp_hash)?;
let (mut U_i1, rho_vec) = NIMFSGadget::<C1>::verify(
let (mut U_i1, rho_bits) = NIMFSGadget::<C1>::verify(
cs.clone(), cs.clone(),
&self.ccs.clone(), &self.ccs.clone(),
&mut transcript, &mut transcript,
@ -824,19 +813,14 @@ where
x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
// convert rho_bits of the rho_vec to a `NonNativeFieldVar` // 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 // CycleFold part
// C.1. Compute cf1_u_i.x and cf2_u_i.x // C.1. Compute cf1_u_i.x and cf2_u_i.x
let cf_x: Vec<NonNativeUintVar<CF2<C2>>> = [ let cf_x: Vec<NonNativeUintVar<CF2<C2>>> = [
rho_vec_nonnat,
vec![rho_nonnat],
all_Us all_Us
.iter() .iter()
.flat_map(|U| vec![U.C.x.clone(), U.C.y.clone()]) .flat_map(|U| vec![U.C.x.clone(), U.C.y.clone()])
@ -1312,17 +1296,16 @@ mod tests {
let mut transcript_p: PoseidonSponge<Fr> = let mut transcript_p: PoseidonSponge<Fr> =
PoseidonSponge::<Fr>::new(&poseidon_config.clone()); PoseidonSponge::<Fr>::new(&poseidon_config.clone());
transcript_p.absorb(&pp_hash); transcript_p.absorb(&pp_hash);
let (rho_powers, nimfs_proof);
(nimfs_proof, U_i1, W_i1, rho_powers) =
NIMFS::<Projective, PoseidonSponge<Fr>>::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::<Projective, PoseidonSponge<Fr>>::prove(
&mut transcript_p,
&ccs,
&all_Us,
&all_us,
&all_Ws,
&all_ws,
)
.unwrap();
// sanity check: check the folded instance relation // sanity check: check the folded instance relation
U_i1.check_relation(&ccs, &W_i1).unwrap(); U_i1.check_relation(&ccs, &W_i1).unwrap();
@ -1330,23 +1313,13 @@ mod tests {
let u_i1_x = let u_i1_x =
U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone()); U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), z_0.clone(), z_i1.clone());
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()[..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: // CycleFold part:
// get the vector used as public inputs 'x' in the CycleFold circuit // get the vector used as public inputs 'x' in the CycleFold circuit
let cf_u_i_x = [ let cf_u_i_x = [
// all values for multiple instances
rho_powers_Fq,
vec![rho_Fq],
get_cm_coordinates(&U_i.C), get_cm_coordinates(&U_i.C),
Us.iter() Us.iter()
.flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) .flat_map(|Us_i| get_cm_coordinates(&Us_i.C))
@ -1361,7 +1334,7 @@ mod tests {
let cf_circuit = HyperNovaCycleFoldCircuit::<Projective, GVar, MU, NU> { let cf_circuit = HyperNovaCycleFoldCircuit::<Projective, GVar, MU, NU> {
_gc: PhantomData, _gc: PhantomData,
r_bits: Some(rho_powers_bits.clone()),
r_bits: Some(rho_bits.clone()),
points: Some( points: Some(
[ [
vec![U_i.clone().C], vec![U_i.clone().C],
@ -1375,10 +1348,6 @@ mod tests {
}; };
// ensure that the CycleFoldCircuit is well defined // ensure that the CycleFoldCircuit is well defined
assert_eq!(
cf_circuit.r_bits.clone().unwrap().len(),
HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS - 1
);
assert_eq!( assert_eq!(
cf_circuit.points.clone().unwrap().len(), cf_circuit.points.clone().unwrap().len(),
HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS

+ 17
- 33
folding-schemes/src/folding/hypernova/decider_eth_circuit.rs

@ -168,9 +168,8 @@ where
pub U_i1: Option<LCCCS<C1>>, pub U_i1: Option<LCCCS<C1>>,
pub W_i1: Option<Witness<C1::ScalarField>>, pub W_i1: Option<Witness<C1::ScalarField>>,
pub nimfs_proof: Option<NIMFSProof<C1>>, pub nimfs_proof: Option<NIMFSProof<C1>>,
// 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<C1::ScalarField>,
// rho is the 'random' value used for the fold of the last 2 instances
pub rho: Option<C1::ScalarField>,
/// CycleFold running instance /// CycleFold running instance
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
pub cf_W_i: Option<CycleFoldWitness<C2>>, pub cf_W_i: Option<CycleFoldWitness<C2>>,
@ -199,15 +198,14 @@ where
// compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&hn.poseidon_config); let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&hn.poseidon_config);
transcript.absorb(&hn.pp_hash); transcript.absorb(&hn.pp_hash);
let (nimfs_proof, U_i1, W_i1, rho_powers) =
NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::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::<C1, PoseidonSponge<C1::ScalarField>>::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 // compute the KZG challenges used as inputs in the circuit
let kzg_challenge = let kzg_challenge =
@ -222,11 +220,6 @@ where
let p_W = poly_from_vec(W.to_vec())?; let p_W = poly_from_vec(W.to_vec())?;
let eval_W = p_W.evaluate(&kzg_challenge); 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 { Ok(Self {
_c1: PhantomData, _c1: PhantomData,
_gc1: PhantomData, _gc1: PhantomData,
@ -251,7 +244,7 @@ where
U_i1: Some(U_i1), U_i1: Some(U_i1),
W_i1: Some(W_i1), W_i1: Some(W_i1),
nimfs_proof: Some(nimfs_proof), nimfs_proof: Some(nimfs_proof),
rho_0: Some(rho_powers[0]),
rho: Some(rho),
cf_U_i: Some(hn.cf_U_i), cf_U_i: Some(hn.cf_U_i),
cf_W_i: Some(hn.cf_W_i), cf_W_i: Some(hn.cf_W_i),
kzg_challenge: Some(kzg_challenge), 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 // 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 // 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 // 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 (6.b).
// Check 7 is temporary disabled due // 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 // 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 // instances C parameter, which would require non-native arithmetic, henceforth we perform
// that check outside the circuit. // that check outside the circuit.
let (computed_U_i1, rho_vec) = NIMFSGadget::<C1>::verify(
let (computed_U_i1, rho_bits) = NIMFSGadget::<C1>::verify(
cs.clone(), cs.clone(),
&self.ccs.clone(), &self.ccs.clone(),
&mut transcript, &mut transcript,
@ -471,20 +464,11 @@ where
computed_U_i1.v.enforce_equal(&U_i1.v)?; computed_U_i1.v.enforce_equal(&U_i1.v)?;
// 8.b check that the in-circuit computed r is equal to the inputted r. // 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::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.rho_0.unwrap_or(CF1::<C1>::zero()))
})?;
rho_0.enforce_equal(&external_rho_0)?;
let rho = Boolean::le_bits_to_fp_var(&rho_bits)?;
let external_rho =
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.rho.unwrap_or(CF1::<C1>::zero())))?;
rho.enforce_equal(&external_rho)?;
Ok(()) Ok(())
} }

+ 14
- 27
folding-schemes/src/folding/hypernova/mod.rs

@ -664,16 +664,15 @@ where
let mut transcript_p: PoseidonSponge<C1::ScalarField> = let mut transcript_p: PoseidonSponge<C1::ScalarField> =
PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
transcript_p.absorb(&self.pp_hash); transcript_p.absorb(&self.pp_hash);
let (rho_powers, nimfs_proof);
(nimfs_proof, U_i1, W_i1, rho_powers) =
NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::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::<C1, PoseidonSponge<C1::ScalarField>>::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 // sanity check: check the folded instance relation
#[cfg(test)] #[cfg(test)]
@ -687,29 +686,18 @@ where
z_i1.clone(), z_i1.clone(),
); );
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()[..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: // CycleFold part:
// get the vector used as public inputs 'x' in the CycleFold circuit. // 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: // Place the random values and the points coordinates as the public input x:
// In Nova, this is: x == [r, p1, p2, p3]. // In Nova, this is: x == [r, p1, p2, p3].
// In multifolding schemes such as HyperNova, this is: // 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() // where each p_i is in fact p_i.to_constraint_field()
let cf_u_i_x = [ let cf_u_i_x = [
rho_powers_Fq,
vec![rho_Fq],
get_cm_coordinates(&self.U_i.C), get_cm_coordinates(&self.U_i.C),
Us.iter() Us.iter()
.flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) .flat_map(|Us_i| get_cm_coordinates(&Us_i.C))
@ -724,7 +712,7 @@ where
let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU> { let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU> {
_gc: PhantomData, _gc: PhantomData,
r_bits: Some(rho_powers_bits.clone()),
r_bits: Some(rho_bits.clone()),
points: Some( points: Some(
[ [
vec![self.U_i.clone().C], vec![self.U_i.clone().C],
@ -991,7 +979,6 @@ mod tests {
cccs.push((u, w)); cccs.push((u, w));
} }
dbg!(&hypernova.i);
hypernova hypernova
.prove_step(&mut rng, vec![], Some((lcccs, cccs))) .prove_step(&mut rng, vec![], Some((lcccs, cccs)))
.unwrap(); .unwrap();

+ 13
- 35
folding-schemes/src/folding/hypernova/nimfs.rs

@ -73,7 +73,7 @@ where
sigmas_thetas: &SigmasThetas<C::ScalarField>, sigmas_thetas: &SigmasThetas<C::ScalarField>,
r_x_prime: Vec<C::ScalarField>, r_x_prime: Vec<C::ScalarField>,
rho: C::ScalarField, rho: C::ScalarField,
) -> (LCCCS<C>, Vec<C::ScalarField>) {
) -> LCCCS<C> {
let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone()); let (sigmas, thetas) = (sigmas_thetas.0.clone(), sigmas_thetas.1.clone());
let mut C_folded = C::zero(); let mut C_folded = C::zero();
let mut u_folded = C::ScalarField::zero(); let mut u_folded = C::ScalarField::zero();
@ -81,7 +81,6 @@ where
let mut v_folded: Vec<C::ScalarField> = vec![C::ScalarField::zero(); sigmas[0].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_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()) { for i in 0..(lcccs.len() + cccs.len()) {
let c: C; let c: C;
let u: C::ScalarField; let u: C::ScalarField;
@ -123,28 +122,15 @@ where
// compute the next power of rho // compute the next power of rho
rho_i *= 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: C_folded,
u: u_folded,
x: x_folded,
r_x: r_x_prime,
v: v_folded,
},
rho_powers,
)
LCCCS::<C> {
C: C_folded,
u: u_folded,
x: x_folded,
r_x: r_x_prime,
v: v_folded,
}
} }
pub fn fold_witness( pub fn fold_witness(
@ -183,12 +169,6 @@ where
// compute the next power of rho // compute the next power of rho
rho_i *= 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 { Witness {
w: w_folded, w: w_folded,
@ -213,8 +193,7 @@ where
NIMFSProof<C>, NIMFSProof<C>,
LCCCS<C>, LCCCS<C>,
Witness<C::ScalarField>, Witness<C::ScalarField>,
// Vec<bool>,
Vec<C::ScalarField>,
C::ScalarField, // rho
), ),
Error, Error,
> { > {
@ -281,7 +260,7 @@ where
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
// Step 7: Create the folded instance // Step 7: Create the folded instance
let (folded_lcccs, rho_powers) = Self::fold(
let folded_lcccs = Self::fold(
running_instances, running_instances,
new_instances, new_instances,
&sigmas_thetas, &sigmas_thetas,
@ -299,7 +278,7 @@ where
}, },
folded_lcccs, folded_lcccs,
folded_witness, folded_witness,
rho_powers,
rho,
)) ))
} }
@ -406,8 +385,7 @@ where
&proof.sigmas_thetas, &proof.sigmas_thetas,
r_x_prime, r_x_prime,
rho, rho,
)
.0)
))
} }
} }
@ -457,7 +435,7 @@ pub mod tests {
let mut rng = test_rng(); let mut rng = test_rng();
let rho = Fr::rand(&mut rng); let rho = Fr::rand(&mut rng);
let (folded, _) = NIMFS::<Projective, PoseidonSponge<Fr>>::fold(
let folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold(
&[lcccs], &[lcccs],
&[cccs], &[cccs],
&sigmas_thetas, &sigmas_thetas,

+ 2
- 2
folding-schemes/src/folding/nova/mod.rs

@ -608,13 +608,13 @@ where
let cfW_circuit = NovaCycleFoldCircuit::<C1, GC1> { let cfW_circuit = NovaCycleFoldCircuit::<C1, GC1> {
_gc: PhantomData, _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]), points: Some(vec![self.U_i.clone().cmW, self.u_i.clone().cmW]),
x: Some(cfW_u_i_x.clone()), x: Some(cfW_u_i_x.clone()),
}; };
let cfE_circuit = NovaCycleFoldCircuit::<C1, GC1> { let cfE_circuit = NovaCycleFoldCircuit::<C1, GC1> {
_gc: PhantomData, _gc: PhantomData,
r_bits: Some(vec![r_bits.clone()]),
r_bits: Some(r_bits.clone()),
points: Some(vec![self.U_i.clone().cmE, cmT]), points: Some(vec![self.U_i.clone().cmE, cmT]),
x: Some(cfE_u_i_x.clone()), x: Some(cfE_u_i_x.clone()),
}; };

Loading…
Cancel
Save