Browse Source

Change CycleFold circuit approach (#77)

* Change CycleFold approach:

Instead of having a single CycleFold circuit that checks the 2 forign
scalarmul of the main circuit instances, now there are 2 separated
CycleFold circuits each of them checking a single foreign scalarmul.
Increasing the number of constraints of the AugmentedFCircuit, but
reducing the number of constraints in the CycleFold circuit, which will
translate into reducing the number of constraints in the Decider
circuit.

* CycleFold circuits checks in AugmentedFCircuit:

- update NonNativeAffineVar to work with NonNativeFieldVar directly
  instead of FpVar comming from NonNativeFieldVar.to_constraint_field()
- include in AugmentedFCircuit intermediate steps inbetween CycleFold
  circuits, and update the internal checks of the CycleFold circuits

Pending to document the new CycleFold circuits approach and better
variable namings, rm unwraps, etc

* matrix_vec_mul_sparse gadget: skip value * v[col_i] mul when value==1

Saves a notable amount of constraints since there is a notable amount of
1 values in R1CS matrices.

* Reuse computed vector of U_i

Reuse computed vector of U_i, saving 4k constraints in AugmentedFCircuit.

* fixes post last rebase to main

* rm test_augmentedfcircuit since it is already tested in test_ivc (and is a slow computation)

* rm dbg!()

* small fixes after last main rebase
main
arnaucube 9 months ago
committed by GitHub
parent
commit
602a367411
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
10 changed files with 446 additions and 573 deletions
  1. +16
    -7
      folding-schemes/src/commitment/pedersen.rs
  2. +15
    -16
      folding-schemes/src/folding/circuits/nonnative.rs
  3. +159
    -338
      folding-schemes/src/folding/nova/circuits.rs
  4. +81
    -114
      folding-schemes/src/folding/nova/cyclefold.rs
  5. +1
    -3
      folding-schemes/src/folding/nova/decider_eth.rs
  6. +17
    -14
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  7. +148
    -78
      folding-schemes/src/folding/nova/mod.rs
  8. +2
    -2
      folding-schemes/src/folding/nova/nifs.rs
  9. +2
    -1
      folding-schemes/src/lib.rs
  10. +5
    -0
      folding-schemes/src/utils/gadgets.rs

+ 16
- 7
folding-schemes/src/commitment/pedersen.rs

@ -1,6 +1,6 @@
use ark_ec::CurveGroup; use ark_ec::CurveGroup;
use ark_ff::Field; use ark_ff::Field;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
use ark_r1cs_std::{boolean::Boolean, groups::GroupOpsBounds, prelude::CurveVar};
use ark_relations::r1cs::SynthesisError; use ark_relations::r1cs::SynthesisError;
use ark_std::Zero; use ark_std::Zero;
use ark_std::{ use ark_std::{
@ -148,7 +148,7 @@ where
_gc: PhantomData<GC>, _gc: PhantomData<GC>,
} }
use ark_r1cs_std::{fields::nonnative::NonNativeFieldVar, ToBitsGadget};
use ark_r1cs_std::ToBitsGadget;
impl<C, GC, const H: bool> PedersenGadget<C, GC, H> impl<C, GC, const H: bool> PedersenGadget<C, GC, H>
where where
C: CurveGroup, C: CurveGroup,
@ -160,12 +160,12 @@ where
pub fn commit( pub fn commit(
h: GC, h: GC,
g: Vec<GC>, g: Vec<GC>,
v: Vec<NonNativeFieldVar<C::ScalarField, CF<C>>>,
r: NonNativeFieldVar<C::ScalarField, CF<C>>,
v: Vec<Vec<Boolean<CF<C>>>>,
r: Vec<Boolean<CF<C>>>,
) -> Result<GC, SynthesisError> { ) -> Result<GC, SynthesisError> {
let mut res = GC::zero(); let mut res = GC::zero();
if H { if H {
res += h.scalar_mul_le(r.to_bits_le()?.iter())?;
res += h.scalar_mul_le(r.iter())?;
} }
for (i, v_i) in v.iter().enumerate() { for (i, v_i) in v.iter().enumerate() {
res += g[i].scalar_mul_le(v_i.to_bits_le()?.iter())?; res += g[i].scalar_mul_le(v_i.to_bits_le()?.iter())?;
@ -176,6 +176,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget}; use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
@ -226,12 +227,20 @@ mod tests {
let r: Fr = Fr::rand(&mut rng); let r: Fr = Fr::rand(&mut rng);
let cm = Pedersen::<Projective>::commit(&params, &v, &r).unwrap(); let cm = Pedersen::<Projective>::commit(&params, &v, &r).unwrap();
let v_bits: Vec<Vec<bool>> = v.iter().map(|val| val.into_bigint().to_bits_le()).collect();
let r_bits: Vec<bool> = r.into_bigint().to_bits_le();
// circuit // circuit
let cs = ConstraintSystem::<Fq>::new_ref(); let cs = ConstraintSystem::<Fq>::new_ref();
// prepare inputs // prepare inputs
let vVar = Vec::<NonNativeFieldVar<Fr, Fq>>::new_witness(cs.clone(), || Ok(v)).unwrap();
let rVar = NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r)).unwrap();
let vVar: Vec<Vec<Boolean<Fq>>> = v_bits
.iter()
.map(|val_bits| {
Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(val_bits.clone())).unwrap()
})
.collect();
let rVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
let gVar = Vec::<GVar>::new_witness(cs.clone(), || Ok(params.generators)).unwrap(); let gVar = Vec::<GVar>::new_witness(cs.clone(), || Ok(params.generators)).unwrap();
let hVar = GVar::new_witness(cs.clone(), || Ok(params.h)).unwrap(); let hVar = GVar::new_witness(cs.clone(), || Ok(params.h)).unwrap();
let expected_cmVar = GVar::new_witness(cs.clone(), || Ok(cm)).unwrap(); let expected_cmVar = GVar::new_witness(cs.clone(), || Ok(cm)).unwrap();

+ 15
- 16
folding-schemes/src/folding/circuits/nonnative.rs

@ -1,10 +1,8 @@
use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar}; use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar};
use ark_r1cs_std::{ use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode}, alloc::{AllocVar, AllocationMode},
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
ToConstraintFieldGadget,
fields::nonnative::NonNativeFieldVar,
}; };
use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_std::{One, Zero}; use ark_std::{One, Zero};
@ -14,12 +12,15 @@ use core::borrow::Borrow;
/// field, over the constraint field. It is not intended to perform operations, but just to contain /// field, over the constraint field. It is not intended to perform operations, but just to contain
/// the affine coordinates in order to perform hash operations of the point. /// the affine coordinates in order to perform hash operations of the point.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NonNativeAffineVar<F: PrimeField> {
pub x: Vec<FpVar<F>>,
pub y: Vec<FpVar<F>>,
pub struct NonNativeAffineVar<C: CurveGroup>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
pub x: NonNativeFieldVar<C::BaseField, C::ScalarField>,
pub y: NonNativeFieldVar<C::BaseField, C::ScalarField>,
} }
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C::ScalarField>
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C>
where where
C: CurveGroup, C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
@ -40,14 +41,12 @@ where
cs.clone(), cs.clone(),
|| Ok(xy.0), || Ok(xy.0),
mode, mode,
)?
.to_constraint_field()?;
)?;
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable( let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
cs.clone(), cs.clone(),
|| Ok(xy.1), || Ok(xy.1),
mode, mode,
)?
.to_constraint_field()?;
)?;
Ok(Self { x, y }) Ok(Self { x, y })
}) })
@ -108,7 +107,7 @@ where
mod tests { mod tests {
use super::*; use super::*;
use ark_pallas::{Fr, Projective}; use ark_pallas::{Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_r1cs_std::{alloc::AllocVar, R1CSVar, ToConstraintFieldGadget};
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::{UniformRand, Zero}; use ark_std::{UniformRand, Zero};
@ -118,14 +117,14 @@ mod tests {
// dealing with the 'zero' point should not panic when doing the unwrap // dealing with the 'zero' point should not panic when doing the unwrap
let p = Projective::zero(); let p = Projective::zero();
NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
// check that point_to_nonnative_limbs returns the expected values // check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng); let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = point_to_nonnative_limbs(p).unwrap(); let (x, y) = point_to_nonnative_limbs(p).unwrap();
assert_eq!(pVar.x.value().unwrap(), x);
assert_eq!(pVar.y.value().unwrap(), y);
assert_eq!(pVar.x.to_constraint_field().unwrap().value().unwrap(), x);
assert_eq!(pVar.y.to_constraint_field().unwrap().value().unwrap(), y);
} }
} }

+ 159
- 338
folding-schemes/src/folding/nova/circuits.rs

@ -46,11 +46,14 @@ pub type CF2 = <::BaseField as Field>::BasePrimeField;
/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are /// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are
/// represented non-natively over the constraint field. /// represented non-natively over the constraint field.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CommittedInstanceVar<C: CurveGroup> {
pub struct CommittedInstanceVar<C: CurveGroup>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
pub u: FpVar<C::ScalarField>, pub u: FpVar<C::ScalarField>,
pub x: Vec<FpVar<C::ScalarField>>, pub x: Vec<FpVar<C::ScalarField>>,
pub cmE: NonNativeAffineVar<C::ScalarField>,
pub cmW: NonNativeAffineVar<C::ScalarField>,
pub cmE: NonNativeAffineVar<C>,
pub cmW: NonNativeAffineVar<C>,
} }
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C> impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
@ -70,16 +73,10 @@ where
let x: Vec<FpVar<C::ScalarField>> = let x: Vec<FpVar<C::ScalarField>> =
Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
let cmE = NonNativeAffineVar::<C::ScalarField>::new_variable(
cs.clone(),
|| Ok(val.borrow().cmE),
mode,
)?;
let cmW = NonNativeAffineVar::<C::ScalarField>::new_variable(
cs.clone(),
|| Ok(val.borrow().cmW),
mode,
)?;
let cmE =
NonNativeAffineVar::<C>::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
let cmW =
NonNativeAffineVar::<C>::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
Ok(Self { u, x, cmE, cmW }) Ok(Self { u, x, cmE, cmW })
}) })
@ -90,31 +87,36 @@ impl CommittedInstanceVar
where where
C: CurveGroup, C: CurveGroup,
<C as Group>::ScalarField: Absorb, <C as Group>::ScalarField: Absorb,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{ {
/// hash implements the committed instance hash compatible with the native implementation from /// hash implements the committed instance hash compatible with the native implementation from
/// CommittedInstance.hash. /// CommittedInstance.hash.
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the
/// `CommittedInstance`. /// `CommittedInstance`.
/// Additionally it returns the vector of the field elements from the self parameters, so they
/// can be reused in other gadgets avoiding recalculating (reconstraining) them.
#[allow(clippy::type_complexity)]
pub fn hash( pub fn hash(
self, self,
crh_params: &CRHParametersVar<CF1<C>>, crh_params: &CRHParametersVar<CF1<C>>,
i: FpVar<CF1<C>>, i: FpVar<CF1<C>>,
z_0: Vec<FpVar<CF1<C>>>, z_0: Vec<FpVar<CF1<C>>>,
z_i: Vec<FpVar<CF1<C>>>, z_i: Vec<FpVar<CF1<C>>>,
) -> Result<FpVar<CF1<C>>, SynthesisError> {
let input = vec![
vec![i],
z_0,
z_i,
) -> Result<(FpVar<CF1<C>>, Vec<FpVar<CF1<C>>>), SynthesisError> {
let U_vec = [
vec![self.u], vec![self.u],
self.x, self.x,
self.cmE.x,
self.cmE.y,
self.cmW.x,
self.cmW.y,
self.cmE.x.to_constraint_field()?,
self.cmE.y.to_constraint_field()?,
self.cmW.x.to_constraint_field()?,
self.cmW.y.to_constraint_field()?,
] ]
.concat(); .concat();
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
let input = [vec![i], z_0, z_i, U_vec.clone()].concat();
Ok((
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)?,
U_vec,
))
} }
} }
@ -128,14 +130,15 @@ pub struct NIFSGadget {
impl<C: CurveGroup> NIFSGadget<C> impl<C: CurveGroup> NIFSGadget<C>
where where
C: CurveGroup, C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{ {
/// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to
/// the CycleFold circuit. /// the CycleFold circuit.
pub fn verify( pub fn verify(
r: FpVar<CF1<C>>, r: FpVar<CF1<C>>,
ci1: CommittedInstanceVar<C>,
ci2: CommittedInstanceVar<C>,
ci3: CommittedInstanceVar<C>,
ci1: CommittedInstanceVar<C>, // U_i
ci2: CommittedInstanceVar<C>, // u_i
ci3: CommittedInstanceVar<C>, // U_{i+1}
) -> Result<Boolean<CF1<C>>, SynthesisError> { ) -> Result<Boolean<CF1<C>>, SynthesisError> {
// ensure that: ci3.u == ci1.u + r * ci2.u // ensure that: ci3.u == ci1.u + r * ci2.u
let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?; let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?;
@ -166,30 +169,30 @@ where
{ {
pub fn get_challenge_native( pub fn get_challenge_native(
poseidon_config: &PoseidonConfig<C::ScalarField>, poseidon_config: &PoseidonConfig<C::ScalarField>,
u_i: CommittedInstance<C>,
U_i: CommittedInstance<C>, U_i: CommittedInstance<C>,
u_i: CommittedInstance<C>,
cmT: C, cmT: C,
) -> Result<Vec<bool>, SynthesisError> { ) -> Result<Vec<bool>, SynthesisError> {
let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::<C>(u_i.cmE)?;
let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::<C>(u_i.cmW)?;
let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::<C>(U_i.cmE)?; let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::<C>(U_i.cmE)?;
let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::<C>(U_i.cmW)?; let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::<C>(U_i.cmW)?;
let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::<C>(u_i.cmE)?;
let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::<C>(u_i.cmW)?;
let (cmT_x, cmT_y) = point_to_nonnative_limbs::<C>(cmT)?; let (cmT_x, cmT_y) = point_to_nonnative_limbs::<C>(cmT)?;
let mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config); let mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
let input = vec![ let input = vec![
vec![u_i.u],
u_i.x.clone(),
u_cmE_x,
u_cmE_y,
u_cmW_x,
u_cmW_y,
vec![U_i.u], vec![U_i.u],
U_i.x.clone(), U_i.x.clone(),
U_cmE_x, U_cmE_x,
U_cmE_y, U_cmE_y,
U_cmW_x, U_cmW_x,
U_cmW_y, U_cmW_y,
vec![u_i.u],
u_i.x.clone(),
u_cmE_x,
u_cmE_y,
u_cmW_x,
u_cmW_y,
cmT_x, cmT_x,
cmT_y, cmT_y,
] ]
@ -203,27 +206,22 @@ where
pub fn get_challenge_gadget( pub fn get_challenge_gadget(
cs: ConstraintSystemRef<C::ScalarField>, cs: ConstraintSystemRef<C::ScalarField>,
poseidon_config: &PoseidonConfig<C::ScalarField>, poseidon_config: &PoseidonConfig<C::ScalarField>,
U_i_vec: Vec<FpVar<CF1<C>>>, // apready processed input, so we don't have to recompute these values
u_i: CommittedInstanceVar<C>, u_i: CommittedInstanceVar<C>,
U_i: CommittedInstanceVar<C>,
cmT: NonNativeAffineVar<C::ScalarField>,
cmT: NonNativeAffineVar<C>,
) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> { ) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> {
let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config); let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config);
let input: Vec<FpVar<C::ScalarField>> = vec![ let input: Vec<FpVar<C::ScalarField>> = vec![
U_i_vec,
vec![u_i.u.clone()], vec![u_i.u.clone()],
u_i.x.clone(), u_i.x.clone(),
u_i.cmE.x,
u_i.cmE.y,
u_i.cmW.x,
u_i.cmW.y,
vec![U_i.u.clone()],
U_i.x.clone(),
U_i.cmE.x,
U_i.cmE.y,
U_i.cmW.x,
U_i.cmW.y,
cmT.x,
cmT.y,
u_i.cmE.x.to_constraint_field()?,
u_i.cmE.y.to_constraint_field()?,
u_i.cmW.x.to_constraint_field()?,
u_i.cmW.y.to_constraint_field()?,
cmT.x.to_constraint_field()?,
cmT.y.to_constraint_field()?,
] ]
.concat(); .concat();
sponge.absorb(&input)?; sponge.absorb(&input)?;
@ -257,11 +255,17 @@ pub struct AugmentedFCircuit<
pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x) pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x)
// cyclefold verifier on C1 // cyclefold verifier on C1
pub cf_u_i: Option<CommittedInstance<C2>>,
pub cf_U_i: Option<CommittedInstance<C2>>,
pub cf_U_i1: Option<CommittedInstance<C2>>,
pub cf_cmT: Option<C2>,
pub cf_r_nonnat: Option<C2::ScalarField>,
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
// cmE respectively
pub cf1_u_i: Option<CommittedInstance<C2>>, // input
pub cf2_u_i: Option<CommittedInstance<C2>>, // input
pub cf_U_i: Option<CommittedInstance<C2>>, // input
pub cf1_U_i1: Option<CommittedInstance<C2>>, // intermediate
pub cf_U_i1: Option<CommittedInstance<C2>>, // output
pub cf1_cmT: Option<C2>,
pub cf2_cmT: Option<C2>,
pub cf1_r_nonnat: Option<C2::ScalarField>,
pub cf2_r_nonnat: Option<C2::ScalarField>,
} }
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>> impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
@ -283,11 +287,15 @@ where
F: F_circuit, F: F_circuit,
x: None, x: None,
// cyclefold values // cyclefold values
cf_u_i: None,
cf1_u_i: None,
cf2_u_i: None,
cf_U_i: None, cf_U_i: None,
cf1_U_i1: None,
cf_U_i1: None, cf_U_i1: None,
cf_cmT: None,
cf_r_nonnat: None,
cf1_cmT: None,
cf2_cmT: None,
cf1_r_nonnat: None,
cf2_r_nonnat: None,
} }
} }
} }
@ -347,9 +355,9 @@ where
let is_not_basecase = i.is_neq(&zero)?; let is_not_basecase = i.is_neq(&zero)?;
// 1. u_i.x == H(i, z_0, z_i, U_i) // 1. u_i.x == H(i, z_0, z_i, U_i)
let u_i_x = U_i
.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
let (u_i_x, U_i_vec) =
U_i.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
// check that h == u_i.x // check that h == u_i.x
(u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?; (u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
@ -358,13 +366,11 @@ where
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant( let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(), cs.clone(),
C1::BaseField::zero(), C1::BaseField::zero(),
)?
.to_constraint_field()?;
)?;
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant( let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(), cs.clone(),
C1::BaseField::one(), C1::BaseField::one(),
)?
.to_constraint_field()?;
)?;
(u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
@ -374,19 +380,19 @@ where
let r_bits = ChallengeGadget::<C1>::get_challenge_gadget( let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
cs.clone(), cs.clone(),
&self.poseidon_config, &self.poseidon_config,
U_i_vec,
u_i.clone(), u_i.clone(),
U_i.clone(),
cmT.clone(), cmT.clone(),
)?; )?;
let r = Boolean::le_bits_to_fp_var(&r_bits)?; let r = Boolean::le_bits_to_fp_var(&r_bits)?;
// Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will // Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
// be done on the other curve. // be done on the other curve.
let nifs_check = NIFSGadget::<C1>::verify(r, u_i.clone(), U_i.clone(), U_i1.clone())?;
let nifs_check = NIFSGadget::<C1>::verify(r, U_i.clone(), u_i.clone(), U_i1.clone())?;
nifs_check.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; nifs_check.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
// 4. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F' // 4. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F'
let u_i1_x = U_i1.clone().hash(
let (u_i1_x, _) = U_i1.clone().hash(
&crh_params, &crh_params,
i + FpVar::<CF1<C1>>::one(), i + FpVar::<CF1<C1>>::one(),
z_0.clone(), z_0.clone(),
@ -397,65 +403,99 @@ where
// CycleFold part // CycleFold part
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN); let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN);
let cf_u_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_u_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
// cf W circuit data
let cf1_u_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf1_u_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;
let cf2_u_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf2_u_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?; })?;
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone())) Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?; })?;
let cf1_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf1_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;
let cf_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || { let cf_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone())) Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?; })?;
let cf_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?;
// cf_r_nonnat is an auxiliary input
let cf_r_nonnat =
NonNativeFieldVar::<C2::ScalarField, CF2<C2>>::new_witness(cs.clone(), || {
Ok(self.cf_r_nonnat.unwrap_or_else(C2::ScalarField::zero))
let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?;
let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?;
let cf1_r_nonnat =
NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
Ok(self.cf1_r_nonnat.unwrap_or_else(C2::ScalarField::zero))
})?;
let cf2_r_nonnat =
NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
Ok(self.cf2_r_nonnat.unwrap_or_else(C2::ScalarField::zero))
})?; })?;
// check that cf_u_i.x == [u_i, U_i, U_{i+1}]
let incircuit_x = vec![
u_i.cmE.x, u_i.cmE.y, u_i.cmW.x, u_i.cmW.y, U_i.cmE.x, U_i.cmE.y, U_i.cmW.x, U_i.cmW.y,
U_i1.cmE.x, U_i1.cmE.y, U_i1.cmW.x, U_i1.cmW.y,
]
.concat();
let cfW_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
U_i.cmW.x, U_i.cmW.y, u_i.cmW.x, u_i.cmW.y, U_i1.cmW.x, U_i1.cmW.y,
];
let cfE_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
U_i.cmE.x, U_i.cmE.y, u_i.cmE.x, u_i.cmE.y, U_i1.cmE.x, U_i1.cmE.y,
];
let mut cf_u_i_x: Vec<FpVar<CF2<C2>>> = vec![];
for x_i in cf_u_i.x.iter() {
let mut x_fpvar = x_i.to_constraint_field()?;
cf_u_i_x.append(&mut x_fpvar);
}
cf_u_i_x.conditional_enforce_equal(&incircuit_x, &is_not_basecase)?;
// ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i,
// u_i, U_i+1 coordinates of the commitments
cf1_u_i
.x
.conditional_enforce_equal(&cfW_x, &is_not_basecase)?;
cf2_u_i
.x
.conditional_enforce_equal(&cfE_x, &is_not_basecase)?;
// cf_r_bits is denoted by rho* in the paper
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
// cf_r_bits is denoted by rho* in the paper.
// assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary
// value used to compute RLC of NonNativeFieldVar values, since we can convert
// NonNativeFieldVar into Vec<Boolean>, but not in the other direction.
let cf1_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(), cs.clone(),
&self.poseidon_config, &self.poseidon_config,
cf_u_i.clone(),
cf_U_i.clone(), cf_U_i.clone(),
cf_cmT.clone(),
cf1_u_i.clone(),
cf1_cmT.clone(),
)?; )?;
// assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary
// value used to compute RLC of NonNativeFieldVar values, since we can convert
// NonNativeFieldVar into Vec<Boolean>, but not in the other direction.
let cf_r_nonnat_bits = cf_r_nonnat.to_bits_le()?;
cf_r_bits.conditional_enforce_equal(&cf_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?;
let cf1_r_nonnat_bits = cf1_r_nonnat.to_bits_le()?;
cf1_r_bits.conditional_enforce_equal(&cf1_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?;
// same for cf2_r:
let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
cf1_U_i1.clone(),
cf2_u_i.clone(),
cf2_cmT.clone(),
)?;
let cf2_r_nonnat_bits = cf2_r_nonnat.to_bits_le()?;
cf2_r_bits.conditional_enforce_equal(&cf2_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?;
// check cf_u_i.cmE=0, cf_u_i.u=1 // check cf_u_i.cmE=0, cf_u_i.u=1
(cf_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(cf_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(cf1_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(cf1_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(cf2_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(cf2_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
// check the fold of all the parameters of the CycleFold instances, where the elliptic // check the fold of all the parameters of the CycleFold instances, where the elliptic
// curve points relations are checked natively in Curve1 circuit (this one) // curve points relations are checked natively in Curve1 circuit (this one)
let v = NIFSFullGadget::<C2, GC2>::verify(
cf_r_bits,
cf_r_nonnat,
cf_cmT,
let v1 = NIFSFullGadget::<C2, GC2>::verify(
cf1_r_bits,
cf1_r_nonnat,
cf1_cmT,
cf_U_i, cf_U_i,
cf_u_i,
cf1_u_i,
cf1_U_i1.clone(),
)?;
v1.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
let v2 = NIFSFullGadget::<C2, GC2>::verify(
cf2_r_bits,
cf2_r_nonnat,
cf2_cmT,
cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u)
cf2_u_i,
cf_U_i1, cf_U_i1,
)?; )?;
v.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
v2.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
Ok(()) Ok(())
} }
@ -465,21 +505,14 @@ where
pub mod tests { pub mod tests {
use super::*; use super::*;
use ark_ff::BigInteger; use ark_ff::BigInteger;
use ark_pallas::{Fq, Fr, Projective};
use ark_pallas::{Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::{ConstraintLayer, ConstraintSystem, TracingMode};
use ark_std::One;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand; use ark_std::UniformRand;
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use tracing_subscriber::layer::SubscriberExt;
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
use crate::folding::nova::{
get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness,
};
use crate::frontend::tests::CubicFCircuit;
use crate::folding::nova::nifs::NIFS;
use crate::transcript::poseidon::poseidon_test_config; use crate::transcript::poseidon::poseidon_test_config;
#[test] #[test]
@ -565,7 +598,7 @@ pub mod tests {
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap(); let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
// compute the CommittedInstance hash in-circuit // compute the CommittedInstance hash in-circuit
let hVar = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap();
let (hVar, _) = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap();
assert!(cs.is_satisfied().unwrap()); assert!(cs.is_satisfied().unwrap());
// check that the natively computed and in-circuit computed hashes match // check that the natively computed and in-circuit computed hashes match
@ -595,8 +628,8 @@ pub mod tests {
// compute the challenge natively // compute the challenge natively
let r_bits = ChallengeGadget::<Projective>::get_challenge_native( let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
&poseidon_config, &poseidon_config,
u_i.clone(),
U_i.clone(), U_i.clone(),
u_i.clone(),
cmT, cmT,
) )
.unwrap(); .unwrap();
@ -609,14 +642,23 @@ pub mod tests {
let U_iVar = let U_iVar =
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone())) CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone()))
.unwrap(); .unwrap();
let cmTVar = NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let cmTVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(cmT)).unwrap();
// compute the challenge in-circuit // compute the challenge in-circuit
let U_iVar_vec = [
vec![U_iVar.u.clone()],
U_iVar.x.clone(),
U_iVar.cmE.x.to_constraint_field().unwrap(),
U_iVar.cmE.y.to_constraint_field().unwrap(),
U_iVar.cmW.x.to_constraint_field().unwrap(),
U_iVar.cmW.y.to_constraint_field().unwrap(),
]
.concat();
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget( let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
cs.clone(), cs.clone(),
&poseidon_config, &poseidon_config,
U_iVar_vec,
u_iVar, u_iVar,
U_iVar,
cmTVar, cmTVar,
) )
.unwrap(); .unwrap();
@ -627,225 +669,4 @@ pub mod tests {
assert_eq!(rVar.value().unwrap(), r); assert_eq!(rVar.value().unwrap(), r);
assert_eq!(r_bitsVar.value().unwrap(), r_bits); assert_eq!(r_bitsVar.value().unwrap(), r_bits);
} }
#[test]
/// test_augmented_f_circuit folds the CubicFCircuit circuit in multiple iterations, feeding the
/// values into the AugmentedFCircuit.
fn test_augmented_f_circuit() {
let mut layer = ConstraintLayer::default();
layer.mode = TracingMode::OnlyConstraints;
let subscriber = tracing_subscriber::Registry::default().with(layer);
let _guard = tracing::subscriber::set_default(subscriber);
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fr>();
// compute z vector for the initial instance
let cs = ConstraintSystem::<Fr>::new_ref();
// prepare the circuit to obtain its R1CS
let F_circuit = CubicFCircuit::<Fr>::new(());
let mut augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>>::empty(
&poseidon_config,
F_circuit,
);
augmented_F_circuit
.generate_constraints(cs.clone())
.unwrap();
cs.finalize();
let cs = cs.into_inner().unwrap();
let r1cs = extract_r1cs::<Fr>(&cs);
let (w, x) = extract_w_x::<Fr>(&cs);
assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols);
assert_eq!(r1cs.l, x.len());
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
// first step, set z_i=z_0=3 and z_{i+1}=35 (initial values)
let z_0 = vec![Fr::from(3_u32)];
let mut z_i = z_0.clone();
let mut z_i1 = vec![Fr::from(35_u32)];
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); w.len()], r1cs.A.n_rows);
let u_dummy = CommittedInstance::<Projective>::dummy(x.len());
// W_i is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that
// we're working with.
// set U_i <-- dummy instance
let mut W_i = w_dummy.clone();
let mut U_i = u_dummy.clone();
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
let mut w_i = w_dummy.clone();
let mut u_i = u_dummy.clone();
let (mut W_i1, mut U_i1, mut cmT): (
Witness<Projective>,
CommittedInstance<Projective>,
Projective,
) = (w_dummy.clone(), u_dummy.clone(), Projective::generator());
// as expected, dummy instances pass the relaxed_r1cs check
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
let mut i = Fr::zero();
let mut u_i1_x: Fr;
for _ in 0..4 {
if i == Fr::zero() {
// base case: i=0, z_i=z_0, U_i = U_d := dummy instance
// u_1.x = H(1, z_0, z_i, U_i)
u_i1_x = U_i
.hash(&poseidon_config, Fr::one(), z_0.clone(), z_i1.clone())
.unwrap();
// base case
augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
i: Some(i), // = 0
z_0: Some(z_0.clone()), // = z_i=3
z_i: Some(z_i.clone()),
u_i: Some(u_i.clone()), // = dummy
U_i: Some(U_i.clone()), // = dummy
U_i1: Some(U_i1.clone()), // = dummy
cmT: Some(cmT),
F: F_circuit,
x: Some(u_i1_x),
// cyclefold instances (not tested in this test)
cf_u_i: None,
cf_U_i: None,
cf_U_i1: None,
cf_cmT: None,
cf_r_nonnat: None,
};
} else {
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
// U_{i+1}
let T: Vec<Fr>;
(T, cmT) = NIFS::<Projective, Pedersen<Projective>>::compute_cmT(
&pedersen_params,
&r1cs,
&w_i,
&u_i,
&W_i,
&U_i,
)
.unwrap();
// get challenge r
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
&poseidon_config,
u_i.clone(),
U_i.clone(),
cmT,
)
.unwrap();
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
(W_i1, U_i1) = NIFS::<Projective, Pedersen<Projective>>::fold_instances(
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
)
.unwrap();
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
// folded instance output (public input, x)
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
u_i1_x = U_i1
.hash(&poseidon_config, i + Fr::one(), z_0.clone(), z_i1.clone())
.unwrap();
// set up dummy cyclefold instances just for the sake of this test. Warning, this
// is only because we are in a test were we're not testing the cyclefold side of
// things.
let cf_W_i = Witness::<Projective2>::new(vec![Fq::zero(); 1], 1);
let cf_U_i = CommittedInstance::<Projective2>::dummy(CF_IO_LEN);
let cf_u_i_x = [
get_committed_instance_coordinates(&u_i),
get_committed_instance_coordinates(&U_i),
get_committed_instance_coordinates(&U_i1),
]
.concat();
let cf_u_i = CommittedInstance::<Projective2> {
cmE: cf_U_i.cmE,
u: Fq::one(),
cmW: cf_U_i.cmW,
x: cf_u_i_x,
};
let cf_w_i = cf_W_i.clone();
let (cf_T, cf_cmT): (Vec<Fq>, Projective2) =
(vec![Fq::zero(); cf_W_i.E.len()], Projective2::zero());
let cf_r_bits =
CycleFoldChallengeGadget::<Projective2, GVar2>::get_challenge_native(
&poseidon_config,
cf_u_i.clone(),
cf_U_i.clone(),
cf_cmT,
)
.unwrap();
let cf_r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&cf_r_bits)).unwrap();
let (_, cf_U_i1) = NIFS::<Projective2, Pedersen<Projective2>>::fold_instances(
cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT,
)
.unwrap();
augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
i: Some(i),
z_0: Some(z_0.clone()),
z_i: Some(z_i.clone()),
u_i: Some(u_i),
U_i: Some(U_i.clone()),
U_i1: Some(U_i1.clone()),
cmT: Some(cmT),
F: F_circuit,
x: Some(u_i1_x),
cf_u_i: Some(cf_u_i),
cf_U_i: Some(cf_U_i),
cf_U_i1: Some(cf_U_i1),
cf_cmT: Some(cf_cmT),
cf_r_nonnat: Some(cf_r_Fq),
};
}
let cs = ConstraintSystem::<Fr>::new_ref();
augmented_F_circuit
.generate_constraints(cs.clone())
.unwrap();
let is_satisfied = cs.is_satisfied().unwrap();
if !is_satisfied {
dbg!(cs.which_is_unsatisfied().unwrap());
}
assert!(is_satisfied);
cs.finalize();
let cs = cs.into_inner().unwrap();
let (w_i1, x_i1) = extract_w_x::<Fr>(&cs);
assert_eq!(x_i1.len(), 1);
assert_eq!(x_i1[0], u_i1_x);
// compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we
// assign them directly to w_i, u_i.
w_i = Witness::<Projective>::new(w_i1.clone(), r1cs.A.n_rows);
u_i = w_i
.commit::<Pedersen<Projective>>(&pedersen_params, vec![u_i1_x])
.unwrap();
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
// set values for next iteration
i += Fr::one();
// advance the F circuit state
z_i = z_i1.clone();
z_i1 = F_circuit.step_native(z_i.clone()).unwrap();
U_i = U_i1.clone();
W_i = W_i1.clone();
}
}
} }

+ 81
- 114
folding-schemes/src/folding/nova/cyclefold.rs

@ -27,8 +27,8 @@ use super::CommittedInstance;
use crate::constants::N_BITS_RO; use crate::constants::N_BITS_RO;
use crate::Error; use crate::Error;
// publi inputs length for the CycleFoldCircuit, |[u_i, U_i, U_{i+1}]|
pub const CF_IO_LEN: usize = 12;
// publi inputs length for the CycleFoldCircuit, |[p1.x,y, p2.x,y, p3.x,y]|
pub const CF_IO_LEN: usize = 6;
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova /// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova
/// circuit. /// circuit.
@ -122,40 +122,6 @@ where
} }
} }
/// NIFSinCycleFoldGadget performs the Nova NIFS.V elliptic curve points relation checks in the other
/// curve (natively) following [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
pub struct NIFSinCycleFoldGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
_c: PhantomData<C>,
_gc: PhantomData<GC>,
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSinCycleFoldGadget<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
pub fn verify(
r_bits: Vec<Boolean<CF2<C>>>,
cmT: GC,
ci1: CommittedInstanceInCycleFoldVar<C, GC>,
ci2: CommittedInstanceInCycleFoldVar<C, GC>,
ci3: CommittedInstanceInCycleFoldVar<C, GC>,
) -> Result<Boolean<CF2<C>>, SynthesisError> {
// cm(E) check: ci3.cmE == ci1.cmE + r * cmT + r^2 * ci2.cmE
let first_check = ci3.cmE.is_eq(
&((ci2.cmE.scalar_mul_le(r_bits.iter())? + cmT).scalar_mul_le(r_bits.iter())?
+ ci1.cmE),
)?;
// cm(W) check: ci3.cmW == ci1.cmW + r * ci2.cmW
let second_check = ci3
.cmW
.is_eq(&(ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?))?;
first_check.and(&second_check)
}
}
/// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding, /// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding,
/// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks /// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks
/// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1. /// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1.
@ -171,6 +137,7 @@ where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{ {
pub fn verify( pub fn verify(
// assumes that r_bits is equal to r_nonnat just that in a different format
r_bits: Vec<Boolean<CF2<C>>>, r_bits: Vec<Boolean<CF2<C>>>,
r_nonnat: NonNativeFieldVar<C::ScalarField, CF2<C>>, r_nonnat: NonNativeFieldVar<C::ScalarField, CF2<C>>,
cmT: GC, cmT: GC,
@ -224,38 +191,38 @@ where
{ {
pub fn get_challenge_native( pub fn get_challenge_native(
poseidon_config: &PoseidonConfig<C::BaseField>, poseidon_config: &PoseidonConfig<C::BaseField>,
u_i: CommittedInstance<C>,
U_i: CommittedInstance<C>, U_i: CommittedInstance<C>,
u_i: CommittedInstance<C>,
cmT: C, cmT: C,
) -> Result<Vec<bool>, Error> { ) -> Result<Vec<bool>, Error> {
let mut sponge = PoseidonSponge::<C::BaseField>::new(poseidon_config); let mut sponge = PoseidonSponge::<C::BaseField>::new(poseidon_config);
let u_i_cmE_bytes = point_to_bytes(u_i.cmE);
let u_i_cmW_bytes = point_to_bytes(u_i.cmW);
let U_i_cmE_bytes = point_to_bytes(U_i.cmE);
let U_i_cmW_bytes = point_to_bytes(U_i.cmW);
let cmT_bytes = point_to_bytes(cmT);
let U_i_cmE_bytes = point_to_bytes(U_i.cmE)?;
let U_i_cmW_bytes = point_to_bytes(U_i.cmW)?;
let u_i_cmE_bytes = point_to_bytes(u_i.cmE)?;
let u_i_cmW_bytes = point_to_bytes(u_i.cmW)?;
let cmT_bytes = point_to_bytes(cmT)?;
let mut u_i_u_bytes = Vec::new();
u_i.u.serialize_uncompressed(&mut u_i_u_bytes)?;
let mut u_i_x_bytes = Vec::new();
u_i.x.serialize_uncompressed(&mut u_i_x_bytes)?;
u_i_x_bytes = u_i_x_bytes[8..].to_vec();
let mut U_i_u_bytes = Vec::new(); let mut U_i_u_bytes = Vec::new();
U_i.u.serialize_uncompressed(&mut U_i_u_bytes)?; U_i.u.serialize_uncompressed(&mut U_i_u_bytes)?;
let mut U_i_x_bytes = Vec::new(); let mut U_i_x_bytes = Vec::new();
U_i.x.serialize_uncompressed(&mut U_i_x_bytes)?; U_i.x.serialize_uncompressed(&mut U_i_x_bytes)?;
U_i_x_bytes = U_i_x_bytes[8..].to_vec(); U_i_x_bytes = U_i_x_bytes[8..].to_vec();
let mut u_i_u_bytes = Vec::new();
u_i.u.serialize_uncompressed(&mut u_i_u_bytes)?;
let mut u_i_x_bytes = Vec::new();
u_i.x.serialize_uncompressed(&mut u_i_x_bytes)?;
u_i_x_bytes = u_i_x_bytes[8..].to_vec();
let input: Vec<u8> = [ let input: Vec<u8> = [
u_i_cmE_bytes,
u_i_u_bytes,
u_i_cmW_bytes,
u_i_x_bytes,
U_i_cmE_bytes, U_i_cmE_bytes,
U_i_u_bytes, U_i_u_bytes,
U_i_cmW_bytes, U_i_cmW_bytes,
U_i_x_bytes, U_i_x_bytes,
u_i_cmE_bytes,
u_i_u_bytes,
u_i_cmW_bytes,
u_i_x_bytes,
cmT_bytes, cmT_bytes,
] ]
.concat(); .concat();
@ -267,32 +234,32 @@ where
pub fn get_challenge_gadget( pub fn get_challenge_gadget(
cs: ConstraintSystemRef<C::BaseField>, cs: ConstraintSystemRef<C::BaseField>,
poseidon_config: &PoseidonConfig<C::BaseField>, poseidon_config: &PoseidonConfig<C::BaseField>,
u_i: CycleFoldCommittedInstanceVar<C, GC>,
U_i: CycleFoldCommittedInstanceVar<C, GC>, U_i: CycleFoldCommittedInstanceVar<C, GC>,
u_i: CycleFoldCommittedInstanceVar<C, GC>,
cmT: GC, cmT: GC,
) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> { ) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> {
let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config); let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config);
let u_i_x_bytes: Vec<UInt8<CF2<C>>> = u_i
let U_i_x_bytes: Vec<UInt8<CF2<C>>> = U_i
.x .x
.iter() .iter()
.flat_map(|e| e.to_bytes().unwrap_or(vec![])) .flat_map(|e| e.to_bytes().unwrap_or(vec![]))
.collect::<Vec<UInt8<CF2<C>>>>(); .collect::<Vec<UInt8<CF2<C>>>>();
let U_i_x_bytes: Vec<UInt8<CF2<C>>> = U_i
let u_i_x_bytes: Vec<UInt8<CF2<C>>> = u_i
.x .x
.iter() .iter()
.flat_map(|e| e.to_bytes().unwrap_or(vec![])) .flat_map(|e| e.to_bytes().unwrap_or(vec![]))
.collect::<Vec<UInt8<CF2<C>>>>(); .collect::<Vec<UInt8<CF2<C>>>>();
let input: Vec<UInt8<CF2<C>>> = [ let input: Vec<UInt8<CF2<C>>> = [
u_i.cmE.to_bytes()?,
u_i.u.to_bytes()?,
u_i.cmW.to_bytes()?,
u_i_x_bytes,
U_i.cmE.to_bytes()?, U_i.cmE.to_bytes()?,
U_i.u.to_bytes()?, U_i.u.to_bytes()?,
U_i.cmW.to_bytes()?, U_i.cmW.to_bytes()?,
U_i_x_bytes, U_i_x_bytes,
u_i.cmE.to_bytes()?,
u_i.u.to_bytes()?,
u_i.cmW.to_bytes()?,
u_i_x_bytes,
cmT.to_bytes()?, cmT.to_bytes()?,
// TODO instead of bytes, use field elements, but needs x,y coordinates from // TODO instead of bytes, use field elements, but needs x,y coordinates from
// u_i.{cmE,cmW}, U_i.{cmE,cmW}, cmT. Depends exposing x,y coordinates of GC. Issue to // u_i.{cmE,cmW}, U_i.{cmE,cmW}, cmT. Depends exposing x,y coordinates of GC. Issue to
@ -307,16 +274,16 @@ where
} }
/// returns the bytes being compatible with the ark_r1cs_std `.to_bytes` approach /// returns the bytes being compatible with the ark_r1cs_std `.to_bytes` approach
fn point_to_bytes<C: CurveGroup>(p: C) -> Vec<u8> {
fn point_to_bytes<C: CurveGroup>(p: C) -> Result<Vec<u8>, Error> {
let l = p.uncompressed_size(); let l = p.uncompressed_size();
let mut b = Vec::new(); let mut b = Vec::new();
p.serialize_uncompressed(&mut b).unwrap();
p.serialize_uncompressed(&mut b)?;
b[l - 1] = 0; b[l - 1] = 0;
if p.is_zero() { if p.is_zero() {
b[l / 2] = 1; b[l / 2] = 1;
b[l - 1] = 1; b[l - 1] = 1;
} }
b
Ok(b)
} }
/// CycleFoldCircuit contains the constraints that check the correct fold of the committed /// CycleFoldCircuit contains the constraints that check the correct fold of the committed
@ -326,12 +293,9 @@ fn point_to_bytes(p: C) -> Vec {
pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> { pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
pub _gc: PhantomData<GC>, pub _gc: PhantomData<GC>,
pub r_bits: Option<Vec<bool>>, pub r_bits: Option<Vec<bool>>,
pub cmT: Option<C>,
// u_i,U_i,U_i1 are the nova instances from AugmentedFCircuit which will be (their elliptic
// curve points) checked natively in CycleFoldCircuit
pub u_i: Option<CommittedInstance<C>>,
pub U_i: Option<CommittedInstance<C>>,
pub U_i1: Option<CommittedInstance<C>>,
pub p1: Option<C>,
pub p2: Option<C>,
pub p3: Option<C>,
pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x) pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
} }
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> { impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
@ -339,10 +303,9 @@ impl>> CycleFoldCircuit {
Self { Self {
_gc: PhantomData, _gc: PhantomData,
r_bits: None, r_bits: None,
cmT: None,
u_i: None,
U_i: None,
U_i1: None,
p1: None,
p2: None,
p3: None,
x: None, x: None,
} }
} }
@ -358,29 +321,21 @@ where
let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || { let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || {
Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO])) Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO]))
})?; })?;
let cmT = GC::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or(C::zero())))?;
let u_dummy_native = CommittedInstance::<C>::dummy(1);
let p1 = GC::new_witness(cs.clone(), || Ok(self.p1.unwrap_or(C::zero())))?;
let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?;
let p3 = GC::new_witness(cs.clone(), || Ok(self.p3.unwrap_or(C::zero())))?;
let u_i = CommittedInstanceInCycleFoldVar::<C, GC>::new_witness(cs.clone(), || {
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
})?;
let U_i = CommittedInstanceInCycleFoldVar::<C, GC>::new_witness(cs.clone(), || {
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
})?;
let U_i1 = CommittedInstanceInCycleFoldVar::<C, GC>::new_witness(cs.clone(), || {
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
})?;
let _x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || { let _x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN])) Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
})?; })?;
#[cfg(test)] #[cfg(test)]
assert_eq!(_x.len(), CF_IO_LEN); // non-constrained sanity check assert_eq!(_x.len(), CF_IO_LEN); // non-constrained sanity check
// fold the original Nova instances natively in CycleFold
let v =
NIFSinCycleFoldGadget::<C, GC>::verify(r_bits.clone(), cmT, u_i.clone(), U_i, U_i1)?;
v.enforce_equal(&Boolean::TRUE)?;
// Fold the original Nova instances natively in CycleFold
// For the cmW we're checking: U_i1.cmW == U_i.cmW + r * u_i.cmW
// For the cmE we're checking: 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
p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?;
// check that x == [u_i, U_i, U_{i+1}], check that the cmW & cmW from u_i, U_i, U_{i+1} in // check that x == [u_i, U_i, U_{i+1}], check that the cmW & cmW from u_i, U_i, U_{i+1} in
// the CycleFoldCircuit are the sames used in the public inputs 'x', which come from the // the CycleFoldCircuit are the sames used in the public inputs 'x', which come from the
@ -401,6 +356,7 @@ pub mod tests {
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand; use ark_std::UniformRand;
use crate::folding::nova::get_cm_coordinates;
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
use crate::transcript::poseidon::poseidon_test_config; use crate::transcript::poseidon::poseidon_test_config;
@ -427,37 +383,48 @@ pub mod tests {
} }
#[test] #[test]
fn test_nifs_gadget_cyclefold() {
fn test_CycleFoldCircuit_constraints() {
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs(); let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
// 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
// (E2::Fr)
// (E1::Fq=E2::Fr)
let cs = ConstraintSystem::<Fq>::new_ref(); let cs = ConstraintSystem::<Fq>::new_ref();
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let ci1Var =
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci1.clone())
})
.unwrap();
let ci2Var =
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci2.clone())
})
.unwrap();
let ci3Var =
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci3.clone())
})
.unwrap();
let cfW_u_i_x = [
get_cm_coordinates(&ci1.cmW),
get_cm_coordinates(&ci2.cmW),
get_cm_coordinates(&ci3.cmW),
]
.concat();
let cfW_circuit = CycleFoldCircuit::<Projective, GVar> {
_gc: PhantomData,
r_bits: Some(r_bits.clone()),
p1: Some(ci1.clone().cmW),
p2: Some(ci2.clone().cmW),
p3: Some(ci3.clone().cmW),
x: Some(cfW_u_i_x.clone()),
};
cfW_circuit.generate_constraints(cs.clone()).unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!(cs.num_constraints());
let nifs_cf_check = NIFSinCycleFoldGadget::<Projective, GVar>::verify(
r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var,
)
.unwrap();
nifs_cf_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
// same for E:
let cs = ConstraintSystem::<Fq>::new_ref();
let cfE_u_i_x = [
get_cm_coordinates(&ci1.cmE),
get_cm_coordinates(&ci2.cmE),
get_cm_coordinates(&ci3.cmE),
]
.concat();
let cfE_circuit = CycleFoldCircuit::<Projective, GVar> {
_gc: PhantomData,
r_bits: Some(r_bits.clone()),
p1: Some(ci1.clone().cmE),
p2: Some(cmT),
p3: Some(ci3.clone().cmE),
x: Some(cfE_u_i_x.clone()),
};
cfE_circuit.generate_constraints(cs.clone()).unwrap();
assert!(cs.is_satisfied().unwrap()); assert!(cs.is_satisfied().unwrap());
} }
@ -527,8 +494,8 @@ pub mod tests {
// compute the challenge natively // compute the challenge natively
let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native( let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
&poseidon_config, &poseidon_config,
u_i.clone(),
U_i.clone(), U_i.clone(),
u_i.clone(),
cmT, cmT,
) )
.unwrap(); .unwrap();
@ -549,8 +516,8 @@ pub mod tests {
let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget( let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
cs.clone(), cs.clone(),
&poseidon_config, &poseidon_config,
u_iVar,
U_iVar, U_iVar,
u_iVar,
cmTVar, cmTVar,
) )
.unwrap(); .unwrap();

+ 1
- 3
folding-schemes/src/folding/nova/decider_eth.rs

@ -68,9 +68,7 @@ where
let circuit = let circuit =
DeciderEthCircuit::<C1, GC1, C2, GC2, CP1, CP2>::from_nova::<FC>(folding_scheme.into()); DeciderEthCircuit::<C1, GC1, C2, GC2, CP1, CP2>::from_nova::<FC>(folding_scheme.into());
let proof = S::prove(pp, circuit.clone(), &mut rng).unwrap();
Ok(proof)
S::prove(pp, circuit.clone(), &mut rng).map_err(|e| Error::Other(e.to_string()))
} }
fn verify( fn verify(

+ 17
- 14
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -11,7 +11,6 @@ use ark_r1cs_std::{
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
groups::GroupOpsBounds, groups::GroupOpsBounds,
prelude::CurveVar, prelude::CurveVar,
ToConstraintFieldGadget,
}; };
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{One, Zero}; use ark_std::{One, Zero};
@ -344,19 +343,17 @@ where
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant( let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(), cs.clone(),
C1::BaseField::zero(), C1::BaseField::zero(),
)?
.to_constraint_field()?;
)?;
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant( let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(), cs.clone(),
C1::BaseField::one(), C1::BaseField::one(),
)?
.to_constraint_field()?;
)?;
(u_i.cmE.x.is_eq(&zero_x)?).enforce_equal(&Boolean::TRUE)?; (u_i.cmE.x.is_eq(&zero_x)?).enforce_equal(&Boolean::TRUE)?;
(u_i.cmE.y.is_eq(&zero_y)?).enforce_equal(&Boolean::TRUE)?; (u_i.cmE.y.is_eq(&zero_y)?).enforce_equal(&Boolean::TRUE)?;
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?; (u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
// 4. u_i.x == H(i, z_0, z_i, U_i) // 4. u_i.x == H(i, z_0, z_i, U_i)
let u_i_x = U_i
let (u_i_x, _) = U_i
.clone() .clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
(u_i.x[0]).enforce_equal(&u_i_x)?; (u_i.x[0]).enforce_equal(&u_i_x)?;
@ -370,6 +367,7 @@ where
// `#[cfg(not(test))]` // `#[cfg(not(test))]`
use crate::commitment::pedersen::PedersenGadget; use crate::commitment::pedersen::PedersenGadget;
use crate::folding::nova::cyclefold::{CycleFoldCommittedInstanceVar, CF_IO_LEN}; use crate::folding::nova::cyclefold::{CycleFoldCommittedInstanceVar, CF_IO_LEN};
use ark_r1cs_std::ToBitsGadget;
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN); let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN);
let w_dummy_native = Witness::<C2>::new( let w_dummy_native = Witness::<C2>::new(
@ -386,16 +384,20 @@ where
// 5. check Pedersen commitments of cf_U_i.{cmE, cmW} // 5. check Pedersen commitments of cf_U_i.{cmE, cmW}
let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?; let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?;
let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?; let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?;
let cf_W_i_E_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
cf_W_i.E.iter().map(|E_i| E_i.to_bits_le()).collect();
let cf_W_i_W_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
cf_W_i.W.iter().map(|W_i| W_i.to_bits_le()).collect();
let computed_cmE = PedersenGadget::<C2, GC2>::commit( let computed_cmE = PedersenGadget::<C2, GC2>::commit(
H.clone(), H.clone(),
G.clone(), G.clone(),
cf_W_i.E.clone(),
cf_W_i.rE,
cf_W_i_E_bits?,
cf_W_i.rE.to_bits_le()?,
)?; )?;
cf_U_i.cmE.enforce_equal(&computed_cmE)?; cf_U_i.cmE.enforce_equal(&computed_cmE)?;
let computed_cmW = let computed_cmW =
PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i.W.clone(), cf_W_i.rW)?;
PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i_W_bits?, cf_W_i.rW.to_bits_le()?)?;
cf_U_i.cmW.enforce_equal(&computed_cmW)?; cf_U_i.cmW.enforce_equal(&computed_cmW)?;
let cf_r1cs = R1CSVar::< let cf_r1cs = R1CSVar::<
@ -641,17 +643,18 @@ pub mod tests {
let ivc_v = nova.clone(); let ivc_v = nova.clone();
let verifier_params = VerifierParams::<Projective, Projective2> { let verifier_params = VerifierParams::<Projective, Projective2> {
poseidon_config: poseidon_config.clone(), poseidon_config: poseidon_config.clone(),
r1cs: ivc_v.r1cs,
cf_r1cs: ivc_v.cf_r1cs,
r1cs: ivc_v.clone().r1cs,
cf_r1cs: ivc_v.clone().cf_r1cs,
}; };
let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances();
NOVA::verify( NOVA::verify(
verifier_params, verifier_params,
z_0, z_0,
ivc_v.z_i, ivc_v.z_i,
Fr::one(), Fr::one(),
(ivc_v.U_i, ivc_v.W_i),
(ivc_v.u_i, ivc_v.w_i),
(ivc_v.cf_U_i, ivc_v.cf_W_i),
running_instance,
incoming_instance,
cyclefold_instance,
) )
.unwrap(); .unwrap();

+ 148
- 78
folding-schemes/src/folding/nova/mod.rs

@ -289,17 +289,19 @@ where
/// Implements IVC.P of Nova+CycleFold /// Implements IVC.P of Nova+CycleFold
fn prove_step(&mut self) -> Result<(), Error> { fn prove_step(&mut self) -> Result<(), Error> {
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
let cf_circuit: CycleFoldCircuit<C1, GC1>;
let cfW_circuit: CycleFoldCircuit<C1, GC1>;
let cfE_circuit: CycleFoldCircuit<C1, GC1>;
let z_i1 = self.F.step_native(self.z_i.clone())?; let z_i1 = self.F.step_native(self.z_i.clone())?;
// compute T and cmT for AugmentedFCircuit // compute T and cmT for AugmentedFCircuit
let (T, cmT) = self.compute_cmT()?; let (T, cmT) = self.compute_cmT()?;
// r_bits is the r used to the RLC of the F' instances
let r_bits = ChallengeGadget::<C1>::get_challenge_native( let r_bits = ChallengeGadget::<C1>::get_challenge_native(
&self.poseidon_config, &self.poseidon_config,
self.u_i.clone(),
self.U_i.clone(), self.U_i.clone(),
self.u_i.clone(),
cmT, cmT,
)?; )?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
@ -307,7 +309,7 @@ where
// fold Nova instances // fold Nova instances
let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) = NIFS::<C1, CP1>::fold_instances( let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) = NIFS::<C1, CP1>::fold_instances(
r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT,
r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &T, cmT,
)?; )?;
// folded instance output (public input, x) // folded instance output (public input, x)
@ -333,75 +335,65 @@ where
cmT: Some(cmT), cmT: Some(cmT),
F: self.F, F: self.F,
x: Some(u_i1_x), x: Some(u_i1_x),
cf_u_i: None,
cf1_u_i: None,
cf2_u_i: None,
cf_U_i: None, cf_U_i: None,
cf1_U_i1: None,
cf_U_i1: None, cf_U_i1: None,
cf_cmT: None,
cf_r_nonnat: None,
cf1_cmT: None,
cf2_cmT: None,
cf1_r_nonnat: None,
cf2_r_nonnat: None,
}; };
#[cfg(test)] #[cfg(test)]
NIFS::<C1, CP1>::verify_folded_instance(r_Fr, &self.u_i, &self.U_i, &U_i1, &cmT)?;
NIFS::<C1, CP1>::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?;
} else { } else {
// 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 = [
get_committed_instance_coordinates(&self.u_i),
get_committed_instance_coordinates(&self.U_i),
get_committed_instance_coordinates(&U_i1),
// cyclefold circuit for cmW
let cfW_u_i_x = [
get_cm_coordinates(&self.U_i.cmW),
get_cm_coordinates(&self.u_i.cmW),
get_cm_coordinates(&U_i1.cmW),
]
.concat();
// cyclefold circuit for cmE
let cfE_u_i_x = [
get_cm_coordinates(&self.U_i.cmE),
get_cm_coordinates(&self.u_i.cmE),
get_cm_coordinates(&U_i1.cmE),
] ]
.concat(); .concat();
cf_circuit = CycleFoldCircuit::<C1, GC1> {
cfW_circuit = CycleFoldCircuit::<C1, GC1> {
_gc: PhantomData, _gc: PhantomData,
r_bits: Some(r_bits.clone()), r_bits: Some(r_bits.clone()),
cmT: Some(cmT),
u_i: Some(self.u_i.clone()),
U_i: Some(self.U_i.clone()),
U_i1: Some(U_i1.clone()),
x: Some(cf_u_i_x.clone()),
p1: Some(self.U_i.clone().cmW),
p2: Some(self.u_i.clone().cmW),
p3: Some(U_i1.clone().cmW),
x: Some(cfW_u_i_x.clone()),
};
cfE_circuit = CycleFoldCircuit::<C1, GC1> {
_gc: PhantomData,
r_bits: Some(r_bits.clone()),
p1: Some(self.U_i.clone().cmE),
p2: Some(cmT),
p3: Some(U_i1.clone().cmE),
x: Some(cfE_u_i_x.clone()),
}; };
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
cf_circuit.generate_constraints(cs2.clone())?;
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let (cf_w_i, cf_x_i) = extract_w_x::<C1::BaseField>(&cs2);
if cf_x_i != cf_u_i_x {
return Err(Error::NotEqual);
}
#[cfg(test)]
if cf_x_i.len() != CF_IO_LEN {
return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN));
}
// fold cyclefold instances
let cf_w_i = Witness::<C2>::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows);
let cf_u_i: CommittedInstance<C2> =
cf_w_i.commit::<CP2>(&self.cf_cm_params, cf_x_i.clone())?;
// compute T* and cmT* for CycleFoldCircuit
let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i)?;
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native(
&self.poseidon_config,
cf_u_i.clone(),
self.cf_U_i.clone(),
cf_cmT,
)?;
let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits))
.ok_or(Error::OutOfBounds)?;
let (cf_W_i1, cf_U_i1) = NIFS::<C2, CP2>::fold_instances(
cf_r_Fq,
&self.cf_W_i,
&self.cf_U_i,
&cf_w_i,
&cf_u_i,
&cf_T,
cf_cmT,
)?;
// fold self.cf_U_i + cfW_U -> folded running with cfW
let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, cfW_r1_Fq) = self
.fold_cyclefold_circuit(
self.cf_W_i.clone(), // CycleFold running instance witness
self.cf_U_i.clone(), // CycleFold running instance
cfW_u_i_x,
cfW_circuit,
)?;
// fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE
let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r2_Fq) =
self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?;
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> { augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData, _gc2: PhantomData,
@ -416,11 +408,15 @@ where
F: self.F, F: self.F,
x: Some(u_i1_x), x: Some(u_i1_x),
// cyclefold values // cyclefold values
cf_u_i: Some(cf_u_i.clone()),
cf1_u_i: Some(cfW_u_i.clone()),
cf2_u_i: Some(cfE_u_i.clone()),
cf_U_i: Some(self.cf_U_i.clone()), cf_U_i: Some(self.cf_U_i.clone()),
cf1_U_i1: Some(cfW_U_i1.clone()),
cf_U_i1: Some(cf_U_i1.clone()), cf_U_i1: Some(cf_U_i1.clone()),
cf_cmT: Some(cf_cmT),
cf_r_nonnat: Some(cf_r_Fq),
cf1_cmT: Some(cfW_cmT),
cf2_cmT: Some(cf_cmT),
cf1_r_nonnat: Some(cfW_r1_Fq),
cf2_r_nonnat: Some(cf_r2_Fq),
}; };
self.cf_W_i = cf_W_i1.clone(); self.cf_W_i = cf_W_i1.clone();
@ -428,7 +424,8 @@ where
#[cfg(test)] #[cfg(test)]
{ {
self.cf_r1cs.check_instance_relation(&cf_w_i, &cf_u_i)?;
self.cf_r1cs.check_instance_relation(&_cfW_w_i, &cfW_u_i)?;
self.cf_r1cs.check_instance_relation(&_cfE_w_i, &cfE_u_i)?;
self.cf_r1cs self.cf_r1cs
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?; .check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
} }
@ -557,18 +554,94 @@ where
&self, &self,
cf_w_i: &Witness<C2>, cf_w_i: &Witness<C2>,
cf_u_i: &CommittedInstance<C2>, cf_u_i: &CommittedInstance<C2>,
cf_W_i: &Witness<C2>,
cf_U_i: &CommittedInstance<C2>,
) -> Result<(Vec<C2::ScalarField>, C2), Error> { ) -> Result<(Vec<C2::ScalarField>, C2), Error> {
NIFS::<C2, CP2>::compute_cyclefold_cmT( NIFS::<C2, CP2>::compute_cyclefold_cmT(
&self.cf_cm_params, &self.cf_cm_params,
&self.cf_r1cs, &self.cf_r1cs,
cf_w_i, cf_w_i,
cf_u_i, cf_u_i,
&self.cf_W_i,
&self.cf_U_i,
cf_W_i,
cf_U_i,
) )
} }
} }
impl<C1, GC1, C2, GC2, FC, CP1, CP2> Nova<C1, GC1, C2, GC2, FC, CP1, CP2>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CP1: CommitmentProver<C1>,
CP2: CommitmentProver<C2>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
// folds the given cyclefold circuit and its instances
#[allow(clippy::type_complexity)]
fn fold_cyclefold_circuit(
&self,
cf_W_i: Witness<C2>, // witness of the running instance
cf_U_i: CommittedInstance<C2>, // running instance
cf_u_i_x: Vec<C2::ScalarField>,
cf_circuit: CycleFoldCircuit<C1, GC1>,
) -> Result<
(
Witness<C2>,
CommittedInstance<C2>, // u_i
Witness<C2>, // W_i1
CommittedInstance<C2>, // U_i1
C2, // cmT
C2::ScalarField, // r_Fq
),
Error,
> {
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
cf_circuit.generate_constraints(cs2.clone())?;
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let (cf_w_i, cf_x_i) = extract_w_x::<C1::BaseField>(&cs2);
if cf_x_i != cf_u_i_x {
return Err(Error::NotEqual);
}
#[cfg(test)]
if cf_x_i.len() != CF_IO_LEN {
return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN));
}
// fold cyclefold instances
let cf_w_i = Witness::<C2>::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows);
let cf_u_i: CommittedInstance<C2> =
cf_w_i.commit::<CP2>(&self.cf_cm_params, cf_x_i.clone())?;
// compute T* and cmT* for CycleFoldCircuit
let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i, &cf_W_i, &cf_U_i)?;
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native(
&self.poseidon_config,
cf_U_i.clone(),
cf_u_i.clone(),
cf_cmT,
)?;
let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits))
.ok_or(Error::OutOfBounds)?;
let (cf_W_i1, cf_U_i1) = NIFS::<C2, CP2>::fold_instances(
cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT,
)?;
Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq))
}
}
/// helper method to get the r1cs from the ConstraintSynthesizer /// helper method to get the r1cs from the ConstraintSynthesizer
pub fn get_r1cs_from_cs<F: PrimeField>( pub fn get_r1cs_from_cs<F: PrimeField>(
circuit: impl ConstraintSynthesizer<F>, circuit: impl ConstraintSynthesizer<F>,
@ -633,17 +706,11 @@ where
Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows)) Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows))
} }
pub(crate) fn get_committed_instance_coordinates<C: CurveGroup>(
u: &CommittedInstance<C>,
) -> Vec<C::BaseField> {
pub(crate) fn get_cm_coordinates<C: CurveGroup>(cm: &C) -> Vec<C::BaseField> {
let zero = (&C::BaseField::zero(), &C::BaseField::one()); let zero = (&C::BaseField::zero(), &C::BaseField::one());
let cmE = u.cmE.into_affine();
let (cmE_x, cmE_y) = cmE.xy().unwrap_or(zero);
let cmW = u.cmW.into_affine();
let (cmW_x, cmW_y) = cmW.xy().unwrap_or(zero);
vec![*cmE_x, *cmE_y, *cmW_x, *cmW_y]
let cm = cm.into_affine();
let (cm_x, cm_y) = cm.xy().unwrap_or(zero);
vec![*cm_x, *cm_y]
} }
#[cfg(test)] #[cfg(test)]
@ -656,6 +723,8 @@ pub mod tests {
use crate::frontend::tests::CubicFCircuit; use crate::frontend::tests::CubicFCircuit;
use crate::transcript::poseidon::poseidon_test_config; use crate::transcript::poseidon::poseidon_test_config;
/// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the
/// AugmentedFCircuit
#[test] #[test]
fn test_ivc() { fn test_ivc() {
type NOVA = Nova< type NOVA = Nova<
@ -700,17 +769,18 @@ pub mod tests {
let verifier_params = VerifierParams::<Projective, Projective2> { let verifier_params = VerifierParams::<Projective, Projective2> {
poseidon_config, poseidon_config,
r1cs: nova.r1cs,
cf_r1cs: nova.cf_r1cs,
r1cs: nova.clone().r1cs,
cf_r1cs: nova.clone().cf_r1cs,
}; };
let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
NOVA::verify( NOVA::verify(
verifier_params, verifier_params,
z_0, z_0,
nova.z_i, nova.z_i,
nova.i, nova.i,
(nova.U_i, nova.W_i),
(nova.u_i, nova.w_i),
(nova.cf_U_i, nova.cf_W_i),
running_instance,
incoming_instance,
cyclefold_instance,
) )
.unwrap(); .unwrap();
} }

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

@ -68,8 +68,8 @@ where
pub fn fold_committed_instance( pub fn fold_committed_instance(
r: C::ScalarField, r: C::ScalarField,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
ci1: &CommittedInstance<C>, // U_i
ci2: &CommittedInstance<C>, // u_i
cmT: &C, cmT: &C,
) -> CommittedInstance<C> { ) -> CommittedInstance<C> {
let r2 = r * r; let r2 = r * r;

+ 2
- 1
folding-schemes/src/lib.rs

@ -104,7 +104,8 @@ where
// returns the state at the current step // returns the state at the current step
fn state(&self) -> Vec<C1::ScalarField>; fn state(&self) -> Vec<C1::ScalarField>;
// returns the instances at the current step
// returns the instances at the current step, in the following order:
// (running_instance, incoming_instance, cyclefold_instance)
fn instances( fn instances(
&self, &self,
) -> ( ) -> (

+ 5
- 0
folding-schemes/src/utils/gadgets.rs

@ -15,6 +15,11 @@ pub fn mat_vec_mul_sparse>(
let mut res = vec![FV::zero(); m.n_rows]; let mut res = vec![FV::zero(); m.n_rows];
for (row_i, row) in m.coeffs.iter().enumerate() { for (row_i, row) in m.coeffs.iter().enumerate() {
for (value, col_i) in row.iter() { for (value, col_i) in row.iter() {
if value.value().unwrap() == F::one() {
// no need to multiply by 1
res[row_i] += v[*col_i].clone();
continue;
}
res[row_i] += value.clone().mul(&v[*col_i].clone()); res[row_i] += value.clone().mul(&v[*col_i].clone());
} }
} }

Loading…
Cancel
Save