Browse Source

Implement CycleFold in Nova's IVC (#45)

* Implement CycleFold in Nova's IVC

(CycleFold: https://eprint.iacr.org/2023/1192)

* CycleFoldChallengeGadget: add points coordinates as bytes

* Apply PR review suggestions
update-nifs-interface
arnaucube 11 months ago
committed by GitHub
parent
commit
b4f42e7023
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1320 additions and 559 deletions
  1. +2
    -2
      Cargo.toml
  2. +1
    -1
      src/ccs/mod.rs
  3. +8
    -8
      src/ccs/r1cs.rs
  4. +5
    -3
      src/constants.rs
  5. +0
    -65
      src/folding/circuits/cyclefold.rs
  6. +0
    -1
      src/folding/circuits/mod.rs
  7. +5
    -7
      src/folding/circuits/nonnative.rs
  8. +342
    -296
      src/folding/nova/circuits.rs
  9. +566
    -0
      src/folding/nova/cyclefold.rs
  10. +231
    -74
      src/folding/nova/ivc.rs
  11. +8
    -5
      src/folding/nova/mod.rs
  12. +118
    -82
      src/folding/nova/nifs.rs
  13. +9
    -0
      src/frontend/arkworks/mod.rs
  14. +5
    -1
      src/lib.rs
  15. +3
    -1
      src/pedersen.rs
  16. +4
    -6
      src/transcript/poseidon.rs
  17. +13
    -7
      src/utils/vec.rs

+ 2
- 2
Cargo.toml

@ -11,6 +11,7 @@ ark-std = "^0.4.0"
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = ["r1cs", "sponge", "crh"] }
ark-relations = { version = "^0.4.0", default-features = false }
ark-r1cs-std = { default-features = false } # use latest version from the patch
ark-serialize = "^0.4.0"
ark-circom = { git = "https://github.com/gakonst/ark-circom.git" }
thiserror = "1.0"
rayon = "1.7.0"
@ -18,12 +19,11 @@ num-bigint = "0.4"
color-eyre = "=0.6.2"
# tmp imports for espresso's sumcheck
ark-serialize = "^0.4.0"
espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", package="subroutines"}
[dev-dependencies]
ark-pallas = {version="0.4.0", features=["r1cs"]}
ark-vesta = {version="0.4.0"}
ark-vesta = {version="0.4.0", features=["r1cs"]}
ark-bn254 = "0.4.0"
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] }
tracing-subscriber = { version = "0.2" }

+ 1
- 1
src/ccs/mod.rs

@ -51,7 +51,7 @@ impl CCS {
// complete the hadamard chain
let mut hadamard_result = vec![C::ScalarField::one(); self.m];
for M_j in vec_M_j.into_iter() {
hadamard_result = hadamard(&hadamard_result, &mat_vec_mul_sparse(M_j, z))?;
hadamard_result = hadamard(&hadamard_result, &mat_vec_mul_sparse(M_j, z)?)?;
}
// multiply by the coefficient of this step

+ 8
- 8
src/ccs/r1cs.rs

@ -19,9 +19,9 @@ impl R1CS {
/// check that a R1CS structure is satisfied by a z vector. Only for testing.
pub fn check_relation(&self, z: &[F]) -> Result<(), Error> {
let Az = mat_vec_mul_sparse(&self.A, z);
let Bz = mat_vec_mul_sparse(&self.B, z);
let Cz = mat_vec_mul_sparse(&self.C, z);
let Az = mat_vec_mul_sparse(&self.A, z)?;
let Bz = mat_vec_mul_sparse(&self.B, z)?;
let Cz = mat_vec_mul_sparse(&self.C, z)?;
let AzBz = hadamard(&Az, &Bz)?;
if AzBz != Cz {
return Err(Error::NotSatisfied);
@ -57,12 +57,12 @@ pub struct RelaxedR1CS {
impl<F: PrimeField> RelaxedR1CS<F> {
/// check that a RelaxedR1CS structure is satisfied by a z vector. Only for testing.
pub fn check_relation(&self, z: &[F]) -> Result<(), Error> {
let Az = mat_vec_mul_sparse(&self.A, z);
let Bz = mat_vec_mul_sparse(&self.B, z);
let Cz = mat_vec_mul_sparse(&self.C, z);
let Az = mat_vec_mul_sparse(&self.A, z)?;
let Bz = mat_vec_mul_sparse(&self.B, z)?;
let Cz = mat_vec_mul_sparse(&self.C, z)?;
let uCz = vec_scalar_mul(&Cz, &self.u);
let uCzE = vec_add(&uCz, &self.E);
let AzBz = hadamard(&Az, &Bz);
let uCzE = vec_add(&uCz, &self.E)?;
let AzBz = hadamard(&Az, &Bz)?;
if AzBz != uCzE {
return Err(Error::NotSatisfied);
}

+ 5
- 3
src/constants.rs

@ -1,3 +1,5 @@
// used for committed instances hash, so when going to the other curve of the cycle it does not
// overflow the scalar field
pub const N_BITS_HASH: usize = 250;
// used for the RO challenges.
// From [Srinath Setty](research.microsoft.com/en-us/people/srinath/): In Nova, soundness error ≤
// 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this case,
// we keep the size of S close to 2^128.
pub const N_BITS_RO: usize = 128;

+ 0
- 65
src/folding/circuits/cyclefold.rs

@ -1,65 +0,0 @@
/// Implements the C_{EC} circuit described in [CycleFold paper](https://eprint.iacr.org/2023/1192.pdf)
use ark_ec::CurveGroup;
use ark_r1cs_std::{boolean::Boolean, prelude::CurveVar};
use ark_relations::r1cs::SynthesisError;
use core::marker::PhantomData;
use super::CF;
/// ECRLC implements gadget that checks the Elliptic Curve points RandomLinearCombination described
/// in [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
#[derive(Debug)]
pub struct ECRLC<C: CurveGroup, GC: CurveVar<C, CF<C>>> {
_c: PhantomData<C>,
_gc: PhantomData<GC>,
}
impl<C: CurveGroup, GC: CurveVar<C, CF<C>>> ECRLC<C, GC> {
pub fn check(
// get r in bits format, so it can be reused across many instances of ECRLC gadget,
// reducing the number of constraints needed
r_bits: Vec<Boolean<CF<C>>>,
p1: GC,
p2: GC,
p3: GC,
) -> Result<Boolean<CF<C>>, SynthesisError> {
p3.is_eq(&(p1 + p2.scalar_mul_le(r_bits.iter())?))
}
}
#[cfg(test)]
mod tests {
use super::*;
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
use std::ops::Mul;
/// Let Curve1=pallas and Curve2=vesta. Here our constraints system will work over Curve2::Fr =
/// vesta::Fr (=pallas::Fq), thus our points are P_i \in Curve1 (=pasta).
#[test]
fn test_ecrlc_check() {
let mut rng = ark_std::test_rng();
let r = Fr::rand(&mut rng);
let p1 = Projective::rand(&mut rng);
let p2 = Projective::rand(&mut rng);
let p3 = p1 + p2.mul(r);
let cs = ConstraintSystem::<Fq>::new_ref(); // CS over Curve2::Fr = Curve1::Fq
// prepare circuit inputs
let rbitsVar: Vec<Boolean<Fq>> =
Vec::new_witness(cs.clone(), || Ok(r.into_bigint().to_bits_le())).unwrap();
let p1Var = GVar::new_witness(cs.clone(), || Ok(p1)).unwrap();
let p2Var = GVar::new_witness(cs.clone(), || Ok(p2)).unwrap();
let p3Var = GVar::new_witness(cs.clone(), || Ok(p3)).unwrap();
// check ECRLC circuit
let check_pass = ECRLC::<Projective, GVar>::check(rbitsVar, p1Var, p2Var, p3Var).unwrap();
check_pass.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
assert!(cs.is_satisfied().unwrap());
}
}

+ 0
- 1
src/folding/circuits/mod.rs

@ -2,7 +2,6 @@
use ark_ec::CurveGroup;
use ark_ff::Field;
pub mod cyclefold;
pub mod nonnative;
// CF represents the constraints field

+ 5
- 7
src/folding/circuits/nonnative.rs

@ -33,11 +33,9 @@ where
let cs = cs.into();
let affine = val.borrow().into_affine();
let xy_obj = &affine.xy();
let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
if xy_obj.is_some() {
xy = xy_obj.unwrap();
}
let zero_point = (&C::BaseField::zero(), &C::BaseField::one());
let xy = affine.xy().unwrap_or(zero_point);
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(xy.0),
@ -56,8 +54,8 @@ where
}
}
/// point_to_nonnative_limbs is used to return (outside the circuit) the limbs representation that
/// matches the one used in-circuit.
/// point_to_nonnative_limbs is used to compute (outside the circuit) the limbs representation of a
/// point that matches the one used in-circuit.
#[allow(clippy::type_complexity)]
pub fn point_to_nonnative_limbs<C: CurveGroup>(
p: C,

+ 342
- 296
src/folding/nova/circuits.rs

@ -1,40 +1,49 @@
/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits
use ark_crypto_primitives::crh::{
poseidon::constraints::{CRHGadget, CRHParametersVar},
poseidon::CRH,
CRHScheme, CRHSchemeGadget,
CRHSchemeGadget,
};
use ark_crypto_primitives::sponge::{
constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,
};
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{Field, PrimeField};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, FieldVar},
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
groups::GroupOpsBounds,
prelude::CurveVar,
ToBitsGadget, ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::fmt::Debug;
use ark_std::Zero;
use ark_std::{One, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::CommittedInstance;
use crate::folding::circuits::{
cyclefold::ECRLC,
nonnative::{point_to_nonnative_limbs, NonNativeAffineVar},
use super::{
cyclefold::{
CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN,
},
CommittedInstance,
};
use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar};
/// 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.
pub type CF1<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
/// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq,
/// where E2 is the auxiliary curve (from [CycleFold](https://eprint.iacr.org/2023/1192.pdf)
/// approach) where we check the folding of the commitments.
/// approach) where we check the folding of the commitments (elliptic curve points).
pub type CF2<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova
/// constraints field (E1::Fr, where E1 is the main curve).
/// 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.
#[derive(Debug, Clone)]
pub struct CommittedInstanceVar<C: CurveGroup> {
u: FpVar<C::ScalarField>,
@ -108,43 +117,6 @@ where
}
}
/// CommittedInstanceCycleFoldVar represents the commitments to E and W from the CommittedInstance
/// on the E2, which are folded on the auxiliary curve constraints field (E2::Fr = E1::Fq).
pub struct CommittedInstanceCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
_c: PhantomData<C>,
cmE: GC,
cmW: GC,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceCycleFoldVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
Ok(Self {
_c: PhantomData,
cmE,
cmW,
})
})
}
}
/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier
/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are
/// delegated to the NIFSCycleFoldGadget.
@ -180,96 +152,12 @@ where
}
}
/// NIFSCycleFoldGadget performs the Nova NIFS.V elliptic curve points relation checks in the other
/// curve following [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
pub struct NIFSCycleFoldGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a
/// rust-native and a in-circuit compatible versions.
pub struct ChallengeGadget<C: CurveGroup> {
_c: PhantomData<C>,
_gc: PhantomData<GC>,
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSCycleFoldGadget<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
pub fn verify(
r_bits: Vec<Boolean<CF2<C>>>,
cmT: GC,
ci1: CommittedInstanceCycleFoldVar<C, GC>,
ci2: CommittedInstanceCycleFoldVar<C, GC>,
ci3: CommittedInstanceCycleFoldVar<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 = ECRLC::<C, GC>::check(r_bits, ci1.cmW, ci2.cmW, ci3.cmW)?;
first_check.and(&second_check)
}
}
/// FCircuit defines the trait of the circuit of the F function, which is the one being executed
/// inside the agmented F' function.
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
/// returns a new FCircuit instance
fn new() -> Self;
/// computes the next state values in place, assigning z_{i+1} into z_i, and
/// computing the new z_i
fn step_native(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to compute the next state.
self,
z_i: Vec<F>,
) -> Vec<F>;
/// generates the constraints for the step of F for the given z_i
fn generate_step_constraints(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to generate the constraints.
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError>;
}
/// AugmentedFCircuit implements the F' circuit (augmented F) defined in
/// [Nova](https://eprint.iacr.org/2021/370.pdf).
#[derive(Debug, Clone)]
pub struct AugmentedFCircuit<C: CurveGroup, FC: FCircuit<CF1<C>>> {
pub poseidon_config: PoseidonConfig<CF1<C>>,
pub i: Option<CF1<C>>,
pub z_0: Option<Vec<C::ScalarField>>,
pub z_i: Option<Vec<C::ScalarField>>,
pub u_i: Option<CommittedInstance<C>>,
pub U_i: Option<CommittedInstance<C>>,
pub U_i1: Option<CommittedInstance<C>>,
pub cmT: Option<C>,
pub F: FC, // F circuit
pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x)
}
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
pub fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self {
Self {
poseidon_config: poseidon_config.clone(),
i: None,
z_0: None,
z_i: None,
u_i: None,
U_i: None,
U_i1: None,
cmT: None,
F: F_circuit,
x: None,
}
}
}
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC>
impl<C: CurveGroup> ChallengeGadget<C>
where
C: CurveGroup,
<C as CurveGroup>::BaseField: PrimeField,
@ -280,13 +168,14 @@ where
u_i: CommittedInstance<C>,
U_i: CommittedInstance<C>,
cmT: C,
) -> Result<C::ScalarField, 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 mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
let input = vec![
vec![u_i.u],
u_i.x.clone(),
@ -304,16 +193,22 @@ where
cmT_y,
]
.concat();
Ok(CRH::<C::ScalarField>::evaluate(poseidon_config, input).unwrap())
sponge.absorb(&input);
let bits = sponge.squeeze_bits(N_BITS_RO);
Ok(bits)
}
pub fn get_challenge(
crh_params: &CRHParametersVar<C::ScalarField>,
// compatible with the native get_challenge_native
pub fn get_challenge_gadget(
cs: ConstraintSystemRef<C::ScalarField>,
poseidon_config: &PoseidonConfig<C::ScalarField>,
u_i: CommittedInstanceVar<C>,
U_i: CommittedInstanceVar<C>,
cmT: NonNativeAffineVar<C::ScalarField>,
) -> Result<FpVar<C::ScalarField>, SynthesisError> {
let input = vec![
) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> {
let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config);
let input: Vec<FpVar<C::ScalarField>> = vec![
vec![u_i.u.clone()],
u_i.x.clone(),
u_i.cmE.x,
@ -330,55 +225,148 @@ where
cmT.y,
]
.concat();
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)
sponge.absorb(&input)?;
let bits = sponge.squeeze_bits(N_BITS_RO)?;
Ok(bits)
}
}
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> ConstraintSynthesizer<CF1<C>> for AugmentedFCircuit<C, FC>
/// FCircuit defines the trait of the circuit of the F function, which is the one being executed
/// inside the agmented F' function.
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
/// returns a new FCircuit instance
fn new() -> Self;
/// computes the next state values in place, assigning z_{i+1} into z_i, and
/// computing the new z_i
fn step_native(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to compute the next state.
self,
z_i: Vec<F>,
) -> Vec<F>;
/// generates the constraints for the step of F for the given z_i
fn generate_step_constraints(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to generate the constraints.
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError>;
}
/// AugmentedFCircuit implements the F' circuit (augmented F) defined in
/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
#[derive(Debug, Clone)]
pub struct AugmentedFCircuit<
C1: CurveGroup,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<CF1<C1>>,
> where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub i: Option<CF1<C1>>,
pub z_0: Option<Vec<C1::ScalarField>>,
pub z_i: Option<Vec<C1::ScalarField>>,
pub u_i: Option<CommittedInstance<C1>>,
pub U_i: Option<CommittedInstance<C1>>,
pub U_i1: Option<CommittedInstance<C1>>,
pub cmT: Option<C1>,
pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x)
// 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>,
}
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
AugmentedFCircuit<C1, C2, GC2, FC>
where
C: CurveGroup,
<C as CurveGroup>::BaseField: PrimeField,
<C as Group>::ScalarField: Absorb,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C>>) -> Result<(), SynthesisError> {
let i =
FpVar::<CF1<C>>::new_witness(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C>::zero)))?;
let z_0 = Vec::<FpVar<CF1<C>>>::new_witness(cs.clone(), || {
Ok(self.z_0.unwrap_or_else(|| vec![CF1::<C>::zero()]))
pub fn empty(poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC) -> Self {
Self {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
i: None,
z_0: None,
z_i: None,
u_i: None,
U_i: None,
U_i1: None,
cmT: None,
F: F_circuit,
x: None,
// cyclefold values
cf_u_i: None,
cf_U_i: None,
cf_U_i1: None,
cf_cmT: None,
cf_r_nonnat: None,
}
}
}
impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
where
C1: CurveGroup,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<CF1<C1>>,
<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 GC2: GroupOpsBounds<'a, C2, GC2>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
})?;
let z_i = Vec::<FpVar<CF1<C>>>::new_witness(cs.clone(), || {
Ok(self.z_i.unwrap_or_else(|| vec![CF1::<C>::zero()]))
let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let u_dummy_native = CommittedInstance::<C>::dummy(1);
let u_dummy =
CommittedInstanceVar::<C>::new_witness(cs.clone(), || Ok(u_dummy_native.clone()))?;
let u_i = CommittedInstanceVar::<C>::new_witness(cs.clone(), || {
Ok(self.u_i.unwrap_or_else(|| u_dummy_native.clone()))
let u_dummy_native = CommittedInstance::<C1>::dummy(1);
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
})?;
let U_i = CommittedInstanceVar::<C>::new_witness(cs.clone(), || {
Ok(self.U_i.unwrap_or_else(|| u_dummy_native.clone()))
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
})?;
let U_i1 = CommittedInstanceVar::<C>::new_witness(cs.clone(), || {
Ok(self.U_i1.unwrap_or_else(|| u_dummy_native.clone()))
let U_i1 = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
})?;
let cmT =
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C::zero)))?;
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
let x =
FpVar::<CF1<C>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C>::zero)))?;
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C1>::zero)))?;
let crh_params =
CRHParametersVar::<C::ScalarField>::new_constant(cs.clone(), self.poseidon_config)?;
let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant(
cs.clone(),
self.poseidon_config.clone(),
)?;
// get z_{i+1} from the F circuit
let z_i1 = self.F.generate_step_constraints(cs.clone(), z_i.clone())?;
let zero = FpVar::<CF1<C>>::new_constant(cs.clone(), CF1::<C>::zero())?;
let is_basecase = i.is_eq(&zero)?;
let zero = FpVar::<CF1<C1>>::new_constant(cs.clone(), CF1::<C1>::zero())?;
let is_not_basecase = i.is_neq(&zero)?;
// 1. h_{i+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())?;
@ -387,41 +375,107 @@ where
(u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
// 2. u_i.cmE==cm(0), u_i.u==1
(u_i.cmE.x.is_eq(&u_dummy.cmE.x)?)
.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.cmE.y.is_eq(&u_dummy.cmE.y)?)
.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(),
C1::BaseField::zero(),
)?
.to_constraint_field()?;
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(),
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.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)?;
// 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}.
// compute r = H(u_i, U_i, cmT)
let r = Self::get_challenge(&crh_params, u_i.clone(), U_i.clone(), cmT)?;
let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
u_i.clone(),
U_i.clone(),
cmT.clone(),
)?;
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
// be done on the other curve.
let nifs_check = NIFSGadget::<C>::verify(r, u_i, 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)?;
// 4. (base case) u_{i+1}.X == H(1, z_0, F(z_0)=F(z_i)=z_i1, U_i) (with U_i being dummy)
let u_i1_x_basecase = U_i.hash(
// 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(
&crh_params,
FpVar::<CF1<C>>::one(),
i + FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
// 4. (non-base case). 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.hash(
&crh_params,
i + FpVar::<CF1<C>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
u_i1_x.enforce_equal(&x)?;
// CycleFold part
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()))
})?;
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 cf_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.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))
})?;
// 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();
// if i==0: check x==u_{i+1}.x_basecase
u_i1_x_basecase.conditional_enforce_equal(&x, &is_basecase)?;
// else: check x==u_{i+1}.x
u_i1_x.conditional_enforce_equal(&x, &is_not_basecase)?;
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)?;
// cf_r_bits is denoted by rho* in the paper
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
cf_u_i.clone(),
cf_U_i.clone(),
cf_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)?;
// 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)?;
// check the fold of all the parameteres of the CycleFold instances, where the elliptic
// 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,
cf_U_i,
cf_u_i,
cf_U_i1,
)?;
v.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
Ok(())
}
@ -431,19 +485,21 @@ where
pub mod tests {
use super::*;
use ark_ff::BigInteger;
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_pallas::{Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::{ConstraintLayer, ConstraintSystem, TracingMode};
use ark_std::One;
use ark_std::UniformRand;
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use tracing_subscriber::layer::SubscriberExt;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::folding::nova::{nifs::NIFS, traits::NovaR1CS, Witness};
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
use crate::folding::nova::{
ivc::get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness,
};
use crate::frontend::arkworks::{extract_r1cs, extract_z};
use crate::pedersen::Pedersen;
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
use crate::transcript::Transcript;
use crate::transcript::poseidon::tests::poseidon_test_config;
#[derive(Clone, Copy, Debug)]
/// TestFCircuit is a variation of `x^3 + x + 5 = y` (as in
@ -488,49 +544,14 @@ pub mod tests {
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
assert_eq!(ciVar.u.value().unwrap(), ci.u);
assert_eq!(ciVar.x.value().unwrap(), ci.x);
// check the instantiation of the CycleFold side:
let cs = ConstraintSystem::<Fq>::new_ref();
let ciVar =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci.clone())
})
.unwrap();
assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
// the values cmE and cmW are checked in the CycleFold's circuit
// CommittedInstanceInCycleFoldVar in
// nova::cyclefold::tests::test_committed_instance_cyclefold_var
}
#[test]
fn test_nifs_gadget() {
let r1cs = get_test_r1cs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
let (w1, x1) = r1cs.split_z(&z1);
let (w2, x2) = r1cs.split_z(&z2);
let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
let mut rng = ark_std::test_rng();
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
// compute committed instances
let ci1 = w1.commit(&pedersen_params, x1.clone()).unwrap();
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
// NIFS.P
let (T, cmT) =
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
// get challenge from transcript, since we're in a test skip absorbing values into
// transcript
let poseidon_config = poseidon_test_config::<Fr>();
let mut tr = PoseidonTranscript::<Projective>::new(&poseidon_config);
let r_bits = tr.get_challenge_nbits(128);
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
let (_, ci3) =
NIFS::<Projective>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, _, r_Fr) = prepare_simple_fold_inputs();
let ci3_verifier = NIFS::<Projective>::verify(r_Fr, &ci1, &ci2, &cmT);
assert_eq!(ci3_verifier, ci3);
@ -557,36 +578,6 @@ pub mod tests {
.unwrap();
nifs_check.enforce_equal(&Boolean::<Fr>::TRUE).unwrap();
assert!(cs.is_satisfied().unwrap());
// cs_CC is the Constraint System on the Curve Cycle auxiliary curve constraints field
// (E2::Fr)
let cs_CC = ConstraintSystem::<Fq>::new_ref();
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs_CC.clone(), || Ok(r_bits)).unwrap();
let cmTVar = GVar::new_witness(cs_CC.clone(), || Ok(cmT)).unwrap();
let ci1Var =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci1.clone())
})
.unwrap();
let ci2Var =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci2.clone())
})
.unwrap();
let ci3Var =
CommittedInstanceCycleFoldVar::<Projective, GVar>::new_witness(cs_CC.clone(), || {
Ok(ci3.clone())
})
.unwrap();
let nifs_cf_check = NIFSCycleFoldGadget::<Projective, GVar>::verify(
r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var,
)
.unwrap();
nifs_cf_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
assert!(cs_CC.is_satisfied().unwrap());
}
#[test]
@ -648,13 +639,14 @@ pub mod tests {
let cmT = Projective::rand(&mut rng);
// compute the challenge natively
let r = AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::get_challenge_native(
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
&poseidon_config,
u_i.clone(),
U_i.clone(),
cmT,
)
.unwrap();
let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let u_iVar =
@ -665,11 +657,10 @@ pub mod tests {
.unwrap();
let cmTVar = NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
// compute the challenge in-circuit
let rVar = AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::get_challenge(
&crh_params,
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
cs.clone(),
&poseidon_config,
u_iVar,
U_iVar,
cmTVar,
@ -678,7 +669,9 @@ pub mod tests {
assert!(cs.is_satisfied().unwrap());
// check that the natively computed and in-circuit computed hashes match
let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
assert_eq!(rVar.value().unwrap(), r);
assert_eq!(r_bitsVar.value().unwrap(), r_bits);
}
#[test]
@ -699,7 +692,10 @@ pub mod tests {
// prepare the circuit to obtain its R1CS
let F_circuit = TestFCircuit::<Fr>::new();
let mut augmented_F_circuit =
AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::empty(&poseidon_config, F_circuit);
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>>::empty(
&poseidon_config,
F_circuit,
);
augmented_F_circuit
.generate_constraints(cs.clone())
.unwrap();
@ -751,18 +747,26 @@ pub mod tests {
.unwrap();
// base case
augmented_F_circuit = AugmentedFCircuit::<Projective, TestFCircuit<Fr>> {
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),
};
augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<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();
@ -780,13 +784,14 @@ pub mod tests {
.unwrap();
// get challenge r
let r_Fr = AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::get_challenge_native(
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>::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT)
@ -800,18 +805,59 @@ pub mod tests {
.hash(&poseidon_config, i + Fr::one(), z_0.clone(), z_i1.clone())
.unwrap();
augmented_F_circuit = AugmentedFCircuit::<Projective, TestFCircuit<Fr>> {
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),
// 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>::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, TestFCircuit<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();
@ -821,7 +867,7 @@ pub mod tests {
.unwrap();
let is_satisfied = cs.is_satisfied().unwrap();
if !is_satisfied {
println!("{:?}", cs.which_is_unsatisfied());
dbg!(cs.which_is_unsatisfied().unwrap());
}
assert!(is_satisfied);

+ 566
- 0
src/folding/nova/cyclefold.rs

@ -0,0 +1,566 @@
/// contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits
use ark_crypto_primitives::sponge::{
constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,
};
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
bits::uint8::UInt8,
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
groups::GroupOpsBounds,
prelude::CurveVar,
ToBytesGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_serialize::CanonicalSerialize;
use ark_std::fmt::Debug;
use ark_std::Zero;
use core::{borrow::Borrow, marker::PhantomData};
use super::circuits::CF2;
use super::CommittedInstance;
use crate::constants::N_BITS_RO;
use crate::Error;
// publi inputs length for the CycleFoldCircuit, |[u_i, U_i, U_{i+1}]|
pub const CF_IO_LEN: usize = 12;
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova
/// circuit.
#[derive(Debug, Clone)]
pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
_c: PhantomData<C>,
pub cmE: GC,
pub u: NonNativeFieldVar<C::ScalarField, CF2<C>>,
pub cmW: GC,
pub x: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<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>,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
let u = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
cs.clone(),
|| Ok(val.borrow().u),
mode,
)?;
let x = Vec::<NonNativeFieldVar<C::ScalarField, CF2<C>>>::new_variable(
cs.clone(),
|| Ok(val.borrow().x.clone()),
mode,
)?;
Ok(Self {
_c: PhantomData,
cmE,
u,
cmW,
x,
})
})
}
}
/// CommittedInstanceInCycleFoldVar represents the Nova CommittedInstance in the CycleFold circuit,
/// where the commitments to E and W (cmW and cmW) from the CommittedInstance on the E2,
/// represented as native points, which are folded on the auxiliary curve constraints field (E2::Fr
/// = E1::Fq).
#[derive(Debug, Clone)]
pub struct CommittedInstanceInCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
_c: PhantomData<C>,
pub cmE: GC,
pub cmW: GC,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceInCycleFoldVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
Ok(Self {
_c: PhantomData,
cmE,
cmW,
})
})
}
}
/// 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,
/// 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.
pub struct NIFSFullGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
_c: PhantomData<C>,
_gc: PhantomData<GC>,
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<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>>>,
r_nonnat: NonNativeFieldVar<C::ScalarField, CF2<C>>,
cmT: GC,
// ci1 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
ci1: CycleFoldCommittedInstanceVar<C, GC>,
ci2: 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)
}
}
/// ChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a
/// rust-native and a in-circuit compatible versions.
pub struct CycleFoldChallengeGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
_c: PhantomData<C>, // Nova's Curve2, the one used for the CycleFold circuit
_gc: PhantomData<GC>,
}
impl<C, GC> CycleFoldChallengeGadget<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
<C as CurveGroup>::BaseField: PrimeField,
<C as CurveGroup>::BaseField: Absorb,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
pub fn get_challenge_native(
poseidon_config: &PoseidonConfig<C::BaseField>,
u_i: CommittedInstance<C>,
U_i: CommittedInstance<C>,
cmT: C,
) -> Result<Vec<bool>, Error> {
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 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();
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> = [
u_i_cmE_bytes,
u_i_u_bytes,
u_i_cmW_bytes,
u_i_x_bytes,
U_i_cmE_bytes,
U_i_u_bytes,
U_i_cmW_bytes,
U_i_x_bytes,
cmT_bytes,
]
.concat();
sponge.absorb(&input);
let bits = sponge.squeeze_bits(N_BITS_RO);
Ok(bits)
}
// compatible with the native get_challenge_native
pub fn get_challenge_gadget(
cs: ConstraintSystemRef<C::BaseField>,
poseidon_config: &PoseidonConfig<C::BaseField>,
u_i: CycleFoldCommittedInstanceVar<C, GC>,
U_i: CycleFoldCommittedInstanceVar<C, GC>,
cmT: GC,
) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> {
let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config);
let u_i_x_bytes: Vec<UInt8<CF2<C>>> = u_i
.x
.iter()
.flat_map(|e| e.to_bytes().unwrap_or(vec![]))
.collect::<Vec<UInt8<CF2<C>>>>();
let U_i_x_bytes: Vec<UInt8<CF2<C>>> = U_i
.x
.iter()
.flat_map(|e| e.to_bytes().unwrap_or(vec![]))
.collect::<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.u.to_bytes()?,
U_i.cmW.to_bytes()?,
U_i_x_bytes,
cmT.to_bytes()?,
// 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
// keep track of this:
// https://github.com/privacy-scaling-explorations/folding-schemes/issues/44
]
.concat();
sponge.absorb(&input)?;
let bits = sponge.squeeze_bits(N_BITS_RO)?;
Ok(bits)
}
}
/// returns the bytes being compatible with the ark_r1cs_std `.to_bytes` approach
fn point_to_bytes<C: CurveGroup>(p: C) -> Vec<u8> {
let l = p.uncompressed_size();
let mut b = Vec::new();
p.serialize_uncompressed(&mut b).unwrap();
b[l - 1] = 0;
if p.is_zero() {
b[l / 2] = 1;
b[l - 1] = 1;
}
b
}
/// CycleFoldCircuit contains the constraints that check the correct fold of the committed
/// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve
/// (Curve1) points of u_i, U_i leading to U_{i+1}
#[derive(Debug, Clone)]
pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
pub _gc: PhantomData<GC>,
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 x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
pub fn empty() -> Self {
Self {
_gc: PhantomData,
r_bits: None,
cmT: None,
u_i: None,
U_i: None,
U_i1: None,
x: None,
}
}
}
impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<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>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> {
let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || {
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 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(), || {
Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
})?;
#[cfg(test)]
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)?;
// 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
// AugmentedFCircuit.
// TODO: Issue to keep track of this: https://github.com/privacy-scaling-explorations/folding-schemes/issues/44
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_ff::BigInteger;
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
use crate::transcript::poseidon::tests::poseidon_test_config;
#[test]
fn test_committed_instance_cyclefold_var() {
let mut rng = ark_std::test_rng();
let ci = CommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
x: vec![Fr::rand(&mut rng); 1],
};
// check the instantiation of the CycleFold side:
let cs = ConstraintSystem::<Fq>::new_ref();
let ciVar =
CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci.clone())
})
.unwrap();
assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
}
#[test]
fn test_nifs_gadget_cyclefold() {
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
// cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
// (E2::Fr)
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 nifs_cf_check = NIFSinCycleFoldGadget::<Projective, GVar>::verify(
r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var,
)
.unwrap();
nifs_cf_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!(cs.num_constraints());
}
#[test]
fn test_nifs_full_gadget() {
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs();
let cs = ConstraintSystem::<Fq>::new_ref();
let r_nonnatVar =
NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
let ci1Var =
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci1.clone())
})
.unwrap();
let ci2Var =
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci2.clone())
})
.unwrap();
let ci3Var =
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(ci3.clone())
})
.unwrap();
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let nifs_check = NIFSFullGadget::<Projective, GVar>::verify(
r_bitsVar,
r_nonnatVar,
cmTVar,
ci1Var,
ci2Var,
ci3Var,
)
.unwrap();
nifs_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!(cs.num_constraints());
}
#[test]
fn test_cyclefold_challenge_gadget() {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fq>();
let u_i = CommittedInstance::<Projective> {
cmE: Projective::zero(), // zero on purpose, so we test also the zero point case
u: Fr::zero(),
cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(CF_IO_LEN)
.collect(),
};
let U_i = CommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(CF_IO_LEN)
.collect(),
};
let cmT = Projective::rand(&mut rng);
// compute the challenge natively
let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
&poseidon_config,
u_i.clone(),
U_i.clone(),
cmT,
)
.unwrap();
let cs = ConstraintSystem::<Fq>::new_ref();
let u_iVar =
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(u_i.clone())
})
.unwrap();
let U_iVar =
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(U_i.clone())
})
.unwrap();
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
cs.clone(),
&poseidon_config,
u_iVar,
U_iVar,
cmTVar,
)
.unwrap();
assert!(cs.is_satisfied().unwrap());
// check that the natively computed and in-circuit computed hashes match
let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
let r = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
assert_eq!(rVar.value().unwrap(), r);
assert_eq!(r_bitsVar.value().unwrap(), r_bits);
}
}

+ 231
- 74
src/folding/nova/ivc.rs

@ -1,32 +1,44 @@
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_relations::r1cs::ConstraintSynthesizer;
use ark_relations::r1cs::ConstraintSystem;
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
use ark_std::rand::Rng;
use ark_std::{One, Zero};
use core::marker::PhantomData;
use super::circuits::{AugmentedFCircuit, FCircuit};
use super::{
circuits::{AugmentedFCircuit, ChallengeGadget, FCircuit, CF2},
cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit},
};
use super::{nifs::NIFS, traits::NovaR1CS, CommittedInstance, Witness};
use crate::ccs::r1cs::R1CS;
use crate::frontend::arkworks::{extract_r1cs, extract_z}; // TODO once Frontend trait is ready, use that
use crate::frontend::arkworks::{extract_r1cs, extract_w_x}; // TODO once Frontend trait is ready, use that
use crate::pedersen::{Params as PedersenParams, Pedersen};
use crate::Error;
#[cfg(test)]
use super::cyclefold::CF_IO_LEN;
/// Implements the Incremental Verifiable Computation described in sections 1.2 and 5 of
/// [Nova](https://eprint.iacr.org/2021/370.pdf)
pub struct IVC<C1, C2, FC>
pub struct IVC<C1, GC1, C2, GC2, FC>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
{
_gc1: PhantomData<GC1>,
_c2: PhantomData<C2>,
_gc2: PhantomData<GC2>,
r1cs: R1CS<C1::ScalarField>,
cf_r1cs: R1CS<C2::ScalarField>, // Notice that this is a different set of R1CS constraints than the 'r1cs'. This is the R1CS of the CycleFoldCircuit
poseidon_config: PoseidonConfig<C1::ScalarField>,
pedersen_params: PedersenParams<C1>,
F: FC, // F circuit
pedersen_params: PedersenParams<C1>, // PedersenParams over C1
cf_pedersen_params: PedersenParams<C2>, // CycleFold PedersenParams, over C2
F: FC, // F circuit
i: C1::ScalarField,
z_0: Vec<C1::ScalarField>,
z_i: Vec<C1::ScalarField>,
@ -34,15 +46,26 @@ where
u_i: CommittedInstance<C1>,
W_i: Witness<C1>,
U_i: CommittedInstance<C1>,
// cyclefold running instance
cf_W_i: Witness<C2>,
cf_U_i: CommittedInstance<C2>,
}
impl<C1, C2, FC> IVC<C1, C2, FC>
impl<C1, GC1, C2, GC2, FC> IVC<C1, GC1, C2, GC2, FC>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
<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>,
{
/// Initializes the IVC for the given parameters and initial state `z_0`.
pub fn new<R: Rng>(
@ -53,27 +76,41 @@ where
) -> Result<Self, Error> {
// prepare the circuit to obtain its R1CS
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
let augmented_F_circuit = AugmentedFCircuit::<C1, FC>::empty(&poseidon_config, F);
let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&poseidon_config, F);
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty();
augmented_F_circuit.generate_constraints(cs.clone())?;
cs.finalize();
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
cf_circuit.generate_constraints(cs2.clone())?;
cs2.finalize();
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
// this will not be randomly generated in this method, and will come from above levels, so
// the same params can be loaded on multiple instances
let pedersen_params = Pedersen::<C1>::new_params(rng, r1cs.A.n_rows);
let cf_pedersen_params = Pedersen::<C2>::new_params(rng, cf_r1cs.A.n_rows);
// setup the dummy instances
let (w_dummy, u_dummy) = r1cs.dummy_instance();
let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance();
// W_i=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the R1CS that
// we're working with.
// Set U_i to be dummy instance
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
// R1CS that we're working with.
Ok(Self {
_gc1: PhantomData,
_c2: PhantomData,
_gc2: PhantomData,
r1cs,
cf_r1cs,
poseidon_config,
pedersen_params,
cf_pedersen_params,
F,
i: C1::ScalarField::zero(),
z_0: z_0.clone(),
@ -82,77 +119,130 @@ where
u_i: u_dummy.clone(),
W_i: w_dummy,
U_i: u_dummy,
// cyclefold running instance
cf_W_i: cf_w_dummy.clone(),
cf_U_i: cf_u_dummy.clone(),
})
}
/// Implements IVC.P
pub fn prove_step(&mut self) -> Result<(), Error> {
let u_i1_x: C1::ScalarField;
let augmented_F_circuit: AugmentedFCircuit<C1, FC>;
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
let cf_circuit: CycleFoldCircuit<C1, GC1>;
let z_i1 = self.F.step_native(self.z_i.clone());
let (W_i1, U_i1, cmT): (Witness<C1>, CommittedInstance<C1>, C1);
// compute T and cmT for AugmentedFCircuit
let (T, cmT) = self.compute_cmT()?;
let r_bits = ChallengeGadget::<C1>::get_challenge_native(
&self.poseidon_config,
self.u_i.clone(),
self.U_i.clone(),
cmT,
)?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?;
// fold Nova instances
let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) =
NIFS::<C1>::fold_instances(r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT)?;
// folded instance output (public input, x)
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
let u_i1_x = U_i1.hash(
&self.poseidon_config,
self.i + C1::ScalarField::one(),
self.z_0.clone(),
z_i1.clone(),
)?;
if self.i == C1::ScalarField::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 = self.U_i.hash(
&self.poseidon_config,
C1::ScalarField::one(),
self.z_0.clone(),
z_i1.clone(),
)?;
(W_i1, U_i1, cmT) = (self.w_i.clone(), self.u_i.clone(), C1::generator());
// base case
augmented_F_circuit = AugmentedFCircuit::<C1, FC> {
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
i: Some(C1::ScalarField::zero()), // = i=0
z_0: Some(self.z_0.clone()), // = z_i
z_i: Some(self.z_i.clone()),
u_i: Some(self.u_i.clone()), // = dummy
U_i: Some(self.U_i.clone()), // = dummy
U_i1: Some(U_i1.clone()), // = dummy
U_i1: Some(U_i1.clone()),
cmT: Some(cmT),
F: self.F,
x: Some(u_i1_x),
cf_u_i: None,
cf_U_i: None,
cf_U_i1: None,
cf_cmT: None,
cf_r_nonnat: None,
};
#[cfg(test)]
NIFS::verify_folded_instance(r_Fr, &self.u_i, &self.U_i, &U_i1, &cmT)?;
} else {
let T: Vec<C1::ScalarField>;
(T, cmT) = NIFS::<C1>::compute_cmT(
&self.pedersen_params,
&self.r1cs,
&self.w_i,
&self.u_i,
&self.W_i,
&self.U_i,
)?;
// CycleFold part:
// 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),
]
.concat();
cf_circuit = CycleFoldCircuit::<C1, GC1> {
_gc: PhantomData,
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()),
};
let r_Fr = AugmentedFCircuit::<C1, FC>::get_challenge_native(
&self.poseidon_config,
self.u_i.clone(),
self.U_i.clone(),
cmT,
)?;
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
cf_circuit.generate_constraints(cs2.clone())?;
// compute W_{i+1} and U_{i+1}
(W_i1, U_i1) = NIFS::<C1>::fold_instances(
r_Fr, &self.w_i, &self.u_i, &self.W_i, &self.U_i, &T, cmT,
)?;
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));
}
self.r1cs.check_relaxed_instance_relation(&W_i1, &U_i1)?;
// 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(&self.cf_pedersen_params, cf_x_i.clone())?;
// 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(
// 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,
self.i + C1::ScalarField::one(),
self.z_0.clone(),
z_i1.clone(),
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>::fold_instances(
cf_r_Fq,
&self.cf_W_i,
&self.cf_U_i,
&cf_w_i,
&cf_u_i,
&cf_T,
cf_cmT,
)?;
augmented_F_circuit = AugmentedFCircuit::<C1, FC> {
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
i: Some(self.i),
z_0: Some(self.z_0.clone()),
@ -163,7 +253,23 @@ where
cmT: Some(cmT),
F: self.F,
x: Some(u_i1_x),
// cyclefold values
cf_u_i: Some(cf_u_i.clone()),
cf_U_i: Some(self.cf_U_i.clone()),
cf_U_i1: Some(cf_U_i1.clone()),
cf_cmT: Some(cf_cmT),
cf_r_nonnat: Some(cf_r_Fq),
};
self.cf_W_i = cf_W_i1.clone();
self.cf_U_i = cf_U_i1.clone();
#[cfg(test)]
{
self.cf_r1cs.check_instance_relation(&cf_w_i, &cf_u_i)?;
self.cf_r1cs
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
}
}
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
@ -171,23 +277,30 @@ where
augmented_F_circuit.generate_constraints(cs.clone())?;
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
// notice that here we use 'Z' (uppercase) to denote the 'z-vector' as in the paper, not
// the value 'z' (lowercase) which is the state
let Z_i1 = extract_z::<C1::ScalarField>(&cs);
let (w_i1, x_i1) = self.r1cs.split_z(&Z_i1);
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.
self.w_i = Witness::<C1>::new(w_i1.clone(), self.r1cs.A.n_rows);
self.u_i = self.w_i.commit(&self.pedersen_params, vec![u_i1_x])?;
let (w_i1, x_i1) = extract_w_x::<C1::ScalarField>(&cs);
if x_i1[0] != u_i1_x {
return Err(Error::NotEqual);
}
#[cfg(test)]
if x_i1.len() != 1 {
return Err(Error::NotExpectedLength(x_i1.len(), 1));
}
// set values for next iteration
self.i += C1::ScalarField::one();
self.z_i = z_i1.clone();
self.U_i = U_i1.clone();
self.w_i = Witness::<C1>::new(w_i1, self.r1cs.A.n_rows);
self.u_i = self.w_i.commit(&self.pedersen_params, vec![u_i1_x])?;
self.W_i = W_i1.clone();
self.U_i = U_i1.clone();
#[cfg(test)]
{
self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?;
self.r1cs
.check_relaxed_instance_relation(&self.W_i, &self.U_i)?;
}
Ok(())
}
@ -212,7 +325,7 @@ where
}
// check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance)
if self.u_i.cmE != C1::zero() || self.u_i.u != C1::ScalarField::one() {
if !self.u_i.cmE.is_zero() || !self.u_i.u.is_one() {
return Err(Error::IVCVerificationFail);
}
@ -222,15 +335,59 @@ where
self.r1cs
.check_relaxed_instance_relation(&self.W_i, &self.U_i)?;
// check CycleFold RelaxedR1CS satisfiability
self.cf_r1cs
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
Ok(())
}
// computes T and cmT for the AugmentedFCircuit
fn compute_cmT(&self) -> Result<(Vec<C1::ScalarField>, C1), Error> {
NIFS::<C1>::compute_cmT(
&self.pedersen_params,
&self.r1cs,
&self.w_i,
&self.u_i,
&self.W_i,
&self.U_i,
)
}
// computes T* and cmT* for the CycleFoldCircuit
fn compute_cf_cmT(
&self,
cf_w_i: &Witness<C2>,
cf_u_i: &CommittedInstance<C2>,
) -> Result<(Vec<C2::ScalarField>, C2), Error> {
NIFS::<C2>::compute_cyclefold_cmT(
&self.cf_pedersen_params,
&self.cf_r1cs,
cf_w_i,
cf_u_i,
&self.cf_W_i,
&self.cf_U_i,
)
}
}
pub(crate) fn get_committed_instance_coordinates<C: CurveGroup>(
u: &CommittedInstance<C>,
) -> Vec<C::BaseField> {
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]
}
#[cfg(test)]
mod tests {
use super::*;
use ark_pallas::{Fr, Projective};
use ark_vesta::Projective as Projective2;
use ark_pallas::{constraints::GVar, Fr, Projective};
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use crate::folding::nova::circuits::tests::TestFCircuit;
use crate::transcript::poseidon::tests::poseidon_test_config;
@ -243,9 +400,9 @@ mod tests {
let F_circuit = TestFCircuit::<Fr>::new();
let z_0 = vec![Fr::from(3_u32)];
let mut ivc = IVC::<Projective, Projective2, TestFCircuit<Fr>>::new(
let mut ivc = IVC::<Projective, GVar, Projective2, GVar2, TestFCircuit<Fr>>::new(
&mut rng,
poseidon_config, // poseidon config
poseidon_config,
F_circuit,
z_0.clone(),
)

+ 8
- 5
src/folding/nova/mod.rs

@ -13,6 +13,7 @@ use crate::utils::vec::is_zero_vec;
use crate::Error;
pub mod circuits;
pub mod cyclefold;
pub mod ivc;
pub mod nifs;
pub mod traits;
@ -25,11 +26,7 @@ pub struct CommittedInstance {
pub x: Vec<C::ScalarField>,
}
impl<C: CurveGroup> CommittedInstance<C>
where
<C as Group>::ScalarField: Absorb,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
impl<C: CurveGroup> CommittedInstance<C> {
pub fn dummy(io_len: usize) -> Self {
Self {
cmE: C::zero(),
@ -38,7 +35,13 @@ where
x: vec![C::ScalarField::zero(); io_len],
}
}
}
impl<C: CurveGroup> CommittedInstance<C>
where
<C as Group>::ScalarField: Absorb,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
/// hash implements the committed instance hash compatible with the gadget implemented in
/// nova/circuits.rs::CommittedInstanceVar.hash.
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the

+ 118
- 82
src/folding/nova/nifs.rs

@ -3,8 +3,8 @@ use ark_ec::{CurveGroup, Group};
use ark_std::One;
use std::marker::PhantomData;
use super::{CommittedInstance, Witness};
use crate::ccs::r1cs::R1CS;
use crate::folding::nova::{CommittedInstance, Witness};
use crate::pedersen::{Params as PedersenParams, Pedersen, Proof as PedersenProof};
use crate::transcript::Transcript;
use crate::utils::vec::*;
@ -31,12 +31,12 @@ where
let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone());
// this is parallelizable (for the future)
let Az1 = mat_vec_mul_sparse(&A, z1);
let Bz1 = mat_vec_mul_sparse(&B, z1);
let Cz1 = mat_vec_mul_sparse(&C, z1);
let Az2 = mat_vec_mul_sparse(&A, z2);
let Bz2 = mat_vec_mul_sparse(&B, z2);
let Cz2 = mat_vec_mul_sparse(&C, z2);
let Az1 = mat_vec_mul_sparse(&A, z1)?;
let Bz1 = mat_vec_mul_sparse(&B, z1)?;
let Cz1 = mat_vec_mul_sparse(&C, z1)?;
let Az2 = mat_vec_mul_sparse(&A, z2)?;
let Bz2 = mat_vec_mul_sparse(&B, z2)?;
let Cz2 = mat_vec_mul_sparse(&C, z2)?;
let Az1_Bz2 = hadamard(&Az1, &Bz2)?;
let Az2_Bz1 = hadamard(&Az2, &Bz1)?;
@ -105,12 +105,31 @@ where
let cmT = Pedersen::commit(pedersen_params, &T, &C::ScalarField::one())?;
Ok((T, cmT))
}
pub fn compute_cyclefold_cmT(
pedersen_params: &PedersenParams<C>,
r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2)
w1: &Witness<C>,
ci1: &CommittedInstance<C>,
w2: &Witness<C>,
ci2: &CommittedInstance<C>,
) -> Result<(Vec<C::ScalarField>, C), Error>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
let z2: Vec<C::ScalarField> = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat();
// compute cross terms
let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?;
// use r_T=1 since we don't need hiding property for cm(T)
let cmT = Pedersen::commit(pedersen_params, &T, &C::ScalarField::one())?;
Ok((T, cmT))
}
/// fold_instances is part of the NIFS.P logic described in
/// [Nova](https://eprint.iacr.org/2021/370.pdf)'s section 4. It returns the folded Committed
/// Instances and the Witness.
pub fn fold_instances(
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
r: C::ScalarField,
w1: &Witness<C>,
ci1: &CommittedInstance<C>,
@ -196,19 +215,82 @@ where
#[cfg(test)]
pub mod tests {
use super::*;
use ark_ff::PrimeField;
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{Fr, Projective};
use ark_std::{ops::Mul, UniformRand, Zero};
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::folding::nova::circuits::ChallengeGadget;
use crate::folding::nova::traits::NovaR1CS;
use crate::transcript::poseidon::{tests::poseidon_test_config, PoseidonTranscript};
use crate::utils::vec::vec_scalar_mul;
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
#[allow(clippy::type_complexity)]
pub(crate) fn prepare_simple_fold_inputs() -> (
PedersenParams<Projective>,
PoseidonConfig<Fr>,
R1CS<Fr>,
Witness<Projective>, // w1
CommittedInstance<Projective>, // ci1
Witness<Projective>, // w2
CommittedInstance<Projective>, // ci2
Witness<Projective>, // w3
CommittedInstance<Projective>, // ci3
Vec<Fr>, // T
Projective, // cmT
Vec<bool>, // r_bits
Fr, // r_Fr
) {
let r1cs = get_test_r1cs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
let (w1, x1) = r1cs.split_z(&z1);
let (w2, x2) = r1cs.split_z(&z2);
let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
let mut rng = ark_std::test_rng();
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
pub fn check_relaxed_r1cs<F: PrimeField>(r1cs: &R1CS<F>, z: &[F], u: &F, E: &[F]) -> bool {
let Az = mat_vec_mul_sparse(&r1cs.A, z);
let Bz = mat_vec_mul_sparse(&r1cs.B, z);
let Cz = mat_vec_mul_sparse(&r1cs.C, z);
hadamard(&Az, &Bz).unwrap() == vec_add(&vec_scalar_mul(&Cz, u), E).unwrap()
// compute committed instances
let ci1 = w1.commit(&pedersen_params, x1.clone()).unwrap();
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
// NIFS.P
let (T, cmT) =
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
let poseidon_config = poseidon_test_config::<Fr>();
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
&poseidon_config,
ci1.clone(),
ci2.clone(),
cmT,
)
.unwrap();
let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
let (w3, ci3) =
NIFS::<Projective>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
(
pedersen_params,
poseidon_config,
r1cs,
w1,
ci1,
w2,
ci2,
w3,
ci3,
T,
cmT,
r_bits,
r_Fr,
)
}
// fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation
@ -232,9 +314,8 @@ pub mod tests {
let u_i = u_dummy.clone();
let W_i = w_dummy.clone();
let U_i = u_dummy.clone();
let z_dummy = vec![Fr::zero(); z1.len()];
assert!(check_relaxed_r1cs(&r1cs, &z_dummy, &u_i.u, &w_i.E));
assert!(check_relaxed_r1cs(&r1cs, &z_dummy, &U_i.u, &W_i.E));
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
let r_Fr = Fr::from(3_u32);
@ -243,52 +324,23 @@ pub mod tests {
.unwrap();
let (W_i1, U_i1) =
NIFS::<Projective>::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT).unwrap();
let z: Vec<Fr> = [vec![U_i1.u], U_i1.x.to_vec(), W_i1.W.to_vec()].concat();
assert_eq!(z.len(), z1.len());
assert!(check_relaxed_r1cs(&r1cs, &z, &U_i1.u, &W_i1.E));
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
}
// fold 2 instances into one
#[test]
fn test_nifs_one_fold() {
let r1cs = get_test_r1cs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
let (w1, x1) = r1cs.split_z(&z1);
let (w2, x2) = r1cs.split_z(&z2);
let w1 = Witness::<Projective>::new(w1.clone(), r1cs.A.n_rows);
let w2 = Witness::<Projective>::new(w2.clone(), r1cs.A.n_rows);
let mut rng = ark_std::test_rng();
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_cols);
let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
// compute committed instances
let ci1 = w1.commit(&pedersen_params, x1.clone()).unwrap();
let ci2 = w2.commit(&pedersen_params, x2.clone()).unwrap();
// NIFS.P
let (T, cmT) =
NIFS::<Projective>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2).unwrap();
let (w3, ci3_aux) =
NIFS::<Projective>::fold_instances(r, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap();
let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, w3, ci3, T, cmT, _, r) =
prepare_simple_fold_inputs();
// NIFS.V
let ci3 = NIFS::<Projective>::verify(r, &ci1, &ci2, &cmT);
assert_eq!(ci3, ci3_aux);
// naive check that the folded witness satisfies the relaxed r1cs
let z3: Vec<Fr> = [vec![ci3.u], ci3.x.to_vec(), w3.W.to_vec()].concat();
// check that z3 is as expected
let z3_aux = vec_add(&z1, &vec_scalar_mul(&z2, &r)).unwrap();
assert_eq!(z3, z3_aux);
let ci3_v = NIFS::<Projective>::verify(r, &ci1, &ci2, &cmT);
assert_eq!(ci3_v, ci3);
// check that relations hold for the 2 inputted instances and the folded one
assert!(check_relaxed_r1cs(&r1cs, &z1, &ci1.u, &w1.E));
assert!(check_relaxed_r1cs(&r1cs, &z2, &ci2.u, &w2.E));
assert!(check_relaxed_r1cs(&r1cs, &z3, &ci3.u, &w3.E));
r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap();
r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap();
r1cs.check_relaxed_instance_relation(&w3, &ci3).unwrap();
// check that folded commitments from folded instance (ci) are equal to folding the
// use folded rE, rW to commit w3
@ -303,7 +355,6 @@ pub mod tests {
// NIFS.Verify_Folded_Instance:
NIFS::<Projective>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT).unwrap();
let poseidon_config = poseidon_test_config::<Fr>();
// init Prover's transcript
let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
// init Verifier's transcript
@ -343,12 +394,9 @@ pub mod tests {
let mut running_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
let mut running_committed_instance =
running_instance_w.commit(&pedersen_params, x).unwrap();
assert!(check_relaxed_r1cs(
&r1cs,
&z,
&running_committed_instance.u,
&running_instance_w.E,
));
r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance)
.unwrap();
let num_iters = 10;
for i in 0..num_iters {
@ -358,14 +406,13 @@ pub mod tests {
let incomming_instance_w = Witness::<Projective>::new(w.clone(), r1cs.A.n_rows);
let incomming_committed_instance =
incomming_instance_w.commit(&pedersen_params, x).unwrap();
assert!(check_relaxed_r1cs(
&r1cs,
&incomming_instance_z.clone(),
&incomming_committed_instance.u,
&incomming_instance_w.E,
));
r1cs.check_relaxed_instance_relation(
&incomming_instance_w,
&incomming_committed_instance,
)
.unwrap();
let r = Fr::rand(&mut rng); // folding challenge would come from the transcript
let r = Fr::rand(&mut rng); // folding challenge would come from the RO
// NIFS.P
let (T, cmT) = NIFS::<Projective>::compute_cmT(
@ -396,19 +443,8 @@ pub mod tests {
&cmT,
);
let folded_z: Vec<Fr> = [
vec![folded_committed_instance.u],
folded_committed_instance.x.to_vec(),
folded_w.W.to_vec(),
]
.concat();
assert!(check_relaxed_r1cs(
&r1cs,
&folded_z,
&folded_committed_instance.u,
&folded_w.E
));
r1cs.check_relaxed_instance_relation(&folded_w, &folded_committed_instance)
.unwrap();
// set running_instance for next loop iteration
running_instance_w = folded_w;

+ 9
- 0
src/frontend/arkworks/mod.rs

@ -59,6 +59,15 @@ pub fn extract_z(cs: &ConstraintSystem) -> Vec {
.concat()
}
/// extracts the witness and the public inputs from arkworks ConstraintSystem.
pub fn extract_w_x<F: PrimeField>(cs: &ConstraintSystem<F>) -> (Vec<F>, Vec<F>) {
(
cs.witness_assignment.clone(),
// skip the first element which is '1'
cs.instance_assignment[1..].to_vec(),
)
}
#[cfg(test)]
pub mod tests {
use super::*;

+ 5
- 1
src/lib.rs

@ -16,10 +16,12 @@ pub mod frontend;
pub mod pedersen;
pub mod utils;
#[derive(Debug, Error, PartialEq)]
#[derive(Debug, Error)]
pub enum Error {
#[error("ark_relations::r1cs::SynthesisError")]
SynthesisError(#[from] ark_relations::r1cs::SynthesisError),
#[error("ark_serialize::SerializationError")]
SerializationError(#[from] ark_serialize::SerializationError),
#[error("{0}")]
Other(String),
@ -47,6 +49,8 @@ pub enum Error {
SumCheckProveError(String),
#[error("Sum-check verify failed: {0}")]
SumCheckVerifyError(String),
#[error("Value out of bounds")]
OutOfBounds,
}
/// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined

+ 3
- 1
src/pedersen.rs

@ -132,7 +132,9 @@ mod tests {
// init Verifier's transcript
let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
let v: Vec<Fr> = vec![Fr::rand(&mut rng); n];
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(n)
.collect();
let r: Fr = Fr::rand(&mut rng);
let cm = Pedersen::<Projective>::commit(&params, &v, &r).unwrap();
let proof = Pedersen::<Projective>::prove(&params, &mut transcript_p, &cm, &v, &r).unwrap();

+ 4
- 6
src/transcript/poseidon.rs

@ -61,11 +61,9 @@ where
// over bytes in order to have a logic that can be reproduced in-circuit.
fn prepare_point<C: CurveGroup>(p: &C) -> Result<Vec<C::ScalarField>, Error> {
let affine = p.into_affine();
let xy_obj = &affine.xy();
let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
if xy_obj.is_some() {
xy = xy_obj.unwrap();
}
let zero_point = (&C::BaseField::zero(), &C::BaseField::one());
let xy = affine.xy().unwrap_or(zero_point);
let x_bi =
xy.0.to_base_prime_field_elements()
.next()
@ -175,7 +173,7 @@ pub mod tests {
#[test]
fn test_transcript_and_transcriptvar_nbits() {
let nbits = 128;
let nbits = crate::constants::N_BITS_RO;
// use 'native' transcript
let config = poseidon_test_config::<Fq>();

+ 13
- 7
src/utils/vec.rs

@ -83,14 +83,17 @@ pub fn mat_vec_mul(M: &Vec>, z: &[F]) -> Result, Er
Ok(r)
}
pub fn mat_vec_mul_sparse<F: PrimeField>(matrix: &SparseMatrix<F>, vector: &[F]) -> Vec<F> {
let mut res = vec![F::zero(); matrix.n_rows];
for (row_i, row) in matrix.coeffs.iter().enumerate() {
pub fn mat_vec_mul_sparse<F: PrimeField>(M: &SparseMatrix<F>, z: &[F]) -> Result<Vec<F>, Error> {
if M.n_cols != z.len() {
return Err(Error::NotSameLength(M.n_cols, z.len()));
}
let mut res = vec![F::zero(); M.n_rows];
for (row_i, row) in M.coeffs.iter().enumerate() {
for &(value, col_i) in row.iter() {
res[row_i] += value * vector[col_i];
res[row_i] += value * z[col_i];
}
}
res
Ok(res)
}
pub fn hadamard<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
@ -142,7 +145,7 @@ pub mod tests {
let z = to_F_vec(vec![1, 3, 35, 9, 27, 30]);
assert_eq!(mat_vec_mul(&A, &z).unwrap(), to_F_vec(vec![3, 9, 30, 35]));
assert_eq!(
mat_vec_mul_sparse(&dense_matrix_to_sparse(A), &z),
mat_vec_mul_sparse(&dense_matrix_to_sparse(A), &z).unwrap(),
to_F_vec(vec![3, 9, 30, 35])
);
@ -153,7 +156,10 @@ pub mod tests {
mat_vec_mul(&A.to_dense(), &v).unwrap(),
to_F_vec(vec![418, 1158, 979])
);
assert_eq!(mat_vec_mul_sparse(&A, &v), to_F_vec(vec![418, 1158, 979]));
assert_eq!(
mat_vec_mul_sparse(&A, &v).unwrap(),
to_F_vec(vec![418, 1158, 979])
);
}
#[test]

Loading…
Cancel
Save