Browse Source

Link committed instances and r to the public input x in cyclefold circuit (#81)

* CycleFold circuit: link r,U_i,u_i point coords as inputs

* DeciderEth::prove: rm repeated cmT, r, W_i1 computation
main
arnaucube 5 months ago
committed by GitHub
parent
commit
b8db622a08
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
7 changed files with 73 additions and 88 deletions
  1. +1
    -2
      folding-schemes/src/folding/circuits/nonnative.rs
  2. +19
    -3
      folding-schemes/src/folding/nova/circuits.rs
  3. +22
    -15
      folding-schemes/src/folding/nova/cyclefold.rs
  4. +11
    -49
      folding-schemes/src/folding/nova/decider_eth.rs
  5. +2
    -3
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  6. +18
    -13
      folding-schemes/src/folding/nova/mod.rs
  7. +0
    -3
      folding-schemes/src/utils/mle.rs

+ 1
- 2
folding-schemes/src/folding/circuits/nonnative.rs

@ -1,8 +1,7 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_r1cs_std::fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
fields::nonnative::NonNativeFieldVar,
fields::nonnative::{params::OptimizationType, AllocatedNonNativeFieldVar, NonNativeFieldVar},
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_std::{One, Zero};

+ 19
- 3
folding-schemes/src/folding/nova/circuits.rs

@ -251,6 +251,7 @@ pub struct AugmentedFCircuit<
pub u_i: Option<CommittedInstance<C1>>,
pub U_i: Option<CommittedInstance<C1>>,
pub U_i1: Option<CommittedInstance<C1>>,
pub r_nonnat: Option<CF2<C1>>,
pub cmT: Option<C1>,
pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x)
@ -285,6 +286,7 @@ where
u_i: None,
U_i: None,
U_i1: None,
r_nonnat: None,
cmT: None,
F: F_circuit,
x: None,
@ -340,6 +342,10 @@ where
let U_i1 = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
})?;
let r_nonnat =
NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
Ok(self.r_nonnat.unwrap_or_else(CF2::<C1>::zero))
})?;
let cmT =
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
let x =
@ -363,7 +369,6 @@ where
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
(u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
@ -391,6 +396,11 @@ where
)?;
let r = Boolean::le_bits_to_fp_var(&r_bits)?;
// enforce that the input r_nonnat as bits matches the in-circuit computed r_bits
let r_nonnat_bits: Vec<Boolean<C1::ScalarField>> =
r_nonnat.to_bits_le()?.into_iter().take(N_BITS_RO).collect();
r_nonnat_bits.enforce_equal(&r_bits)?;
// Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
// be done on the other curve.
let nifs_check = NIFSGadget::<C1>::verify(r, U_i.clone(), u_i.clone(), U_i1.clone())?;
@ -436,10 +446,16 @@ where
})?;
let cfW_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
U_i.cmW.x, U_i.cmW.y, u_i.cmW.x, u_i.cmW.y, U_i1.cmW.x, U_i1.cmW.y,
r_nonnat.clone(),
U_i.cmW.x,
U_i.cmW.y,
u_i.cmW.x,
u_i.cmW.y,
U_i1.cmW.x,
U_i1.cmW.y,
];
let cfE_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
U_i.cmE.x, U_i.cmE.y, u_i.cmE.x, u_i.cmE.y, U_i1.cmE.x, U_i1.cmE.y,
r_nonnat, U_i.cmE.x, U_i.cmE.y, cmT.x, cmT.y, U_i1.cmE.x, U_i1.cmE.y,
];
// ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i,

+ 22
- 15
folding-schemes/src/folding/nova/cyclefold.rs

@ -14,7 +14,7 @@ use ark_r1cs_std::{
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
groups::GroupOpsBounds,
prelude::CurveVar,
ToBytesGadget,
ToBytesGadget, ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_serialize::CanonicalSerialize;
@ -27,8 +27,8 @@ use super::CommittedInstance;
use crate::constants::N_BITS_RO;
use crate::Error;
// publi inputs length for the CycleFoldCircuit, |[p1.x,y, p2.x,y, p3.x,y]|
pub const CF_IO_LEN: usize = 6;
// public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]|
pub const CF_IO_LEN: usize = 7;
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova
/// circuit.
@ -318,7 +318,7 @@ impl>> CycleFoldCircuit {
impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
@ -330,11 +330,22 @@ where
let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?;
let p3 = GC::new_witness(cs.clone(), || Ok(self.p3.unwrap_or(C::zero())))?;
let _x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
let x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
})?;
#[cfg(test)]
assert_eq!(_x.len(), CF_IO_LEN); // non-constrained sanity check
assert_eq!(x.len(), CF_IO_LEN); // non-constrained sanity check
// check that the points coordinates are placed as the public input x: x == [r, p1, p2, p3]
let r: FpVar<CF2<C>> = Boolean::le_bits_to_fp_var(&r_bits)?;
let points_coords: Vec<FpVar<CF2<C>>> = [
vec![r],
p1.clone().to_constraint_field()?[..2].to_vec(),
p2.clone().to_constraint_field()?[..2].to_vec(),
p3.clone().to_constraint_field()?[..2].to_vec(),
]
.concat();
points_coords.enforce_equal(&x)?;
// Fold the original Nova instances natively in CycleFold
// For the cmW we're checking: U_i1.cmW == U_i.cmW + r * u_i.cmW
@ -342,12 +353,6 @@ where
// is assumed to be 0, so, U_i1.cmE == U_i.cmE + r * cmT
p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?;
// check that x == [u_i, U_i, U_{i+1}], check that the cmW & cmW from u_i, U_i, U_{i+1} in
// 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
// and https://github.com/privacy-scaling-explorations/folding-schemes/issues/48
Ok(())
}
}
@ -390,12 +395,14 @@ pub mod tests {
#[test]
fn test_CycleFoldCircuit_constraints() {
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
let r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
// cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
// (E1::Fq=E2::Fr)
let cs = ConstraintSystem::<Fq>::new_ref();
let cfW_u_i_x = [
let cfW_u_i_x: Vec<Fq> = [
vec![r_Fq],
get_cm_coordinates(&ci1.cmW),
get_cm_coordinates(&ci2.cmW),
get_cm_coordinates(&ci3.cmW),
@ -411,13 +418,13 @@ pub mod tests {
};
cfW_circuit.generate_constraints(cs.clone()).unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!(cs.num_constraints());
// same for E:
let cs = ConstraintSystem::<Fq>::new_ref();
let cfE_u_i_x = [
vec![r_Fq],
get_cm_coordinates(&ci1.cmE),
get_cm_coordinates(&ci2.cmE),
get_cm_coordinates(&cmT),
get_cm_coordinates(&ci3.cmE),
]
.concat();

+ 11
- 49
folding-schemes/src/folding/nova/decider_eth.rs

@ -1,20 +1,16 @@
/// This file implements the onchain (Ethereum's EVM) decider.
use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_ff::PrimeField;
use ark_r1cs_std::fields::nonnative::params::OptimizationType;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_snark::SNARK;
use ark_std::rand::{CryptoRng, RngCore};
use ark_std::Zero;
use core::marker::PhantomData;
pub use super::decider_eth_circuit::{DeciderEthCircuit, KZGChallengesGadget};
use super::{
circuits::{ChallengeGadget, CF2},
nifs::NIFS,
CommittedInstance, Nova, Witness,
};
use super::{circuits::CF2, nifs::NIFS, CommittedInstance, Nova};
use crate::commitment::{
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
};
@ -60,7 +56,7 @@ impl DeciderTrait
where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<
@ -82,11 +78,7 @@ where
// constrain FS into Nova, since this is a Decider specifically for Nova
Nova<C1, GC1, C2, GC2, FC, CS1, CS2>: From<FS>,
{
type ProverParam = (
PoseidonConfig<C1::ScalarField>,
S::ProvingKey,
CS1::ProverParams,
);
type ProverParam = (S::ProvingKey, CS1::ProverParams);
type Proof = Proof<C1, CS1, S>;
type VerifierParam = (S::VerifyingKey, CS1::VerifierParams);
type PublicInput = Vec<C1::ScalarField>;
@ -98,11 +90,7 @@ where
mut rng: impl RngCore + CryptoRng,
folding_scheme: FS,
) -> Result<Self::Proof, Error> {
let (poseidon_config, snark_pk, cs_pk): (
PoseidonConfig<C1::ScalarField>,
S::ProvingKey,
CS1::ProverParams,
) = pp;
let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(
folding_scheme.into(),
@ -111,35 +99,9 @@ where
let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
.map_err(|e| Error::Other(e.to_string()))?;
let U_i = circuit
.U_i
.clone()
.ok_or(Error::MissingValue("U_i".to_string()))?;
let W_i = circuit
.W_i
.clone()
.ok_or(Error::MissingValue("W_i".to_string()))?;
let u_i = circuit
.u_i
.clone()
.ok_or(Error::MissingValue("u_i".to_string()))?;
let w_i = circuit
.w_i
.clone()
.ok_or(Error::MissingValue("w_i".to_string()))?;
// compute NIFS.P((U_d, W_d), (u_d, w_d)) = (U_{d+1}, W_{d+1}, cmT)
let (T, cmT) = NIFS::<C1, CS1>::compute_cmT(&cs_pk, &circuit.r1cs, &w_i, &u_i, &W_i, &U_i)?;
let r_bits = ChallengeGadget::<C1>::get_challenge_native(
&poseidon_config,
U_i.clone(),
u_i.clone(),
cmT,
)?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?;
let (W_i1, _): (Witness<C1>, CommittedInstance<C1>) =
NIFS::<C1, CS1>::fold_instances(r_Fr, &W_i, &U_i, &w_i, &u_i, &T, cmT)?;
let cmT = circuit.cmT.unwrap();
let r_Fr = circuit.r.unwrap();
let W_i1 = circuit.W_i1.unwrap();
// get the challenges that have been already computed when preparing the circuit inputs in
// the above `from_nova` call
@ -332,7 +294,7 @@ pub mod tests {
// decider proof generation
let start = Instant::now();
let decider_pp = (poseidon_config.clone(), g16_pk, kzg_pk);
let decider_pp = (g16_pk, kzg_pk);
let proof = DECIDER::prove(decider_pp, rng, nova.clone()).unwrap();
println!("Decider prove, {:?}", start.elapsed());

+ 2
- 3
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -244,7 +244,7 @@ impl DeciderEthCircuit
where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>>,
CS1: CommitmentScheme<C1>,
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
@ -551,6 +551,7 @@ fn evaluate_gadget(
pub struct KZGChallengesGadget<C: CurveGroup> {
_c: PhantomData<C>,
}
#[allow(clippy::type_complexity)]
impl<C> KZGChallengesGadget<C>
where
C: CurveGroup,
@ -580,7 +581,6 @@ where
Ok((challenge_W, challenge_E))
}
// compatible with the native get_challenges_native
#[allow(clippy::type_complexity)]
pub fn get_challenges_gadget(
cs: ConstraintSystemRef<C::ScalarField>,
poseidon_config: &PoseidonConfig<C::ScalarField>,
@ -860,7 +860,6 @@ pub mod tests {
// generate the constraints and check that are satisfied by the inputs
decider_circuit.generate_constraints(cs.clone()).unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!(cs.num_constraints());
}
// checks that the gadget and native implementations of the challenge computation match

+ 18
- 13
folding-schemes/src/folding/nova/mod.rs

@ -6,7 +6,7 @@ use ark_crypto_primitives::{
};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_std::fmt::Debug;
use ark_std::{One, Zero};
use core::marker::PhantomData;
@ -160,7 +160,7 @@ pub struct VerifierParams {
pub struct Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
@ -201,7 +201,7 @@ impl FoldingScheme
for Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
@ -289,8 +289,6 @@ where
/// Implements IVC.P of Nova+CycleFold
fn prove_step(&mut self) -> Result<(), Error> {
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
let cfW_circuit: CycleFoldCircuit<C1, GC1>;
let cfE_circuit: CycleFoldCircuit<C1, GC1>;
if self.i > C1::ScalarField::from_le_bytes_mod_order(&std::usize::MAX.to_le_bytes()) {
return Err(Error::MaxStep);
@ -313,6 +311,8 @@ where
)?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?;
let r_Fq = C1::BaseField::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, CS1>::fold_instances(
@ -340,6 +340,7 @@ where
u_i: Some(self.u_i.clone()), // = dummy
U_i: Some(self.U_i.clone()), // = dummy
U_i1: Some(U_i1.clone()),
r_nonnat: Some(r_Fq),
cmT: Some(cmT),
F: self.F.clone(),
x: Some(u_i1_x),
@ -361,6 +362,7 @@ where
// get the vector used as public inputs 'x' in the CycleFold circuit
// cyclefold circuit for cmW
let cfW_u_i_x = [
vec![r_Fq],
get_cm_coordinates(&self.U_i.cmW),
get_cm_coordinates(&self.u_i.cmW),
get_cm_coordinates(&U_i1.cmW),
@ -368,13 +370,14 @@ where
.concat();
// cyclefold circuit for cmE
let cfE_u_i_x = [
vec![r_Fq],
get_cm_coordinates(&self.U_i.cmE),
get_cm_coordinates(&self.u_i.cmE),
get_cm_coordinates(&cmT),
get_cm_coordinates(&U_i1.cmE),
]
.concat();
cfW_circuit = CycleFoldCircuit::<C1, GC1> {
let cfW_circuit = CycleFoldCircuit::<C1, GC1> {
_gc: PhantomData,
r_bits: Some(r_bits.clone()),
p1: Some(self.U_i.clone().cmW),
@ -382,7 +385,7 @@ where
p3: Some(U_i1.clone().cmW),
x: Some(cfW_u_i_x.clone()),
};
cfE_circuit = CycleFoldCircuit::<C1, GC1> {
let cfE_circuit = CycleFoldCircuit::<C1, GC1> {
_gc: PhantomData,
r_bits: Some(r_bits.clone()),
p1: Some(self.U_i.clone().cmE),
@ -413,6 +416,7 @@ where
u_i: Some(self.u_i.clone()),
U_i: Some(self.U_i.clone()),
U_i1: Some(U_i1.clone()),
r_nonnat: Some(r_Fq),
cmT: Some(cmT),
F: self.F.clone(),
x: Some(u_i1_x),
@ -539,7 +543,7 @@ where
impl<C1, GC1, C2, GC2, FC, CS1, CS2> Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
@ -583,7 +587,7 @@ where
impl<C1, GC1, C2, GC2, FC, CS1, CS2> Nova<C1, GC1, C2, GC2, FC, CS1, CS2>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
@ -674,7 +678,7 @@ pub fn get_r1cs(
) -> Result<(R1CS<C1::ScalarField>, R1CS<C2::ScalarField>), Error>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
@ -702,7 +706,7 @@ pub fn get_cs_params_len(
) -> Result<(usize, usize), Error>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
@ -718,7 +722,8 @@ where
Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows))
}
/// returns the coordinates of a commitment point
/// 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 cm = cm.into_affine();

+ 0
- 3
folding-schemes/src/utils/mle.rs

@ -51,8 +51,6 @@ pub fn vec_to_mle(n_vars: usize, v: &Vec) -> DenseMultilinearE
}
pub fn dense_vec_to_mle<F: PrimeField>(n_vars: usize, v: &Vec<F>) -> DenseMultilinearExtension<F> {
dbg!(n_vars);
dbg!(v.len());
// Pad to 2^n_vars
let v_padded: Vec<F> = [
v.clone(),
@ -88,7 +86,6 @@ mod tests {
]);
let A_mle = matrix_to_mle(A);
dbg!(&A_mle);
assert_eq!(A_mle.evaluations.len(), 16); // 4x4 matrix, thus 2bit x 2bit, thus 2^4=16 evals
let A = to_F_matrix::<Fr>(vec![

Loading…
Cancel
Save