Browse Source

Reduce the number of constraints in `AugmentedFCircuit` for Nova (#86)

* Reduce the number of constraints in `AugmentedFCircuit`

For the test `folding::nova::tests::test_ivc`
Before: 138240
After: 86756 (1.6x improvement)

Two notable optimization techniques:
1. Instead of allocating two witness variables `a, b` and enforce their equality by calling `a.conditional_enforce_equal(&b, &cond)`, we can avoid the allocation of `b` and directly set `b = a`. The former might be costly due to the checks in allocation and `conditional_enforce_equal`. See `nova/circuits.rs` for details.
2. Before this commit, `NonNativeFieldVar::to_constraint_field` was majorly called for generating the inputs (preimage) to hash functions. However, it turns out that the underlying conversion strategy (optimized for weight) is not optimal for reducing the length of hash preimage. We can go further by maximizing the number of bits per limb, thereby minimizing the preimage length. See `circuits/nonnative.rs` for details.

* Format

* Fix clippy warnings

* Move the comments to the right position

* Cleanup unnecessary code
main
winderica 8 months ago
committed by GitHub
parent
commit
4dcb981dd4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
7 changed files with 378 additions and 293 deletions
  1. +133
    -16
      folding-schemes/src/folding/circuits/nonnative.rs
  2. +170
    -176
      folding-schemes/src/folding/nova/circuits.rs
  3. +41
    -45
      folding-schemes/src/folding/nova/cyclefold.rs
  4. +4
    -8
      folding-schemes/src/folding/nova/decider_eth.rs
  5. +5
    -13
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  6. +24
    -33
      folding-schemes/src/folding/nova/mod.rs
  7. +1
    -2
      folding-schemes/src/folding/nova/nifs.rs

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

@ -2,16 +2,54 @@ use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{BigInteger, PrimeField}; use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{ use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode}, alloc::{AllocVar, AllocationMode},
boolean::Boolean,
fields::{ fields::{
fp::FpVar, fp::FpVar,
nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar, NonNativeFieldVar},
nonnative::{
params::{get_params, OptimizationType},
AllocatedNonNativeFieldVar, NonNativeFieldVar,
},
FieldVar, FieldVar,
}, },
ToBitsGadget,
ToBitsGadget, ToConstraintFieldGadget,
}; };
use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, OptimizationGoal, SynthesisError};
use ark_std::Zero; use ark_std::Zero;
use core::borrow::Borrow; use core::borrow::Borrow;
use std::marker::PhantomData;
/// Compose a vector boolean into a `NonNativeFieldVar`
pub fn nonnative_field_var_from_le_bits<TargetField: PrimeField, BaseField: PrimeField>(
cs: ConstraintSystemRef<BaseField>,
bits: &[Boolean<BaseField>],
) -> Result<NonNativeFieldVar<TargetField, BaseField>, SynthesisError> {
let params = get_params(
TargetField::MODULUS_BIT_SIZE as usize,
BaseField::MODULUS_BIT_SIZE as usize,
match cs.optimization_goal() {
OptimizationGoal::None => OptimizationType::Constraints,
OptimizationGoal::Constraints => OptimizationType::Constraints,
OptimizationGoal::Weight => OptimizationType::Weight,
},
);
// push the lower limbs first
let mut limbs = bits
.chunks(params.bits_per_limb)
.map(Boolean::le_bits_to_fp_var)
.collect::<Result<Vec<_>, _>>()?;
limbs.resize(params.num_limbs, FpVar::zero());
limbs.reverse();
Ok(AllocatedNonNativeFieldVar {
cs,
limbs,
num_of_additions_over_normal_form: BaseField::one(),
is_in_the_normal_form: false,
target_phantom: PhantomData,
}
.into())
}
/// A more efficient version of `NonNativeFieldVar::to_constraint_field` /// A more efficient version of `NonNativeFieldVar::to_constraint_field`
pub fn nonnative_field_var_to_constraint_field<TargetField: PrimeField, BaseField: PrimeField>( pub fn nonnative_field_var_to_constraint_field<TargetField: PrimeField, BaseField: PrimeField>(
@ -112,23 +150,63 @@ where
} }
} }
/// Wrapper on top of [`point_to_nonnative_limbs_custom_opt`] which always uses
/// [`OptimizationType::Weight`].
impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
// A more efficient version of `point_to_nonnative_limbs_custom_opt`.
// Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
// the circuit.
fn to_constraint_field(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
let x = nonnative_field_var_to_constraint_field(&self.x)?;
let y = nonnative_field_var_to_constraint_field(&self.y)?;
Ok([x, y].concat())
}
}
/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field`
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn point_to_nonnative_limbs<C: CurveGroup>(
pub fn nonnative_affine_to_field_elements<C: CurveGroup>(
p: C, p: C,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> ) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{ {
point_to_nonnative_limbs_custom_opt(p, OptimizationType::Weight)
let affine = p.into_affine();
if affine.is_zero() {
let x = nonnative_field_to_field_elements(&C::BaseField::zero());
let y = nonnative_field_to_field_elements(&C::BaseField::zero());
return Ok((x, y));
}
let (x, y) = affine.xy().unwrap();
let x = nonnative_field_to_field_elements(x);
let y = nonnative_field_to_field_elements(y);
Ok((x, y))
}
impl<C: CurveGroup> NonNativeAffineVar<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
// A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization
// type (which is the default optimization type for arkworks' Groth16).
// Used for extracting a list of field elements of type `C::ScalarField` from the public input
// `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type
// `FpVar` in-circuit.
#[allow(clippy::type_complexity)]
pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> {
point_to_nonnative_limbs_custom_opt(p, OptimizationType::Constraints)
}
} }
/// Used to compute (outside the circuit) the limbs representation of a point that matches the one
/// used in-circuit, and in particular this method allows to specify which [`OptimizationType`] to
/// use.
// Used to compute (outside the circuit) the limbs representation of a point.
// For `OptimizationType::Constraints`, the result matches the one used in-circuit.
// For `OptimizationType::Weight`, the result vector is more dense and is suitable for hashing.
// It is possible to further optimize the length of the result vector (see
// `nonnative_affine_to_field_elements`)
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn point_to_nonnative_limbs_custom_opt<C: CurveGroup>(
fn point_to_nonnative_limbs_custom_opt<C: CurveGroup>(
p: C, p: C,
optimization_type: OptimizationType, optimization_type: OptimizationType,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> ) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
@ -166,24 +244,63 @@ 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, ToConstraintFieldGadget};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::{UniformRand, Zero};
use ark_std::UniformRand;
#[test] #[test]
fn test_alloc_nonnativeaffinevar() {
fn test_alloc_zero() {
let cs = ConstraintSystem::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();
// 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::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
assert!(NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).is_ok());
}
#[test]
fn test_arkworks_to_constraint_field() {
let cs = ConstraintSystem::<Fr>::new_ref();
// 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::<Projective>::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_custom_opt(p, OptimizationType::Weight).unwrap();
assert_eq!(pVar.x.to_constraint_field().unwrap().value().unwrap(), x); assert_eq!(pVar.x.to_constraint_field().unwrap().value().unwrap(), x);
assert_eq!(pVar.y.to_constraint_field().unwrap().value().unwrap(), y); assert_eq!(pVar.y.to_constraint_field().unwrap().value().unwrap(), y);
} }
#[test]
fn test_improved_to_constraint_field() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = nonnative_affine_to_field_elements(p).unwrap();
assert_eq!(
pVar.to_constraint_field().unwrap().value().unwrap(),
[x, y].concat()
);
}
#[test]
fn test_inputize() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = NonNativeAffineVar::inputize(p).unwrap();
match (pVar.x, pVar.y) {
(NonNativeFieldVar::Var(p_x), NonNativeFieldVar::Var(p_y)) => {
assert_eq!(p_x.limbs.value().unwrap(), x);
assert_eq!(p_y.limbs.value().unwrap(), y);
}
_ => unreachable!(),
}
}
} }

+ 170
- 176
folding-schemes/src/folding/nova/circuits.rs

@ -17,11 +17,10 @@ use ark_r1cs_std::{
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
groups::GroupOpsBounds, groups::GroupOpsBounds,
prelude::CurveVar, prelude::CurveVar,
ToBitsGadget, ToConstraintFieldGadget,
R1CSVar, ToConstraintFieldGadget,
}; };
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::fmt::Debug;
use ark_std::Zero;
use ark_std::{fmt::Debug, Zero};
use core::{borrow::Borrow, marker::PhantomData}; use core::{borrow::Borrow, marker::PhantomData};
use super::{ use super::{
@ -30,9 +29,9 @@ use super::{
}, },
CommittedInstance, CommittedInstance,
}; };
use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar};
use crate::folding::circuits::nonnative::{nonnative_affine_to_field_elements, NonNativeAffineVar};
use crate::frontend::FCircuit; use crate::frontend::FCircuit;
use crate::{constants::N_BITS_RO, folding::circuits::nonnative::nonnative_field_var_from_le_bits};
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where /// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where
/// E1 is the main curve where we do the folding. /// E1 is the main curve where we do the folding.
@ -106,10 +105,8 @@ where
let U_vec = [ let U_vec = [
vec![self.u], vec![self.u],
self.x, self.x,
self.cmE.x.to_constraint_field()?,
self.cmE.y.to_constraint_field()?,
self.cmW.x.to_constraint_field()?,
self.cmW.y.to_constraint_field()?,
self.cmE.to_constraint_field()?,
self.cmW.to_constraint_field()?,
] ]
.concat(); .concat();
let input = [vec![i], z_0, z_i, U_vec.clone()].concat(); let input = [vec![i], z_0, z_i, U_vec.clone()].concat();
@ -132,6 +129,26 @@ where
C: CurveGroup, C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{ {
pub fn fold_committed_instance(
r: FpVar<CF1<C>>,
ci1: CommittedInstanceVar<C>, // U_i
ci2: CommittedInstanceVar<C>, // u_i
) -> Result<CommittedInstanceVar<C>, SynthesisError> {
Ok(CommittedInstanceVar {
cmE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
cmW: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
// ci3.u = ci1.u + r * ci2.u
u: ci1.u + &r * ci2.u,
// ci3.x = ci1.x + r * ci2.x
x: ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| a + &r * &b)
.collect::<Vec<FpVar<CF1<C>>>>(),
})
}
/// 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(
@ -139,20 +156,13 @@ where
ci1: CommittedInstanceVar<C>, // U_i ci1: CommittedInstanceVar<C>, // U_i
ci2: CommittedInstanceVar<C>, // u_i ci2: CommittedInstanceVar<C>, // u_i
ci3: CommittedInstanceVar<C>, // U_{i+1} ci3: CommittedInstanceVar<C>, // U_{i+1}
) -> Result<Boolean<CF1<C>>, SynthesisError> {
// ensure that: ci3.u == ci1.u + r * ci2.u
let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?;
// ensure that: ci3.x == ci1.x + r * ci2.x
let x_rlc = ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| a + &r * &b)
.collect::<Vec<FpVar<CF1<C>>>>();
let second_check = x_rlc.is_eq(&ci3.x)?;
first_check.and(&second_check)
) -> Result<(), SynthesisError> {
let ci = Self::fold_committed_instance(r, ci1, ci2)?;
ci.u.enforce_equal(&ci3.u)?;
ci.x.enforce_equal(&ci3.x)?;
Ok(())
} }
} }
@ -173,11 +183,11 @@ where
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_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 (U_cmE_x, U_cmE_y) = nonnative_affine_to_field_elements::<C>(U_i.cmE)?;
let (U_cmW_x, U_cmW_y) = nonnative_affine_to_field_elements::<C>(U_i.cmW)?;
let (u_cmE_x, u_cmE_y) = nonnative_affine_to_field_elements::<C>(u_i.cmE)?;
let (u_cmW_x, u_cmW_y) = nonnative_affine_to_field_elements::<C>(u_i.cmW)?;
let (cmT_x, cmT_y) = nonnative_affine_to_field_elements::<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![
@ -212,16 +222,13 @@ where
) -> 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>> = [
U_i_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.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()?,
u_i.cmE.to_constraint_field()?,
u_i.cmW.to_constraint_field()?,
cmT.to_constraint_field()?,
] ]
.concat(); .concat();
sponge.absorb(&input)?; sponge.absorb(&input)?;
@ -248,10 +255,10 @@ pub struct AugmentedFCircuit<
pub i_usize: Option<usize>, pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>, pub z_0: Option<Vec<C1::ScalarField>>,
pub z_i: Option<Vec<C1::ScalarField>>, pub z_i: Option<Vec<C1::ScalarField>>,
pub u_i: Option<CommittedInstance<C1>>,
pub u_i_cmW: Option<C1>,
pub U_i: Option<CommittedInstance<C1>>, pub U_i: Option<CommittedInstance<C1>>,
pub U_i1: Option<CommittedInstance<C1>>,
pub r_nonnat: Option<CF2<C1>>,
pub U_i1_cmE: Option<C1>,
pub U_i1_cmW: Option<C1>,
pub cmT: Option<C1>, pub cmT: Option<C1>,
pub F: FC, // F circuit pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0]) pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
@ -259,15 +266,11 @@ pub struct AugmentedFCircuit<
// cyclefold verifier on C1 // cyclefold verifier on C1
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
// cmE respectively // 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_u_i_cmW: Option<C2>, // input
pub cf2_u_i_cmW: Option<C2>, // input
pub cf_U_i: Option<CommittedInstance<C2>>, // input
pub cf1_cmT: Option<C2>, pub cf1_cmT: Option<C2>,
pub cf2_cmT: Option<C2>, pub cf2_cmT: Option<C2>,
pub cf1_r_nonnat: Option<C2::ScalarField>,
pub cf2_r_nonnat: Option<C2::ScalarField>,
pub cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1]) pub cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
} }
@ -284,23 +287,19 @@ where
i_usize: None, i_usize: None,
z_0: None, z_0: None,
z_i: None, z_i: None,
u_i: None,
u_i_cmW: None,
U_i: None, U_i: None,
U_i1: None,
r_nonnat: None,
U_i1_cmE: None,
U_i1_cmW: None,
cmT: None, cmT: None,
F: F_circuit, F: F_circuit,
x: None, x: None,
// cyclefold values // cyclefold values
cf1_u_i: None,
cf2_u_i: None,
cf1_u_i_cmW: None,
cf2_u_i_cmW: None,
cf_U_i: None, cf_U_i: None,
cf1_U_i1: None,
cf_U_i1: None,
cf1_cmT: None, cf1_cmT: None,
cf2_cmT: None, cf2_cmT: None,
cf1_r_nonnat: None,
cf2_r_nonnat: None,
cf_x: None, cf_x: None,
} }
} }
@ -334,24 +333,26 @@ where
.unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()])) .unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()]))
})?; })?;
let u_dummy_native = CommittedInstance::<C1>::dummy(2);
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
})?;
let u_dummy = CommittedInstance::dummy(2);
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || { let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
Ok(self.U_i.unwrap_or(u_dummy.clone()))
})?; })?;
let U_i1 = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
let U_i1_cmE = NonNativeAffineVar::new_witness(cs.clone(), || {
Ok(self.U_i1_cmE.unwrap_or_else(C1::zero))
})?; })?;
let r_nonnat =
NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
Ok(self.r_nonnat.unwrap_or_else(CF2::<C1>::zero))
})?;
let U_i1_cmW = NonNativeAffineVar::new_witness(cs.clone(), || {
Ok(self.U_i1_cmW.unwrap_or_else(C1::zero))
})?;
let cmT = let cmT =
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
let x =
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C1>::zero)))?;
let cf_u_dummy = CommittedInstance::dummy(CF_IO_LEN);
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone()))
})?;
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 crh_params = CRHParametersVar::<C1::ScalarField>::new_constant( let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant(
cs.clone(), cs.clone(),
@ -364,22 +365,33 @@ where
.F .F
.generate_step_constraints(cs.clone(), i_usize, z_i.clone())?; .generate_step_constraints(cs.clone(), i_usize, z_i.clone())?;
let zero = FpVar::<CF1<C1>>::new_constant(cs.clone(), CF1::<C1>::zero())?;
let is_not_basecase = i.is_neq(&zero)?;
let is_basecase = i.is_zero()?;
// 1.a u_i.x[0] == H(i, z_0, z_i, U_i)
// Primary Part
// P.1. Compute u_i.x
// u_i.x[0] = H(i, z_0, z_i, U_i)
let (u_i_x, U_i_vec) = let (u_i_x, U_i_vec) =
U_i.clone() U_i.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
// check that h == u_i.x[0]
(u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
// u_i.x[1] = H(cf_U_i)
let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params)?;
// P.2. Construct u_i
let u_i = CommittedInstanceVar {
// u_i.cmE = cm(0)
cmE: NonNativeAffineVar::new_constant(cs.clone(), C1::zero())?,
// u_i.u = 1
u: FpVar::one(),
// u_i.cmW is provided by the prover as witness
cmW: NonNativeAffineVar::new_witness(cs.clone(), || {
Ok(self.u_i_cmW.unwrap_or(C1::zero()))
})?,
// u_i.x is computed in step 1
x: vec![u_i_x, cf_u_i_x],
};
// 2. u_i.cmE==cm(0), u_i.u==1
(u_i.cmE.x.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.cmE.y.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
// P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i .
// 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}.
// compute r = H(u_i, U_i, cmT) // compute r = H(u_i, U_i, cmT)
let r_bits = ChallengeGadget::<C1>::get_challenge_gadget( let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
cs.clone(), cs.clone(),
@ -389,60 +401,38 @@ where
cmT.clone(), cmT.clone(),
)?; )?;
let r = Boolean::le_bits_to_fp_var(&r_bits)?; let r = Boolean::le_bits_to_fp_var(&r_bits)?;
// enforce that the input r_nonnat as bits matches the in-circuit computed r_bits
let r_nonnat_bits: Vec<Boolean<C1::ScalarField>> =
r_nonnat.to_bits_le()?.into_iter().take(N_BITS_RO).collect();
r_nonnat_bits.enforce_equal(&r_bits)?;
// Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
// be done on the other curve.
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)?;
// 4.a u_{i+1}.x[0] = H(i+1, z_0, z_i+1, U_{i+1}), this is the first output of F'
// Also convert r_bits to a `NonNativeFieldVar`
let r_nonnat = nonnative_field_var_from_le_bits(cs.clone(), &r_bits)?;
// Notice that NIFSGadget::fold_committed_instance does not fold cmE & cmW.
// We set `U_i1.cmE` and `U_i1.cmW` to unconstrained witnesses `U_i1_cmE` and `U_i1_cmW`
// respectively.
// The correctness of them will be checked on the other curve.
let mut U_i1 = NIFSGadget::<C1>::fold_committed_instance(r, U_i.clone(), u_i.clone())?;
U_i1.cmE = U_i1_cmE;
U_i1.cmW = U_i1_cmW;
// P.4.a compute and check the first output of F'
// Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
// Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
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(),
z_i1.clone(), z_i1.clone(),
)?; )?;
u_i1_x.enforce_equal(&x)?;
let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
&crh_params,
FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?;
x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
// CycleFold part // CycleFold part
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN);
// 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(), || {
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(), || {
Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;
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))
})?;
let cf_x = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.cf_x.unwrap_or_else(C1::ScalarField::zero))
})?;
let cfW_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
// C.1. Compute cf1_u_i.x and cf2_u_i.x
let cfW_x = vec![
r_nonnat.clone(), r_nonnat.clone(),
U_i.cmW.x, U_i.cmW.x,
U_i.cmW.y, U_i.cmW.y,
@ -451,78 +441,86 @@ where
U_i1.cmW.x, U_i1.cmW.x,
U_i1.cmW.y, U_i1.cmW.y,
]; ];
let cfE_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
let cfE_x = vec![
r_nonnat, U_i.cmE.x, U_i.cmE.y, cmT.x, cmT.y, U_i1.cmE.x, U_i1.cmE.y, r_nonnat, U_i.cmE.x, U_i.cmE.y, cmT.x, cmT.y, U_i1.cmE.x, U_i1.cmE.y,
]; ];
// 1.b u_i.x[1] == H(cf_U_i)
let (cf_u_i_x, _) = cf_U_i.clone().hash(&crh_params)?;
// check that h == u_i.x[1]
(u_i.x[1]).conditional_enforce_equal(&cf_u_i_x, &is_not_basecase)?;
// 4.b u_{i+1}.x[1] = H(cf_U_{i+1}), this is the second output of F'
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params)?;
cf_u_i1_x.enforce_equal(&cf_x)?;
// ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i, // 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 // 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)?;
// C.2. Construct `cf1_u_i` and `cf2_u_i`
let cf1_u_i = CycleFoldCommittedInstanceVar {
// cf1_u_i.cmE = 0
cmE: GC2::zero(),
// cf1_u_i.u = 1
u: NonNativeFieldVar::one(),
// cf1_u_i.cmW is provided by the prover as witness
cmW: GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW.unwrap_or(C2::zero())))?,
// cf1_u_i.x is computed in step 1
x: cfW_x,
};
let cf2_u_i = CycleFoldCommittedInstanceVar {
// cf2_u_i.cmE = 0
cmE: GC2::zero(),
// cf2_u_i.u = 1
u: NonNativeFieldVar::one(),
// cf2_u_i.cmW is provided by the prover as witness
cmW: GC2::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW.unwrap_or(C2::zero())))?,
// cf2_u_i.x is computed in step 1
x: cfE_x,
};
// C.3. nifs.verify, obtains cf1_U_{i+1} by folding cf1_u_i & cf_U_i, and then cf_U_{i+1}
// by folding cf2_u_i & cf1_U_{i+1}.
// compute cf1_r = H(cf1_u_i, cf_U_i, cf1_cmT)
// cf_r_bits is denoted by rho* in the paper. // 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( 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_vec,
cf1_u_i.clone(), cf1_u_i.clone(),
cf1_cmT.clone(), cf1_cmT.clone(),
)?; )?;
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)?;
// Convert cf1_r_bits to a `NonNativeFieldVar`
let cf1_r_nonnat = nonnative_field_var_from_le_bits(cs.clone(), &cf1_r_bits)?;
// Fold cf1_u_i & cf_U_i into cf1_U_{i+1}
let cf1_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
cf1_r_bits,
cf1_r_nonnat,
cf1_cmT,
cf_U_i,
cf1_u_i,
)?;
// same for cf2_r: // same for cf2_r:
let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget( let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(), cs.clone(),
&self.poseidon_config, &self.poseidon_config,
cf1_U_i1.clone(),
cf1_U_i1.to_constraint_field()?,
cf2_u_i.clone(), cf2_u_i.clone(),
cf2_cmT.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
(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
// curve points relations are checked natively in Curve1 circuit (this one)
let v1 = NIFSFullGadget::<C2, GC2>::verify(
cf1_r_bits,
cf1_r_nonnat,
cf1_cmT,
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(
let cf2_r_nonnat = nonnative_field_var_from_le_bits(cs.clone(), &cf2_r_bits)?;
let cf_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
cf2_r_bits, cf2_r_bits,
cf2_r_nonnat, cf2_r_nonnat,
cf2_cmT, cf2_cmT,
cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u)
cf2_u_i, cf2_u_i,
cf_U_i1,
)?; )?;
v2.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
// Back to Primary Part
// P.4.b compute and check the second output of F'
// Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
// Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params)?;
let (cf_u_i1_x_base, _) =
CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)?
.hash(&crh_params)?;
let cf_x = FpVar::new_input(cs.clone(), || {
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
})?;
cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?;
Ok(()) Ok(())
} }
@ -533,7 +531,6 @@ pub mod tests {
use super::*; use super::*;
use ark_bn254::{Fr, G1Projective as Projective}; use ark_bn254::{Fr, G1Projective as Projective};
use ark_ff::BigInteger; use ark_ff::BigInteger;
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand; use ark_std::UniformRand;
@ -583,14 +580,13 @@ pub mod tests {
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone())) CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
.unwrap(); .unwrap();
let nifs_check = NIFSGadget::<Projective>::verify(
NIFSGadget::<Projective>::verify(
rVar.clone(), rVar.clone(),
ci1Var.clone(), ci1Var.clone(),
ci2Var.clone(), ci2Var.clone(),
ci3Var.clone(), ci3Var.clone(),
) )
.unwrap(); .unwrap();
nifs_check.enforce_equal(&Boolean::<Fr>::TRUE).unwrap();
assert!(cs.is_satisfied().unwrap()); assert!(cs.is_satisfied().unwrap());
} }
@ -675,10 +671,8 @@ pub mod tests {
let U_iVar_vec = [ let U_iVar_vec = [
vec![U_iVar.u.clone()], vec![U_iVar.u.clone()],
U_iVar.x.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(),
U_iVar.cmE.to_constraint_field().unwrap(),
U_iVar.cmW.to_constraint_field().unwrap(),
] ]
.concat(); .concat();
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget( let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(

+ 41
- 45
folding-schemes/src/folding/nova/cyclefold.rs

@ -42,7 +42,6 @@ pub struct CycleFoldCommittedInstanceVar>>
where where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{ {
_c: PhantomData<C>,
pub cmE: GC, pub cmE: GC,
pub u: NonNativeFieldVar<C::ScalarField, CF2<C>>, pub u: NonNativeFieldVar<C::ScalarField, CF2<C>>,
pub cmW: GC, pub cmW: GC,
@ -76,13 +75,7 @@ where
mode, mode,
)?; )?;
Ok(Self {
_c: PhantomData,
cmE,
u,
cmW,
x,
})
Ok(Self { cmE, u, cmW, x })
}) })
} }
} }
@ -189,6 +182,7 @@ pub struct NIFSFullGadget>> {
_c: PhantomData<C>, _c: PhantomData<C>,
_gc: PhantomData<GC>, _gc: PhantomData<GC>,
} }
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<C, GC> impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<C, GC>
where where
C: CurveGroup, C: CurveGroup,
@ -196,42 +190,46 @@ where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField, <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{ {
pub fn fold_committed_instance(
// assumes that r_bits is equal to r_nonnat just that in a different format
r_bits: Vec<Boolean<CF2<C>>>,
r_nonnat: NonNativeFieldVar<C::ScalarField, CF2<C>>,
cmT: GC,
ci1: CycleFoldCommittedInstanceVar<C, GC>,
// ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
ci2: CycleFoldCommittedInstanceVar<C, GC>,
) -> Result<CycleFoldCommittedInstanceVar<C, GC>, SynthesisError> {
Ok(CycleFoldCommittedInstanceVar {
cmE: cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE,
cmW: ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?,
u: ci1.u + r_nonnat.clone(),
x: ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| a + &r_nonnat * &b)
.collect::<Vec<_>>(),
})
}
pub fn verify( pub fn verify(
// assumes that r_bits is equal to r_nonnat just that in a different format // 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,
// ci1 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
ci1: CycleFoldCommittedInstanceVar<C, GC>, ci1: CycleFoldCommittedInstanceVar<C, GC>,
// ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
ci2: CycleFoldCommittedInstanceVar<C, GC>, ci2: CycleFoldCommittedInstanceVar<C, GC>,
ci3: CycleFoldCommittedInstanceVar<C, GC>, ci3: CycleFoldCommittedInstanceVar<C, GC>,
) -> Result<Boolean<CF2<C>>, SynthesisError> {
// cm(E) check: ci3.cmE == ci1.cmE + r * cmT (ci2.cmE=0)
let first_check = ci3
.cmE
.is_eq(&(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())?))?;
let u_rlc: NonNativeFieldVar<C::ScalarField, CF2<C>> = ci1.u + r_nonnat.clone();
let third_check = u_rlc.is_eq(&ci3.u)?;
// ensure that: ci3.x == ci1.x + r * ci2.x
let x_rlc: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> = ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| a + &r_nonnat * &b)
.collect::<Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>>();
let fourth_check = x_rlc.is_eq(&ci3.x)?;
first_check
.and(&second_check)?
.and(&third_check)?
.and(&fourth_check)
) -> Result<(), SynthesisError> {
let ci = Self::fold_committed_instance(r_bits, r_nonnat, cmT, ci1, ci2)?;
ci.cmE.enforce_equal(&ci3.cmE)?;
ci.u.enforce_equal(&ci3.u)?;
ci.cmW.enforce_equal(&ci3.cmW)?;
ci.x.enforce_equal(&ci3.x)?;
Ok(())
} }
} }
@ -285,25 +283,24 @@ 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>,
mut U_i_vec: Vec<FpVar<C::BaseField>>,
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 mut U_vec = U_i.to_constraint_field()?;
let mut u_vec = u_i.to_constraint_field()?;
let mut u_i_vec = u_i.to_constraint_field()?;
let mut cmT_vec = cmT.to_constraint_field()?; let mut cmT_vec = cmT.to_constraint_field()?;
let U_cm_is_inf = U_vec.pop().unwrap();
let u_cm_is_inf = u_vec.pop().unwrap();
let U_cm_is_inf = U_i_vec.pop().unwrap();
let u_cm_is_inf = u_i_vec.pop().unwrap();
let cmT_is_inf = cmT_vec.pop().unwrap(); let cmT_is_inf = cmT_vec.pop().unwrap();
// Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf` // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf`
// to save constraints for sponge.squeeze_bits // to save constraints for sponge.squeeze_bits
let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double()? + cmT_is_inf; let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double()? + cmT_is_inf;
let input = [U_vec, u_vec, cmT_vec, vec![is_inf]].concat();
let input = [U_i_vec, u_i_vec, cmT_vec, vec![is_inf]].concat();
sponge.absorb(&input)?; sponge.absorb(&input)?;
let bits = sponge.squeeze_bits(N_BITS_RO)?; let bits = sponge.squeeze_bits(N_BITS_RO)?;
Ok(bits) Ok(bits)
@ -486,7 +483,7 @@ pub mod tests {
.unwrap(); .unwrap();
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap(); let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let nifs_check = NIFSFullGadget::<Projective, GVar>::verify(
NIFSFullGadget::<Projective, GVar>::verify(
r_bitsVar, r_bitsVar,
r_nonnatVar, r_nonnatVar,
cmTVar, cmTVar,
@ -495,7 +492,6 @@ pub mod tests {
ci3Var, ci3Var,
) )
.unwrap(); .unwrap();
nifs_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
assert!(cs.is_satisfied().unwrap()); assert!(cs.is_satisfied().unwrap());
} }
@ -547,7 +543,7 @@ 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.to_constraint_field().unwrap(),
u_iVar, u_iVar,
cmTVar, cmTVar,
) )

+ 4
- 8
folding-schemes/src/folding/nova/decider_eth.rs

@ -2,7 +2,6 @@
use ark_crypto_primitives::sponge::Absorb; use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group}; use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField; use ark_ff::PrimeField;
use ark_r1cs_std::fields::nonnative::params::OptimizationType;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_snark::SNARK; use ark_snark::SNARK;
use ark_std::rand::{CryptoRng, RngCore}; use ark_std::rand::{CryptoRng, RngCore};
@ -14,7 +13,7 @@ use super::{circuits::CF2, nifs::NIFS, CommittedInstance, Nova};
use crate::commitment::{ use crate::commitment::{
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme, kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
}; };
use crate::folding::circuits::nonnative::point_to_nonnative_limbs_custom_opt;
use crate::folding::circuits::nonnative::NonNativeAffineVar;
use crate::frontend::FCircuit; use crate::frontend::FCircuit;
use crate::Error; use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme}; use crate::{Decider as DeciderTrait, FoldingScheme};
@ -151,12 +150,9 @@ where
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT); let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
let (cmE_x, cmE_y) =
point_to_nonnative_limbs_custom_opt::<C1>(U.cmE, OptimizationType::Constraints)?;
let (cmW_x, cmW_y) =
point_to_nonnative_limbs_custom_opt::<C1>(U.cmW, OptimizationType::Constraints)?;
let (cmT_x, cmT_y) =
point_to_nonnative_limbs_custom_opt::<C1>(proof.cmT, OptimizationType::Constraints)?;
let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
let public_input: Vec<C1::ScalarField> = vec![ let public_input: Vec<C1::ScalarField> = vec![
vec![i], vec![i],

+ 5
- 13
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -22,7 +22,7 @@ use core::{borrow::Borrow, marker::PhantomData};
use super::{circuits::ChallengeGadget, nifs::NIFS}; use super::{circuits::ChallengeGadget, nifs::NIFS};
use crate::ccs::r1cs::R1CS; use crate::ccs::r1cs::R1CS;
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar};
use crate::folding::circuits::nonnative::{nonnative_affine_to_field_elements, NonNativeAffineVar};
use crate::folding::nova::{ use crate::folding::nova::{
circuits::{CommittedInstanceVar, CF1, CF2}, circuits::{CommittedInstanceVar, CF1, CF2},
CommittedInstance, Nova, Witness, CommittedInstance, Nova, Witness,
@ -560,10 +560,8 @@ where
poseidon_config: &PoseidonConfig<C::ScalarField>, poseidon_config: &PoseidonConfig<C::ScalarField>,
U_i: CommittedInstance<C>, U_i: CommittedInstance<C>,
) -> Result<(C::ScalarField, C::ScalarField), Error> { ) -> Result<(C::ScalarField, C::ScalarField), Error> {
let (cmE_x_limbs, cmE_y_limbs): (Vec<C::ScalarField>, Vec<C::ScalarField>) =
point_to_nonnative_limbs::<C>(U_i.cmE)?;
let (cmW_x_limbs, cmW_y_limbs): (Vec<C::ScalarField>, Vec<C::ScalarField>) =
point_to_nonnative_limbs::<C>(U_i.cmW)?;
let (cmE_x_limbs, cmE_y_limbs) = nonnative_affine_to_field_elements(U_i.cmE)?;
let (cmW_x_limbs, cmW_y_limbs) = nonnative_affine_to_field_elements(U_i.cmW)?;
let transcript = &mut PoseidonTranscript::<C>::new(poseidon_config); let transcript = &mut PoseidonTranscript::<C>::new(poseidon_config);
// compute the KZG challenges, which are computed in-circuit and checked that it matches // compute the KZG challenges, which are computed in-circuit and checked that it matches
@ -586,16 +584,10 @@ where
let mut transcript = let mut transcript =
PoseidonTranscriptVar::<CF1<C>>::new(cs.clone(), &poseidon_config.clone()); PoseidonTranscriptVar::<CF1<C>>::new(cs.clone(), &poseidon_config.clone());
let cmW_x_limbs = U_i.cmW.x.to_constraint_field()?;
let cmW_y_limbs = U_i.cmW.y.to_constraint_field()?;
transcript.absorb_vec(&cmW_x_limbs)?;
transcript.absorb_vec(&cmW_y_limbs)?;
transcript.absorb_vec(&U_i.cmW.to_constraint_field()?[..])?;
let challenge_W = transcript.get_challenge()?; let challenge_W = transcript.get_challenge()?;
let cmE_x_limbs = U_i.cmE.x.to_constraint_field()?;
let cmE_y_limbs = U_i.cmE.y.to_constraint_field()?;
transcript.absorb_vec(&cmE_x_limbs)?;
transcript.absorb_vec(&cmE_y_limbs)?;
transcript.absorb_vec(&U_i.cmE.to_constraint_field()?[..])?;
let challenge_E = transcript.get_challenge()?; let challenge_E = transcript.get_challenge()?;
Ok((challenge_W, challenge_E)) Ok((challenge_W, challenge_E))

+ 24
- 33
folding-schemes/src/folding/nova/mod.rs

@ -16,7 +16,7 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS};
use crate::commitment::CommitmentScheme; use crate::commitment::CommitmentScheme;
use crate::folding::circuits::nonnative::{ use crate::folding::circuits::nonnative::{
nonnative_field_to_field_elements, point_to_nonnative_limbs,
nonnative_affine_to_field_elements, nonnative_field_to_field_elements,
}; };
use crate::frontend::FCircuit; use crate::frontend::FCircuit;
use crate::utils::vec::is_zero_vec; use crate::utils::vec::is_zero_vec;
@ -73,8 +73,8 @@ where
z_0: Vec<C::ScalarField>, z_0: Vec<C::ScalarField>,
z_i: Vec<C::ScalarField>, z_i: Vec<C::ScalarField>,
) -> Result<C::ScalarField, Error> { ) -> Result<C::ScalarField, Error> {
let (cmE_x, cmE_y) = point_to_nonnative_limbs::<C>(self.cmE)?;
let (cmW_x, cmW_y) = point_to_nonnative_limbs::<C>(self.cmW)?;
let (cmE_x, cmE_y) = nonnative_affine_to_field_elements::<C>(self.cmE)?;
let (cmW_x, cmW_y) = nonnative_affine_to_field_elements::<C>(self.cmW)?;
CRH::<C::ScalarField>::evaluate( CRH::<C::ScalarField>::evaluate(
poseidon_config, poseidon_config,
@ -342,7 +342,7 @@ where
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>;
if self.i > C1::ScalarField::from_le_bytes_mod_order(&std::usize::MAX.to_le_bytes()) {
if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) {
return Err(Error::MaxStep); return Err(Error::MaxStep);
} }
let mut i_bytes: [u8; 8] = [0; 8]; let mut i_bytes: [u8; 8] = [0; 8];
@ -392,22 +392,18 @@ where
i_usize: Some(0), i_usize: Some(0),
z_0: Some(self.z_0.clone()), // = z_i z_0: Some(self.z_0.clone()), // = z_i
z_i: Some(self.z_i.clone()), z_i: Some(self.z_i.clone()),
u_i: Some(self.u_i.clone()), // = dummy
u_i_cmW: Some(self.u_i.cmW), // = dummy
U_i: Some(self.U_i.clone()), // = dummy U_i: Some(self.U_i.clone()), // = dummy
U_i1: Some(U_i1.clone()),
r_nonnat: Some(r_Fq),
U_i1_cmE: Some(U_i1.cmE),
U_i1_cmW: Some(U_i1.cmW),
cmT: Some(cmT), cmT: Some(cmT),
F: self.F.clone(), F: self.F.clone(),
x: Some(u_i1_x), x: Some(u_i1_x),
cf1_u_i: None,
cf2_u_i: None,
cf1_u_i_cmW: None,
cf2_u_i_cmW: None,
cf_U_i: None, cf_U_i: None,
cf1_U_i1: None,
cf_U_i1: None,
cf1_cmT: None, cf1_cmT: None,
cf2_cmT: None, cf2_cmT: None,
cf1_r_nonnat: None,
cf2_r_nonnat: None,
cf_x: Some(cf_u_i1_x), cf_x: Some(cf_u_i1_x),
}; };
@ -451,15 +447,14 @@ where
}; };
// fold self.cf_U_i + cfW_U -> folded running with cfW // 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,
)?;
let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, _) = 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 // 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) =
let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) =
self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?; self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?; cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?;
@ -471,23 +466,19 @@ where
i_usize: Some(i_usize), i_usize: Some(i_usize),
z_0: Some(self.z_0.clone()), z_0: Some(self.z_0.clone()),
z_i: Some(self.z_i.clone()), z_i: Some(self.z_i.clone()),
u_i: Some(self.u_i.clone()),
u_i_cmW: Some(self.u_i.cmW),
U_i: Some(self.U_i.clone()), U_i: Some(self.U_i.clone()),
U_i1: Some(U_i1.clone()),
r_nonnat: Some(r_Fq),
U_i1_cmE: Some(U_i1.cmE),
U_i1_cmW: Some(U_i1.cmW),
cmT: Some(cmT), cmT: Some(cmT),
F: self.F.clone(), F: self.F.clone(),
x: Some(u_i1_x), x: Some(u_i1_x),
// cyclefold values // cyclefold values
cf1_u_i: Some(cfW_u_i.clone()),
cf2_u_i: Some(cfE_u_i.clone()),
cf1_u_i_cmW: Some(cfW_u_i.cmW),
cf2_u_i_cmW: Some(cfE_u_i.cmW),
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()),
cf1_cmT: Some(cfW_cmT), cf1_cmT: Some(cfW_cmT),
cf2_cmT: Some(cf_cmT), cf2_cmT: Some(cf_cmT),
cf1_r_nonnat: Some(cfW_r1_Fq),
cf2_r_nonnat: Some(cf_r2_Fq),
cf_x: Some(cf_u_i1_x), cf_x: Some(cf_u_i1_x),
}; };
@ -523,11 +514,11 @@ where
// set values for next iteration // set values for next iteration
self.i += C1::ScalarField::one(); self.i += C1::ScalarField::one();
self.z_i = z_i1.clone();
self.z_i = z_i1;
self.w_i = Witness::<C1>::new(w_i1, self.r1cs.A.n_rows); self.w_i = Witness::<C1>::new(w_i1, self.r1cs.A.n_rows);
self.u_i = self.w_i.commit::<CS1>(&self.cs_params, x_i1)?; self.u_i = self.w_i.commit::<CS1>(&self.cs_params, x_i1)?;
self.W_i = W_i1.clone();
self.U_i = U_i1.clone();
self.W_i = W_i1;
self.U_i = U_i1;
#[cfg(test)] #[cfg(test)]
{ {

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

@ -203,14 +203,13 @@ pub mod tests {
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig; use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
use ark_ff::{BigInteger, PrimeField}; use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{Fr, Projective}; use ark_pallas::{Fr, Projective};
use ark_std::{ops::Mul, UniformRand, Zero};
use ark_std::{ops::Mul, UniformRand};
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z}; use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
use crate::folding::nova::circuits::ChallengeGadget; use crate::folding::nova::circuits::ChallengeGadget;
use crate::folding::nova::traits::NovaR1CS; use crate::folding::nova::traits::NovaR1CS;
use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript}; use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript};
use crate::utils::vec::vec_scalar_mul;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(crate) fn prepare_simple_fold_inputs<C>() -> ( pub(crate) fn prepare_simple_fold_inputs<C>() -> (

Loading…
Cancel
Save