Browse Source

Add the digest of the Relaxed R1CS instance for CycleFold as a public input to `AugmentedFCircuit` (#84)

* Treat (the digest of) `cf_U_i1` as an additional public input to `AugmentedFCircuit` for full soundness

* Fix the y-coordinate in the affine form of zero points

This in turn fixes the inconsistency between the digest of a constant affine point and that of a witness affine point in circuits.

* Set `cf_u_i1_x` to the correct value

* Fix the number of public inputs in dummy instance and witness

* Unify the logic behind `CycleFoldCommittedInstanceVar::hash` and `CycleFoldChallengeGadget::get_challenge_gadget`

* Add `ToConstraintFieldGadget` bound to `GC2`

* Remove unnecessary code used for debugging

* Make clippy and rustfmt happy

* Move conversion methods for `NonNativeFieldVar` to `folding/circuits/nonnative.rs`

* Simplify the check of zero coordinates

* Gracefully handle the result of `nonnative_field_var_to_constraint_field`

* Make clippy happy again
main
winderica 5 months ago
committed by GitHub
parent
commit
6a7dd935bd
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
8 changed files with 328 additions and 175 deletions
  1. +6
    -4
      Cargo.toml
  2. +64
    -4
      folding-schemes/src/folding/circuits/nonnative.rs
  3. +24
    -18
      folding-schemes/src/folding/nova/circuits.rs
  4. +135
    -102
      folding-schemes/src/folding/nova/cyclefold.rs
  5. +1
    -1
      folding-schemes/src/folding/nova/decider_eth.rs
  6. +17
    -29
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  7. +78
    -14
      folding-schemes/src/folding/nova/mod.rs
  8. +3
    -3
      folding-schemes/src/transcript/poseidon.rs

+ 6
- 4
Cargo.toml

@ -8,11 +8,13 @@ resolver = "2"
[patch.crates-io]
# The following patch is to use a version of ark-r1cs-std compatible with
# v0.4.0 but that includes a cherry-picked commit from after v0.4.0 which fixes
# the in-circuit scalar multiplication of the zero point. The commit is from
# https://github.com/arkworks-rs/r1cs-std/pull/124, without including other
# v0.4.0 but that includes two cherry-picked commits from after v0.4.0 which
# fixes the in-circuit scalar multiplication of the zero point and the
# y-coordinate of the zero point. The commits are respectively from
# https://github.com/arkworks-rs/r1cs-std/pull/124 and
# https://github.com/arkworks-rs/r1cs-std/pull/126, without including other
# changes done between v0.4.0 and this fix which would break compatibility.
ark-r1cs-std = { git = "https://github.com/arnaucube/ark-r1cs-std-cherry-picked/" }
ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", branch="cherry-pick" }
# patch ark_curves to use a cherry-picked version which contains
# bn254::constraints & grumpkin for v0.4.0 (once arkworks v0.5.0 is released
# this will no longer be needed)

+ 64
- 4
folding-schemes/src/folding/circuits/nonnative.rs

@ -1,12 +1,72 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar, NonNativeFieldVar},
fields::{
fp::FpVar,
nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar, NonNativeFieldVar},
FieldVar,
},
ToBitsGadget,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_std::{One, Zero};
use ark_std::Zero;
use core::borrow::Borrow;
/// A more efficient version of `NonNativeFieldVar::to_constraint_field`
pub fn nonnative_field_var_to_constraint_field<TargetField: PrimeField, BaseField: PrimeField>(
f: &NonNativeFieldVar<TargetField, BaseField>,
) -> Result<Vec<FpVar<BaseField>>, SynthesisError> {
let bits = f.to_bits_le()?;
let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1;
let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb);
let mut limbs = bits
.chunks(bits_per_limb)
.map(|chunk| {
let mut limb = FpVar::<BaseField>::zero();
let mut w = BaseField::one();
for b in chunk.iter() {
limb += FpVar::from(b.clone()) * w;
w.double_in_place();
}
limb
})
.collect::<Vec<FpVar<BaseField>>>();
limbs.resize(num_limbs, FpVar::zero());
limbs.reverse();
Ok(limbs)
}
/// The out-circuit counterpart of `nonnative_field_var_to_constraint_field`
pub fn nonnative_field_to_field_elements<TargetField: PrimeField, BaseField: PrimeField>(
f: &TargetField,
) -> Vec<BaseField> {
let bits = f.into_bigint().to_bits_le();
let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1;
let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb);
let mut limbs = bits
.chunks(bits_per_limb)
.map(|chunk| {
let mut limb = BaseField::zero();
let mut w = BaseField::one();
for &b in chunk.iter() {
limb += BaseField::from(b) * w;
w.double_in_place();
}
limb
})
.collect::<Vec<BaseField>>();
limbs.resize(num_limbs, BaseField::zero());
limbs.reverse();
limbs
}
/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native
/// field, over the constraint field. It is not intended to perform operations, but just to contain
/// the affine coordinates in order to perform hash operations of the point.
@ -33,7 +93,7 @@ where
let cs = cs.into();
let affine = val.borrow().into_affine();
let zero_point = (&C::BaseField::zero(), &C::BaseField::one());
let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
let xy = affine.xy().unwrap_or(zero_point);
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
@ -84,7 +144,7 @@ where
)?;
let y =
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
&C::BaseField::one(),
&C::BaseField::zero(),
optimization_type,
)?;
return Ok((x, y));

+ 24
- 18
folding-schemes/src/folding/nova/circuits.rs

@ -21,7 +21,7 @@ use ark_r1cs_std::{
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::fmt::Debug;
use ark_std::{One, Zero};
use ark_std::Zero;
use core::{borrow::Borrow, marker::PhantomData};
use super::{
@ -254,7 +254,7 @@ pub struct AugmentedFCircuit<
pub r_nonnat: Option<CF2<C1>>,
pub cmT: Option<C1>,
pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x)
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
// cyclefold verifier on C1
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
@ -268,6 +268,7 @@ pub struct AugmentedFCircuit<
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])
}
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
@ -300,6 +301,7 @@ where
cf2_cmT: None,
cf1_r_nonnat: None,
cf2_r_nonnat: None,
cf_x: None,
}
}
}
@ -308,7 +310,7 @@ impl ConstraintSynthesizer> for AugmentedFCircuit
where
C1: CurveGroup,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<CF1<C1>>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
@ -332,7 +334,7 @@ where
.unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()]))
})?;
let u_dummy_native = CommittedInstance::<C1>::dummy(1);
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()))
})?;
@ -365,24 +367,16 @@ where
let zero = FpVar::<CF1<C1>>::new_constant(cs.clone(), CF1::<C1>::zero())?;
let is_not_basecase = i.is_neq(&zero)?;
// 1. u_i.x == H(i, z_0, z_i, U_i)
// 1.a u_i.x[0] == H(i, z_0, z_i, U_i)
let (u_i_x, U_i_vec) =
U_i.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
// check that h == u_i.x
// check that h == u_i.x[0]
(u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
// 2. u_i.cmE==cm(0), u_i.u==1
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(),
C1::BaseField::zero(),
)?;
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(),
C1::BaseField::one(),
)?;
(u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
(u_i.cmE.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)?;
// 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}.
@ -406,7 +400,7 @@ where
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. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F'
// 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'
let (u_i1_x, _) = U_i1.clone().hash(
&crh_params,
i + FpVar::<CF1<C1>>::one(),
@ -444,6 +438,9 @@ where
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![
r_nonnat.clone(),
@ -458,6 +455,15 @@ where
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,
// u_i, U_i+1 coordinates of the commitments
cf1_u_i
@ -527,7 +533,7 @@ pub mod tests {
use super::*;
use ark_bn254::{Fr, G1Projective as Projective};
use ark_ff::BigInteger;
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;

+ 135
- 102
folding-schemes/src/folding/nova/cyclefold.rs

@ -1,30 +1,35 @@
/// 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_crypto_primitives::{
crh::{
poseidon::constraints::{CRHGadget, CRHParametersVar},
CRHSchemeGadget,
},
sponge::{
constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,
},
};
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{Field, PrimeField, ToConstraintField};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
bits::uint8::UInt8,
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
groups::GroupOpsBounds,
prelude::CurveVar,
ToBytesGadget, ToConstraintFieldGadget,
ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_serialize::CanonicalSerialize;
use ark_std::fmt::Debug;
use ark_std::Zero;
use ark_std::{One, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::circuits::CF2;
use super::CommittedInstance;
use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::nonnative_field_var_to_constraint_field;
use crate::Error;
// public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]|
@ -82,6 +87,61 @@ where
}
}
impl<C, GC> ToConstraintFieldGadget<CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
// Extract the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order of
// `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for concat).
fn to_constraint_field(&self) -> Result<Vec<FpVar<CF2<C>>>, SynthesisError> {
let mut cmE_elems = self.cmE.to_constraint_field()?;
let mut cmW_elems = self.cmW.to_constraint_field()?;
let cmE_is_inf = cmE_elems.pop().unwrap();
let cmW_is_inf = cmW_elems.pop().unwrap();
// Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate
let is_inf = cmE_is_inf.double()? + cmW_is_inf;
Ok([
nonnative_field_var_to_constraint_field(&self.u)?,
self.x
.iter()
.map(nonnative_field_var_to_constraint_field)
.collect::<Result<Vec<_>, _>>()?
.concat(),
cmE_elems,
cmW_elems,
vec![is_inf],
]
.concat())
}
}
impl<C, GC> CycleFoldCommittedInstanceVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
/// hash implements the committed instance hash compatible with the native implementation from
/// CommittedInstance.hash_cyclefold.
/// Returns `H(U_i)`, where `U` is the `CommittedInstance` for CycleFold.
/// Additionally it returns the vector of the field elements from the self parameters, so they
/// can be reused in other gadgets avoiding recalculating (reconstraining) them.
#[allow(clippy::type_complexity)]
pub fn hash(
self,
crh_params: &CRHParametersVar<CF2<C>>,
) -> Result<(FpVar<CF2<C>>, Vec<FpVar<CF2<C>>>), SynthesisError> {
let U_vec = self.to_constraint_field()?;
Ok((CRHGadget::evaluate(crh_params, &U_vec)?, U_vec))
}
}
/// 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
@ -184,7 +244,7 @@ pub struct CycleFoldChallengeGadget>> {
impl<C, GC> CycleFoldChallengeGadget<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
<C as CurveGroup>::BaseField: PrimeField,
<C as CurveGroup>::BaseField: Absorb,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
@ -197,39 +257,30 @@ where
) -> 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();
let mut U_vec = U_i.to_field_elements().unwrap();
let mut u_vec = u_i.to_field_elements().unwrap();
let (cmT_x, cmT_y, cmT_is_inf) = match cmT.into_affine().xy() {
Some((&x, &y)) => (x, y, C::BaseField::zero()),
None => (
C::BaseField::zero(),
C::BaseField::zero(),
C::BaseField::one(),
),
};
let U_cm_is_inf = U_vec.pop().unwrap();
let u_cm_is_inf = u_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`
// to save constraints for sponge.squeeze_bits in the corresponding circuit
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, vec![cmT_x, cmT_y, is_inf]].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>,
@ -240,57 +291,25 @@ where
) -> 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>>> = [
pointvar_to_bytes(U_i.cmE)?,
U_i.u.to_bytes()?,
pointvar_to_bytes(U_i.cmW)?,
U_i_x_bytes,
pointvar_to_bytes(u_i.cmE)?,
u_i.u.to_bytes()?,
pointvar_to_bytes(u_i.cmW)?,
u_i_x_bytes,
pointvar_to_bytes(cmT)?,
]
.concat();
let mut U_vec = U_i.to_constraint_field()?;
let mut u_vec = u_i.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 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`
// 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 input = [U_vec, u_vec, cmT_vec, vec![is_inf]].concat();
sponge.absorb(&input)?;
let bits = sponge.squeeze_bits(N_BITS_RO)?;
Ok(bits)
}
}
/// returns the bytes being compatible with the pointvar_to_bytes method.
/// These methods are temporary once arkworks has the fix to prevent different to_bytes behaviour
/// across different curves. Eg, in pasta and bn254: pasta returns 65 bytes both native and gadget,
/// whereas bn254 returns 64 bytes native and 65 in gadget, also the penultimate byte is different
/// natively than in gadget.
fn point_to_bytes<C: CurveGroup>(p: C) -> Result<Vec<u8>, Error> {
let l = p.uncompressed_size();
let mut b = Vec::new();
p.serialize_uncompressed(&mut b)?;
if p.is_zero() {
b[l / 2] = 1;
b[l - 1] = 1;
}
Ok(b[..63].to_vec())
}
fn pointvar_to_bytes<C: CurveGroup, GC: CurveVar<C, CF2<C>>>(
p: GC,
) -> Result<Vec<UInt8<CF2<C>>>, SynthesisError> {
let b = p.to_bytes()?;
Ok(b[..63].to_vec())
}
/// 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}
@ -362,7 +381,7 @@ pub mod tests {
use super::*;
use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective};
use ark_ff::BigInteger;
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
@ -480,21 +499,6 @@ pub mod tests {
assert!(cs.is_satisfied().unwrap());
}
#[test]
fn test_point_bytes() {
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let p_bytes = point_to_bytes(p).unwrap();
let cs = ConstraintSystem::<Fq>::new_ref();
let pVar = GVar::new_witness(cs.clone(), || Ok(p)).unwrap();
assert_eq!(pVar.value().unwrap(), p);
let p_bytesVar = &pointvar_to_bytes(pVar).unwrap();
assert_eq!(p_bytesVar.value().unwrap(), p_bytes);
}
#[test]
fn test_cyclefold_challenge_gadget() {
let mut rng = ark_std::test_rng();
@ -556,4 +560,33 @@ pub mod tests {
assert_eq!(rVar.value().unwrap(), r);
assert_eq!(r_bitsVar.value().unwrap(), r_bits);
}
#[test]
fn test_cyclefold_hash_gadget() {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fq>();
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 h = U_i.hash_cyclefold(&poseidon_config).unwrap();
let cs = ConstraintSystem::<Fq>::new_ref();
let U_iVar =
CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
Ok(U_i.clone())
})
.unwrap();
let (hVar, _) = U_iVar
.hash(&CRHParametersVar::new_constant(cs.clone(), poseidon_config).unwrap())
.unwrap();
hVar.enforce_equal(&FpVar::new_witness(cs.clone(), || Ok(h)).unwrap())
.unwrap();
assert!(cs.is_satisfied().unwrap());
}
}

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

@ -57,7 +57,7 @@ where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<
C1,

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

@ -16,7 +16,7 @@ use ark_r1cs_std::{
ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{log2, One, Zero};
use ark_std::{log2, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::{circuits::ChallengeGadget, nifs::NIFS};
@ -245,7 +245,7 @@ where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
CS1: CommitmentScheme<C1>,
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
CS2: CommitmentScheme<C2, ProverParams = PedersenParams<C2>>,
@ -337,7 +337,7 @@ where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
<C1 as CurveGroup>::BaseField: PrimeField,
@ -362,9 +362,9 @@ where
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let u_dummy_native = CommittedInstance::<C1>::dummy(1);
let u_dummy_native = CommittedInstance::<C1>::dummy(2);
let w_dummy_native = Witness::<C1>::new(
vec![C1::ScalarField::zero(); self.r1cs.A.n_cols - 2 /* (2=1+1, since u_i.x.len=1) */],
vec![C1::ScalarField::zero(); self.r1cs.A.n_cols - 3 /* (3=2+1, since u_i.x.len=2) */],
self.E_len,
);
@ -412,20 +412,13 @@ where
)?;
// 2. u_i.cmE==cm(0), u_i.u==1
// Here zero_x & zero_y are the x & y coordinates of the zero point affine representation.
let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(),
C1::BaseField::zero(),
)?;
let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
cs.clone(),
C1::BaseField::one(),
)?;
u_i.cmE.x.enforce_equal(&zero_x)?;
u_i.cmE.y.enforce_equal(&zero_y)?;
// Here zero is the x & y coordinates of the zero point affine representation.
let zero = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::zero();
u_i.cmE.x.enforce_equal(&zero)?;
u_i.cmE.y.enforce_equal(&zero)?;
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
// 3. u_i.x == H(i, z_0, z_i, U_i)
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
let (u_i_x, U_i_vec) =
U_i.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
@ -454,6 +447,10 @@ where
Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone()))
})?;
// 3.b u_i.x[1] == H(cf_U_i)
let (cf_u_i_x, _) = cf_U_i.clone().hash(&crh_params)?;
(u_i.x[1]).enforce_equal(&cf_u_i_x)?;
// 4. check Pedersen commitments of cf_U_i.{cmE, cmW}
let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?;
let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?;
@ -615,16 +612,10 @@ pub mod tests {
},
CRHScheme, CRHSchemeGadget,
};
use ark_ff::BigInteger;
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{
alloc::AllocVar,
bits::uint8::UInt8,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
};
use ark_r1cs_std::bits::uint8::UInt8;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
use ark_std::{One, UniformRand};
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use crate::commitment::pedersen::Pedersen;
@ -633,11 +624,8 @@ pub mod tests {
use crate::transcript::poseidon::poseidon_test_config;
use crate::FoldingScheme;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
use crate::ccs::r1cs::{
tests::{get_test_r1cs, get_test_z},
R1CS,
};
#[test]
fn test_relaxed_r1cs_small_gadget_handcrafted() {

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

@ -5,7 +5,7 @@ use ark_crypto_primitives::{
sponge::{poseidon::PoseidonConfig, Absorb},
};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_std::fmt::Debug;
use ark_std::{One, Zero};
@ -15,7 +15,9 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS};
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::nonnative::point_to_nonnative_limbs;
use crate::folding::circuits::nonnative::{
nonnative_field_to_field_elements, point_to_nonnative_limbs,
};
use crate::frontend::FCircuit;
use crate::utils::vec::is_zero_vec;
use crate::Error;
@ -93,6 +95,56 @@ where
}
}
impl<C: CurveGroup> ToConstraintField<C::BaseField> for CommittedInstance<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
{
fn to_field_elements(&self) -> Option<Vec<C::BaseField>> {
let u = nonnative_field_to_field_elements(&self.u);
let x = self
.x
.iter()
.flat_map(nonnative_field_to_field_elements)
.collect::<Vec<_>>();
let (cmE_x, cmE_y, cmE_is_inf) = match self.cmE.into_affine().xy() {
Some((&x, &y)) => (x, y, C::BaseField::zero()),
None => (
C::BaseField::zero(),
C::BaseField::zero(),
C::BaseField::one(),
),
};
let (cmW_x, cmW_y, cmW_is_inf) = match self.cmW.into_affine().xy() {
Some((&x, &y)) => (x, y, C::BaseField::zero()),
None => (
C::BaseField::zero(),
C::BaseField::zero(),
C::BaseField::one(),
),
};
// Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate in the corresponding circuit
let is_inf = cmE_is_inf.double() + cmW_is_inf;
Some([u, x, vec![cmE_x, cmE_y, cmW_x, cmW_y, is_inf]].concat())
}
}
impl<C: CurveGroup> CommittedInstance<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
{
/// hash_cyclefold implements the committed instance hash compatible with the gadget implemented in
/// nova/cyclefold.rs::CycleFoldCommittedInstanceVar.hash.
/// Returns `H(U_i)`, where `U_i` is the `CommittedInstance` for CycleFold.
pub fn hash_cyclefold(
&self,
poseidon_config: &PoseidonConfig<C::BaseField>,
) -> Result<C::BaseField, Error> {
CRH::<C::BaseField>::evaluate(poseidon_config, self.to_field_elements().unwrap())
.map_err(|e| Error::Other(e.to_string()))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Witness<C: CurveGroup> {
pub E: Vec<C::ScalarField>,
@ -203,7 +255,7 @@ where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
@ -320,15 +372,18 @@ where
)?;
// folded instance output (public input, x)
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
// u_{i+1}.x[0] = 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(),
)?;
// u_{i+1}.x[1] = H(cf_U_{i+1})
let cf_u_i1_x: C1::ScalarField;
if self.i == C1::ScalarField::zero() {
cf_u_i1_x = self.cf_U_i.hash_cyclefold(&self.poseidon_config)?;
// base case
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData,
@ -353,6 +408,7 @@ where
cf2_cmT: None,
cf1_r_nonnat: None,
cf2_r_nonnat: None,
cf_x: Some(cf_u_i1_x),
};
#[cfg(test)]
@ -406,6 +462,8 @@ where
let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r2_Fq) =
self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?;
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
@ -430,6 +488,7 @@ where
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),
};
self.cf_W_i = cf_W_i1.clone();
@ -453,20 +512,20 @@ where
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let (w_i1, x_i1) = extract_w_x::<C1::ScalarField>(&cs);
if x_i1[0] != u_i1_x {
if x_i1[0] != u_i1_x || x_i1[1] != cf_u_i1_x {
return Err(Error::NotEqual);
}
#[cfg(test)]
if x_i1.len() != 1 {
return Err(Error::NotExpectedLength(x_i1.len(), 1));
if x_i1.len() != 2 {
return Err(Error::NotExpectedLength(x_i1.len(), 2));
}
// set values for next iteration
self.i += C1::ScalarField::one();
self.z_i = z_i1.clone();
self.w_i = Witness::<C1>::new(w_i1, self.r1cs.A.n_rows);
self.u_i = self.w_i.commit::<CS1>(&self.cs_params, vec![u_i1_x])?;
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();
@ -511,16 +570,21 @@ where
let (u_i, w_i) = incoming_instance;
let (cf_U_i, cf_W_i) = cyclefold_instance;
if u_i.x.len() != 1 || U_i.x.len() != 1 {
if u_i.x.len() != 2 || U_i.x.len() != 2 {
return Err(Error::IVCVerificationFail);
}
// check that u_i's output points to the running instance
// u_i.X == H(i, z_0, z_i, U_i)
// u_i.X[0] == H(i, z_0, z_i, U_i)
let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?;
if expected_u_i_x != u_i.x[0] {
return Err(Error::IVCVerificationFail);
}
// u_i.X[1] == H(cf_U_i)
let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config)?;
if expected_cf_u_i_x != u_i.x[1] {
return Err(Error::IVCVerificationFail);
}
// check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance)
if !u_i.cmE.is_zero() || !u_i.u.is_one() {
@ -589,7 +653,7 @@ where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
@ -680,7 +744,7 @@ where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
@ -708,7 +772,7 @@ where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
@ -725,7 +789,7 @@ where
/// returns the coordinates of a commitment point. This is compatible with the arkworks
/// GC.to_constraint_field()[..2]
pub(crate) fn get_cm_coordinates<C: CurveGroup>(cm: &C) -> Vec<C::BaseField> {
let zero = (&C::BaseField::zero(), &C::BaseField::one());
let zero = (&C::BaseField::zero(), &C::BaseField::zero());
let cm = cm.into_affine();
let (cm_x, cm_y) = cm.xy().unwrap_or(zero);
vec![*cm_x, *cm_y]

+ 3
- 3
folding-schemes/src/transcript/poseidon.rs

@ -7,7 +7,7 @@ use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, Field, PrimeField};
use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar};
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
use ark_std::{One, Zero};
use ark_std::Zero;
use crate::transcript::Transcript;
use crate::Error;
@ -61,7 +61,7 @@ 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 zero_point = (&C::BaseField::zero(), &C::BaseField::one());
let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
let xy = affine.xy().unwrap_or(zero_point);
let x_bi =
@ -145,7 +145,7 @@ pub fn poseidon_test_config() -> PoseidonConfig {
pub mod tests {
use super::*;
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar};
use ark_r1cs_std::{alloc::AllocVar, groups::CurveVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_vesta::Projective as E2Projective;
use std::ops::Mul;

Loading…
Cancel
Save