mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-07 14:31:31 +01:00
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>
This commit is contained in:
@@ -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<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> {
|
||||
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
|
||||
pub points: Option<Vec<CFG::C>>,
|
||||
/// 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<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(), || {
|
||||
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::<FpVar<CFG::F>>::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<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()
|
||||
.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)?;
|
||||
|
||||
Ok(())
|
||||
@@ -596,13 +584,13 @@ pub mod tests {
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::utils::get_cm_coordinates;
|
||||
|
||||
struct TestCycleFoldConfig<C: CurveGroup> {
|
||||
struct TestCycleFoldConfig<C: CurveGroup, const N: usize> {
|
||||
_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 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<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
|
||||
// (E1::Fq=E2::Fr)
|
||||
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),
|
||||
let x: Vec<Fq> = [
|
||||
vec![rho_Fq],
|
||||
points.iter().flat_map(get_cm_coordinates).collect(),
|
||||
get_cm_coordinates(&res),
|
||||
]
|
||||
.concat();
|
||||
let cfW_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> {
|
||||
let cf_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective, n>, 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()),
|
||||
r_bits: Some(rho_bits),
|
||||
points: Some(points),
|
||||
x: Some(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),
|
||||
]
|
||||
.concat();
|
||||
let cfE_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> {
|
||||
_gc: PhantomData,
|
||||
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();
|
||||
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::<Projective>::IO_LEN)
|
||||
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
|
||||
.collect(),
|
||||
};
|
||||
let U_i = CycleFoldCommittedInstance::<Projective> {
|
||||
@@ -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::<Projective>::IO_LEN)
|
||||
.take(TestCycleFoldConfig::<Projective, 2>::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::<Projective>::IO_LEN)
|
||||
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
|
||||
.collect(),
|
||||
};
|
||||
let pp_hash = Fq::from(42u32); // only for test
|
||||
|
||||
@@ -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<C>], // u
|
||||
proof: ProofVar<C>,
|
||||
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
|
||||
for U_i in running_instances {
|
||||
let v = [
|
||||
@@ -317,15 +317,16 @@ where
|
||||
let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(NOVA_N_BITS_RO)?;
|
||||
let rho = Boolean::le_bits_to_fp_var(&rho_bits)?;
|
||||
|
||||
// Self::fold will return the folded instance, together with the rho's powers vector so
|
||||
// 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<FpVar<CF1<C>>>>, Vec<Vec<FpVar<CF1<C>>>>),
|
||||
r_x_prime: Vec<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 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; NOVA_N_BITS_RO]; lcccs.len() + cccs.len() - 1];
|
||||
let mut rho_i = FpVar::one();
|
||||
for i in 0..(lcccs.len() + cccs.len()) {
|
||||
let u: FpVar<CF1<C>>;
|
||||
@@ -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> {
|
||||
// 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]))
|
||||
})?;
|
||||
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(), || {
|
||||
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::<C1>::verify(
|
||||
let (mut U_i1, rho_bits) = NIMFSGadget::<C1>::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<NonNativeUintVar<CF2<C2>>> = [
|
||||
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<Fr> =
|
||||
PoseidonSponge::<Fr>::new(&poseidon_config.clone());
|
||||
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
|
||||
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<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:
|
||||
// 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::<Projective, GVar, MU, NU> {
|
||||
_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::<Projective, MU, NU>::N_INPUT_POINTS - 1
|
||||
);
|
||||
assert_eq!(
|
||||
cf_circuit.points.clone().unwrap().len(),
|
||||
HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS
|
||||
|
||||
@@ -168,9 +168,8 @@ where
|
||||
pub U_i1: Option<LCCCS<C1>>,
|
||||
pub W_i1: Option<Witness<C1::ScalarField>>,
|
||||
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
|
||||
pub cf_U_i: Option<CycleFoldCommittedInstance<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
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&hn.poseidon_config);
|
||||
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
|
||||
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::<C1>::verify(
|
||||
let (computed_U_i1, rho_bits) = NIMFSGadget::<C1>::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::<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(())
|
||||
}
|
||||
|
||||
@@ -664,16 +664,15 @@ where
|
||||
let mut transcript_p: PoseidonSponge<C1::ScalarField> =
|
||||
PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
|
||||
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
|
||||
#[cfg(test)]
|
||||
@@ -687,29 +686,18 @@ where
|
||||
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:
|
||||
// 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::<C1, GC1, MU, NU> {
|
||||
_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();
|
||||
|
||||
@@ -73,7 +73,7 @@ where
|
||||
sigmas_thetas: &SigmasThetas<C::ScalarField>,
|
||||
r_x_prime: Vec<C::ScalarField>,
|
||||
rho: C::ScalarField,
|
||||
) -> (LCCCS<C>, Vec<C::ScalarField>) {
|
||||
) -> LCCCS<C> {
|
||||
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<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;
|
||||
@@ -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: 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(
|
||||
@@ -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<C>,
|
||||
LCCCS<C>,
|
||||
Witness<C::ScalarField>,
|
||||
// Vec<bool>,
|
||||
Vec<C::ScalarField>,
|
||||
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::<Projective, PoseidonSponge<Fr>>::fold(
|
||||
let folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold(
|
||||
&[lcccs],
|
||||
&[cccs],
|
||||
&sigmas_thetas,
|
||||
|
||||
@@ -608,13 +608,13 @@ where
|
||||
|
||||
let cfW_circuit = NovaCycleFoldCircuit::<C1, GC1> {
|
||||
_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::<C1, GC1> {
|
||||
_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()),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user