Browse Source

Protogalaxy based IVC (#123)

* Parallelize vector and matrix operations

* Implement convenient methods for `NonNativeAffineVar`

* Return `L_X_evals` and intermediate `phi_star`s from ProtoGalaxy prover.

These values will be used as hints to the augmented circuit

* Correctly use number of variables, number of constraints, and `t`

* Fix the size of `F_coeffs` and `K_coeffs` for in-circuit consistency

* Improve prover's performance

* Make `prepare_inputs` generic

* Remove redundant parameters in verifier

* Move `eval_f` to arith

* `u` is unnecessary in ProtoGalaxy

* Convert `RelaxedR1CS` to a trait that can be used in both Nova and ProtoGalaxy

* Implement several traits for ProtoGalaxy

* Move `FCircuit` impls to `utils.rs` and add `DummyCircuit`

* `AugmentedFCircuit` and ProtoGalaxy-based IVC

* Add explanations about IVC prover and in-circuit operations

* Avoid using unstable features

* Rename `PROTOGALAXY` to `PG` to make clippy happy

* Fix merge conflicts in `RelaxedR1CS::sample`

* Fix merge conflicts in `CycleFoldCircuit`

* Swap `m` and `n` for protogalaxy

* Add `#[cfg(test)]` to test-only util circuits

* Prefer unit struct over empty struct

* Add documents to `AugmentedFCircuit` for ProtoGalaxy

* Fix the names for CycleFold cricuits in ProtoGalaxy

* Fix usize conversion when targeting wasm

* Restrict the visibility of fields in `AugmentedFCircuit` to `pub(super)`

* Make CycleFold circuits and configs public

* Add docs for `ProverParams` and `VerifierParams`

* Refactor `pow_i`

* Fix imports

* Remove lint reasons

* Fix type inference
main
winderica 2 months ago
committed by GitHub
parent
commit
1322767a1e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
26 changed files with 2218 additions and 691 deletions
  1. +20
    -12
      folding-schemes/src/arith/ccs.rs
  2. +15
    -2
      folding-schemes/src/arith/mod.rs
  3. +75
    -115
      folding-schemes/src/arith/r1cs.rs
  4. +7
    -2
      folding-schemes/src/folding/circuits/cyclefold.rs
  5. +42
    -4
      folding-schemes/src/folding/circuits/nonnative/affine.rs
  6. +26
    -29
      folding-schemes/src/folding/hypernova/circuits.rs
  7. +1
    -1
      folding-schemes/src/folding/hypernova/decider_eth.rs
  8. +1
    -1
      folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
  9. +23
    -10
      folding-schemes/src/folding/hypernova/mod.rs
  10. +21
    -21
      folding-schemes/src/folding/nova/circuits.rs
  11. +1
    -1
      folding-schemes/src/folding/nova/decider_eth.rs
  12. +52
    -19
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  13. +35
    -32
      folding-schemes/src/folding/nova/mod.rs
  14. +14
    -15
      folding-schemes/src/folding/nova/nifs.rs
  15. +1
    -1
      folding-schemes/src/folding/nova/serialize.rs
  16. +72
    -51
      folding-schemes/src/folding/nova/traits.rs
  17. +9
    -26
      folding-schemes/src/folding/nova/zk.rs
  18. +392
    -29
      folding-schemes/src/folding/protogalaxy/circuits.rs
  19. +95
    -162
      folding-schemes/src/folding/protogalaxy/folding.rs
  20. +990
    -11
      folding-schemes/src/folding/protogalaxy/mod.rs
  21. +74
    -4
      folding-schemes/src/folding/protogalaxy/traits.rs
  22. +57
    -0
      folding-schemes/src/folding/protogalaxy/utils.rs
  23. +2
    -2
      folding-schemes/src/frontend/circom/mod.rs
  24. +2
    -123
      folding-schemes/src/frontend/mod.rs
  25. +181
    -0
      folding-schemes/src/frontend/utils.rs
  26. +10
    -18
      folding-schemes/src/utils/vec.rs

+ 20
- 12
folding-schemes/src/arith/ccs.rs

@ -36,8 +36,7 @@ pub struct CCS {
} }
impl<F: PrimeField> Arith<F> for CCS<F> { impl<F: PrimeField> Arith<F> for CCS<F> {
/// check that a CCS structure is satisfied by a z vector. Only for testing.
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error> {
let mut result = vec![F::zero(); self.m]; let mut result = vec![F::zero(); self.m];
for i in 0..self.q { for i in 0..self.q {
@ -57,14 +56,7 @@ impl Arith for CCS {
result = vec_add(&result, &c_M_j_z)?; result = vec_add(&result, &c_M_j_z)?;
} }
// make sure the final vector is all zeroes
for e in result {
if !e.is_zero() {
return Err(Error::NotSatisfied);
}
}
Ok(())
Ok(result)
} }
fn params_to_le_bytes(&self) -> Vec<u8> { fn params_to_le_bytes(&self) -> Vec<u8> {
@ -113,7 +105,10 @@ impl CCS {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z};
use crate::{
arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z},
utils::vec::is_zero_vec,
};
use ark_pallas::Fr; use ark_pallas::Fr;
pub fn get_test_ccs<F: PrimeField>() -> CCS<F> { pub fn get_test_ccs<F: PrimeField>() -> CCS<F> {
@ -124,9 +119,22 @@ pub mod tests {
r1cs_get_test_z(input) r1cs_get_test_z(input)
} }
#[test]
fn test_eval_ccs_relation() {
let ccs = get_test_ccs::<Fr>();
let mut z = get_test_z(3);
let f_w = ccs.eval_relation(&z).unwrap();
assert!(is_zero_vec(&f_w));
z[1] = Fr::from(111);
let f_w = ccs.eval_relation(&z).unwrap();
assert!(!is_zero_vec(&f_w));
}
/// Test that a basic CCS relation can be satisfied /// Test that a basic CCS relation can be satisfied
#[test] #[test]
fn test_ccs_relation() {
fn test_check_ccs_relation() {
let ccs = get_test_ccs::<Fr>(); let ccs = get_test_ccs::<Fr>();
let z = get_test_z(3); let z = get_test_z(3);

+ 15
- 2
folding-schemes/src/arith/mod.rs

@ -6,8 +6,21 @@ pub mod ccs;
pub mod r1cs; pub mod r1cs;
pub trait Arith<F: PrimeField> { pub trait Arith<F: PrimeField> {
/// Checks that the given Arith structure is satisfied by a z vector. Used only for testing.
fn check_relation(&self, z: &[F]) -> Result<(), Error>;
/// Evaluate the given Arith structure at `z`, a vector of assignments, and
/// return the evaluation.
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error>;
/// Checks that the given Arith structure is satisfied by a z vector, i.e.,
/// if the evaluation is a zero vector
///
/// Used only for testing.
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
if self.eval_relation(z)?.iter().all(|f| f.is_zero()) {
Ok(())
} else {
Err(Error::NotSatisfied)
}
}
/// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of /// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of
/// public inputs, etc, without the matrices/polynomials values. /// public inputs, etc, without the matrices/polynomials values.

+ 75
- 115
folding-schemes/src/arith/r1cs.rs

@ -1,15 +1,13 @@
use crate::commitment::CommitmentScheme; use crate::commitment::CommitmentScheme;
use crate::folding::nova::{CommittedInstance, Witness};
use crate::RngCore; use crate::RngCore;
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ec::CurveGroup;
use ark_ff::PrimeField; use ark_ff::PrimeField;
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::rand::Rng; use ark_std::rand::Rng;
use super::Arith; use super::Arith;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub, SparseMatrix};
use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub, SparseMatrix};
use crate::Error; use crate::Error;
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
@ -21,16 +19,24 @@ pub struct R1CS {
} }
impl<F: PrimeField> Arith<F> for R1CS<F> { impl<F: PrimeField> Arith<F> for R1CS<F> {
/// check that a R1CS structure is satisfied by a z vector. Only for testing.
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error> {
if z.len() != self.A.n_cols {
return Err(Error::NotSameLength(
"z.len()".to_string(),
z.len(),
"number of variables in R1CS".to_string(),
self.A.n_cols,
));
}
let Az = mat_vec_mul(&self.A, z)?; let Az = mat_vec_mul(&self.A, z)?;
let Bz = mat_vec_mul(&self.B, z)?; let Bz = mat_vec_mul(&self.B, z)?;
let Cz = mat_vec_mul(&self.C, z)?; let Cz = mat_vec_mul(&self.C, z)?;
// Multiply Cz by z[0] (u) here, allowing this method to be reused for
// both relaxed and unrelaxed R1CS.
let uCz = vec_scalar_mul(&Cz, &z[0]);
let AzBz = hadamard(&Az, &Bz)?; let AzBz = hadamard(&Az, &Bz)?;
if AzBz != Cz {
return Err(Error::NotSatisfied);
}
Ok(())
vec_sub(&AzBz, &uCz)
} }
fn params_to_le_bytes(&self) -> Vec<u8> { fn params_to_le_bytes(&self) -> Vec<u8> {
@ -65,55 +71,50 @@ impl R1CS {
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) { pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec()) (z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
} }
/// converts the R1CS instance into a RelaxedR1CS as described in
/// [Nova](https://eprint.iacr.org/2021/370.pdf) section 4.1.
pub fn relax(self) -> RelaxedR1CS<F> {
RelaxedR1CS::<F> {
l: self.l,
E: vec![F::zero(); self.A.n_rows],
A: self.A,
B: self.B,
C: self.C,
u: F::one(),
}
}
} }
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RelaxedR1CS<F: PrimeField> {
pub l: usize, // io len
pub A: SparseMatrix<F>,
pub B: SparseMatrix<F>,
pub C: SparseMatrix<F>,
pub u: F,
pub E: Vec<F>,
}
pub trait RelaxedR1CS<C: CurveGroup, W, U>: Arith<C::ScalarField> {
/// returns a dummy running instance (Witness and CommittedInstance) for the current R1CS structure
fn dummy_running_instance(&self) -> (W, U);
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(&self.A, z)?;
let Bz = mat_vec_mul(&self.B, z)?;
let Cz = mat_vec_mul(&self.C, z)?;
let uCz = vec_scalar_mul(&Cz, &self.u);
let uCzE = vec_add(&uCz, &self.E)?;
let AzBz = hadamard(&Az, &Bz)?;
if AzBz != uCzE {
return Err(Error::NotSatisfied);
/// returns a dummy incoming instance (Witness and CommittedInstance) for the current R1CS structure
fn dummy_incoming_instance(&self) -> (W, U);
/// checks if the given instance is relaxed
fn is_relaxed(w: &W, u: &U) -> bool;
/// extracts `z`, the vector of variables, from the given Witness and CommittedInstance
fn extract_z(w: &W, u: &U) -> Vec<C::ScalarField>;
/// checks if the computed error terms correspond to the actual one in `w`
/// or `u`
fn check_error_terms(w: &W, u: &U, e: Vec<C::ScalarField>) -> Result<(), Error>;
/// checks the tight (unrelaxed) R1CS relation
fn check_tight_relation(&self, w: &W, u: &U) -> Result<(), Error> {
if Self::is_relaxed(w, u) {
return Err(Error::R1CSUnrelaxedFail);
} }
Ok(())
let z = Self::extract_z(w, u);
self.check_relation(&z)
}
/// checks the relaxed R1CS relation
fn check_relaxed_relation(&self, w: &W, u: &U) -> Result<(), Error> {
let z = Self::extract_z(w, u);
let e = self.eval_relation(&z)?;
Self::check_error_terms(w, u, e)
} }
// Computes the E term, given A, B, C, z, u // Computes the E term, given A, B, C, z, u
fn compute_E( fn compute_E(
A: &SparseMatrix<F>,
B: &SparseMatrix<F>,
C: &SparseMatrix<F>,
z: &[F],
u: &F,
) -> Result<Vec<F>, Error> {
A: &SparseMatrix<C::ScalarField>,
B: &SparseMatrix<C::ScalarField>,
C: &SparseMatrix<C::ScalarField>,
z: &[C::ScalarField],
u: &C::ScalarField,
) -> Result<Vec<C::ScalarField>, Error> {
let Az = mat_vec_mul(A, z)?; let Az = mat_vec_mul(A, z)?;
let Bz = mat_vec_mul(B, z)?; let Bz = mat_vec_mul(B, z)?;
let AzBz = hadamard(&Az, &Bz)?; let AzBz = hadamard(&Az, &Bz)?;
@ -123,66 +124,9 @@ impl RelaxedR1CS {
vec_sub(&AzBz, &uCz) vec_sub(&AzBz, &uCz)
} }
pub fn check_sampled_relaxed_r1cs(&self, u: F, E: &[F], z: &[F]) -> bool {
let sampled = RelaxedR1CS {
l: self.l,
A: self.A.clone(),
B: self.B.clone(),
C: self.C.clone(),
u,
E: E.to_vec(),
};
sampled.check_relation(z).is_ok()
}
// Implements sampling a (committed) RelaxedR1CS
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
pub fn sample<C, CS>(
&self,
params: &CS::ProverParams,
mut rng: impl RngCore,
) -> Result<(CommittedInstance<C>, Witness<C>), Error>
fn sample<CS>(&self, params: &CS::ProverParams, rng: impl RngCore) -> Result<(W, U), Error>
where where
C: CurveGroup,
C: CurveGroup<ScalarField = F>,
<C as Group>::ScalarField: Absorb,
CS: CommitmentScheme<C, true>,
{
let u = C::ScalarField::rand(&mut rng);
let rE = C::ScalarField::rand(&mut rng);
let rW = C::ScalarField::rand(&mut rng);
let W = (0..self.A.n_cols - self.l - 1)
.map(|_| F::rand(&mut rng))
.collect();
let x = (0..self.l).map(|_| F::rand(&mut rng)).collect::<Vec<F>>();
let mut z = vec![u];
z.extend(&x);
z.extend(&W);
let E = RelaxedR1CS::compute_E(&self.A, &self.B, &self.C, &z, &u)?;
debug_assert!(
z.len() == self.A.n_cols,
"Length of z is {}, while A has {} columns.",
z.len(),
self.A.n_cols
);
debug_assert!(
self.check_sampled_relaxed_r1cs(u, &E, &z),
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
u,
E
);
let witness = Witness { E, rE, W, rW };
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
// witness.commit() sets u to 1, we set it to the sampled u value
cm_witness.u = u;
Ok((cm_witness, witness))
}
CS: CommitmentScheme<C, true>;
} }
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS /// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS
@ -229,9 +173,13 @@ pub fn extract_w_x(cs: &ConstraintSystem) -> (Vec, Vec)
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::folding::nova::{CommittedInstance, Witness};
use crate::{ use crate::{
commitment::pedersen::Pedersen, commitment::pedersen::Pedersen,
utils::vec::tests::{to_F_matrix, to_F_vec},
utils::vec::{
is_zero_vec,
tests::{to_F_matrix, to_F_vec},
},
}; };
use ark_pallas::{Fr, Projective}; use ark_pallas::{Fr, Projective};
@ -242,9 +190,8 @@ pub mod tests {
let r1cs = get_test_r1cs::<Fr>(); let r1cs = get_test_r1cs::<Fr>();
let (prover_params, _) = Pedersen::<Projective>::setup(rng, r1cs.A.n_rows).unwrap(); let (prover_params, _) = Pedersen::<Projective>::setup(rng, r1cs.A.n_rows).unwrap();
let relaxed_r1cs = r1cs.relax();
let sampled =
relaxed_r1cs.sample::<Projective, Pedersen<Projective, true>>(&prover_params, rng);
let sampled: Result<(Witness<Projective>, CommittedInstance<Projective>), _> =
r1cs.sample::<Pedersen<Projective, true>>(&prover_params, rng);
assert!(sampled.is_ok()); assert!(sampled.is_ok());
} }
@ -302,10 +249,23 @@ pub mod tests {
} }
#[test] #[test]
fn test_check_relation() {
fn test_eval_r1cs_relation() {
let mut rng = ark_std::test_rng();
let r1cs = get_test_r1cs::<Fr>();
let mut z = get_test_z::<Fr>(rng.gen::<u16>() as usize);
let f_w = r1cs.eval_relation(&z).unwrap();
assert!(is_zero_vec(&f_w));
z[1] = Fr::from(111);
let f_w = r1cs.eval_relation(&z).unwrap();
assert!(!is_zero_vec(&f_w));
}
#[test]
fn test_check_r1cs_relation() {
let r1cs = get_test_r1cs::<Fr>(); let r1cs = get_test_r1cs::<Fr>();
let z = get_test_z(5); let z = get_test_z(5);
r1cs.check_relation(&z).unwrap(); r1cs.check_relation(&z).unwrap();
r1cs.relax().check_relation(&z).unwrap();
} }
} }

+ 7
- 2
folding-schemes/src/folding/circuits/cyclefold.rs

@ -360,6 +360,8 @@ where
} }
} }
/// `CycleFoldConfig` allows us to customize the behavior of CycleFold circuit
/// according to the folding scheme we are working with.
pub trait CycleFoldConfig { pub trait CycleFoldConfig {
/// `N_INPUT_POINTS` specifies the number of input points that are folded in /// `N_INPUT_POINTS` specifies the number of input points that are folded in
/// [`CycleFoldCircuit`] via random linear combinations. /// [`CycleFoldCircuit`] via random linear combinations.
@ -465,7 +467,10 @@ where
// In multifolding schemes such as HyperNova, this is: // In multifolding schemes such as HyperNova, this is:
// computed_x = [r, p_0, p_1, p_2, ..., p_n, p_folded], // computed_x = [r, p_0, p_1, p_2, ..., p_n, p_folded],
// where each p_i is in fact p_i.to_constraint_field() // where each p_i is in fact p_i.to_constraint_field()
let r_fp = Boolean::le_bits_to_fp_var(&r_bits)?;
let r_fp = r_bits
.chunks(CFG::F::MODULUS_BIT_SIZE as usize - 1)
.map(Boolean::le_bits_to_fp_var)
.collect::<Result<Vec<_>, _>>()?;
let points_aux: Vec<FpVar<CFG::F>> = points let points_aux: Vec<FpVar<CFG::F>> = points
.iter() .iter()
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())) .map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec()))
@ -475,7 +480,7 @@ where
.collect(); .collect();
let computed_x: Vec<FpVar<CFG::F>> = [ let computed_x: Vec<FpVar<CFG::F>> = [
vec![r_fp],
r_fp,
points_aux, points_aux,
p_folded.to_constraint_field()?[..2].to_vec(), p_folded.to_constraint_field()?[..2].to_vec(),
] ]

+ 42
- 4
folding-schemes/src/folding/circuits/nonnative/affine.rs

@ -1,10 +1,12 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_ec::{short_weierstrass::SWFlags, AffineRepr, CurveGroup};
use ark_ff::{Field, PrimeField};
use ark_r1cs_std::{ use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode}, alloc::{AllocVar, AllocationMode},
fields::fp::FpVar, fields::fp::FpVar,
ToConstraintFieldGadget,
R1CSVar, ToConstraintFieldGadget,
}; };
use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};
use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags};
use ark_std::Zero; use ark_std::Zero;
use core::borrow::Borrow; use core::borrow::Borrow;
@ -45,6 +47,39 @@ where
} }
} }
impl<C: CurveGroup> R1CSVar<C::ScalarField> for NonNativeAffineVar<C> {
type Value = C;
fn cs(&self) -> ConstraintSystemRef<C::ScalarField> {
self.x.cs().or(self.y.cs())
}
fn value(&self) -> Result<Self::Value, SynthesisError> {
debug_assert_eq!(C::BaseField::extension_degree(), 1);
let x = <C::BaseField as Field>::BasePrimeField::from_le_bytes_mod_order(
&self.x.value()?.to_bytes_le(),
);
let y = <C::BaseField as Field>::BasePrimeField::from_le_bytes_mod_order(
&self.y.value()?.to_bytes_le(),
);
let mut bytes = vec![];
x.serialize_uncompressed(&mut bytes).unwrap();
y.serialize_with_flags(
&mut bytes,
if x.is_zero() && y.is_zero() {
SWFlags::PointAtInfinity
} else if y <= -y {
SWFlags::YIsPositive
} else {
SWFlags::YIsNegative
},
)
.unwrap();
Ok(C::deserialize_uncompressed_unchecked(&bytes[..]).unwrap())
}
}
impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C> { impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C> {
// Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in // Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
// the circuit. // the circuit.
@ -83,6 +118,10 @@ impl NonNativeAffineVar {
let y = NonNativeUintVar::inputize(*y); let y = NonNativeUintVar::inputize(*y);
Ok((x, y)) Ok((x, y))
} }
pub fn zero() -> Self {
Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap()
}
} }
impl<C: CurveGroup> AbsorbNonNative<C::ScalarField> for C { impl<C: CurveGroup> AbsorbNonNative<C::ScalarField> for C {
@ -105,7 +144,6 @@ impl AbsorbNonNativeGadget for NonNativeAffineVar
mod tests { mod tests {
use super::*; use super::*;
use ark_pallas::{Fr, Projective}; use ark_pallas::{Fr, Projective};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand; use ark_std::UniformRand;

+ 26
- 29
folding-schemes/src/folding/hypernova/circuits.rs

@ -475,30 +475,30 @@ pub struct AugmentedFCircuit<
> where > where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{ {
pub _c2: PhantomData<C2>,
pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit
pub pp_hash: Option<CF1<C1>>,
pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>,
pub z_i: Option<Vec<C1::ScalarField>>,
pub external_inputs: Option<Vec<C1::ScalarField>>,
pub U_i: Option<LCCCS<C1>>,
pub Us: Option<Vec<LCCCS<C1>>>, // other U_i's to be folded that are not the main running instance
pub u_i_C: Option<C1>, // u_i.C
pub us: Option<Vec<CCCS<C1>>>, // other u_i's to be folded that are not the main incoming instance
pub U_i1_C: Option<C1>, // U_{i+1}.C
pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
pub nimfs_proof: Option<NIMFSProof<C1>>,
pub(super) _c2: PhantomData<C2>,
pub(super) _gc2: PhantomData<GC2>,
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
pub(super) ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit
pub(super) pp_hash: Option<CF1<C1>>,
pub(super) i: Option<CF1<C1>>,
pub(super) i_usize: Option<usize>,
pub(super) z_0: Option<Vec<C1::ScalarField>>,
pub(super) z_i: Option<Vec<C1::ScalarField>>,
pub(super) external_inputs: Option<Vec<C1::ScalarField>>,
pub(super) U_i: Option<LCCCS<C1>>,
pub(super) Us: Option<Vec<LCCCS<C1>>>, // other U_i's to be folded that are not the main running instance
pub(super) u_i_C: Option<C1>, // u_i.C
pub(super) us: Option<Vec<CCCS<C1>>>, // other u_i's to be folded that are not the main incoming instance
pub(super) U_i1_C: Option<C1>, // U_{i+1}.C
pub(super) F: FC, // F circuit
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
pub(super) nimfs_proof: Option<NIMFSProof<C1>>,
// cyclefold verifier on C1 // cyclefold verifier on C1
pub cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance
pub cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1])
pub cf_cmT: Option<C2>,
pub(super) cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW
pub(super) cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance
pub(super) cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1])
pub(super) cf_cmT: Option<C2>,
} }
impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> AugmentedFCircuit<C1, C2, GC2, FC, MU, NU> impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>
@ -891,7 +891,7 @@ mod tests {
use crate::{ use crate::{
arith::{ arith::{
ccs::tests::{get_test_ccs, get_test_z}, ccs::tests::{get_test_ccs, get_test_z},
r1cs::extract_w_x,
r1cs::{extract_w_x, RelaxedR1CS},
}, },
commitment::{pedersen::Pedersen, CommitmentScheme}, commitment::{pedersen::Pedersen, CommitmentScheme},
folding::{ folding::{
@ -900,9 +900,8 @@ mod tests {
utils::{compute_c, compute_sigmas_thetas}, utils::{compute_c, compute_sigmas_thetas},
HyperNovaCycleFoldCircuit, HyperNovaCycleFoldCircuit,
}, },
nova::traits::NovaR1CS,
}, },
frontend::tests::CubicFCircuit,
frontend::utils::CubicFCircuit,
transcript::poseidon::poseidon_canonical_config, transcript::poseidon::poseidon_canonical_config,
utils::get_cm_coordinates, utils::get_cm_coordinates,
}; };
@ -1216,7 +1215,7 @@ mod tests {
let (cf_W_dummy, cf_U_dummy): ( let (cf_W_dummy, cf_U_dummy): (
CycleFoldWitness<Projective2>, CycleFoldWitness<Projective2>,
CycleFoldCommittedInstance<Projective2>, CycleFoldCommittedInstance<Projective2>,
) = cf_r1cs.dummy_instance();
) = cf_r1cs.dummy_running_instance();
// set the initial dummy instances // set the initial dummy instances
let mut W_i = W_dummy.clone(); let mut W_i = W_dummy.clone();
@ -1455,9 +1454,7 @@ mod tests {
u_i.check_relation(&ccs, &w_i).unwrap(); u_i.check_relation(&ccs, &w_i).unwrap();
// check the CycleFold instance relation // check the CycleFold instance relation
cf_r1cs
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)
.unwrap();
cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i).unwrap();
println!("augmented_f_circuit step {}: {:?}", i, start.elapsed()); println!("augmented_f_circuit step {}: {:?}", i, start.elapsed());
} }

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

@ -228,7 +228,7 @@ pub mod tests {
use super::*; use super::*;
use crate::commitment::{kzg::KZG, pedersen::Pedersen}; use crate::commitment::{kzg::KZG, pedersen::Pedersen};
use crate::folding::hypernova::PreprocessorParam; use crate::folding::hypernova::PreprocessorParam;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
#[test] #[test]

+ 1
- 1
folding-schemes/src/folding/hypernova/decider_eth_circuit.rs

@ -517,7 +517,7 @@ pub mod tests {
use super::*; use super::*;
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::PreprocessorParam; use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
use crate::FoldingScheme; use crate::FoldingScheme;

+ 23
- 10
folding-schemes/src/folding/hypernova/mod.rs

@ -21,7 +21,6 @@ use circuits::AugmentedFCircuit;
use lcccs::LCCCS; use lcccs::LCCCS;
use nimfs::NIMFS; use nimfs::NIMFS;
use crate::commitment::CommitmentScheme;
use crate::constants::NOVA_N_BITS_RO; use crate::constants::NOVA_N_BITS_RO;
use crate::folding::circuits::{ use crate::folding::circuits::{
cyclefold::{ cyclefold::{
@ -30,10 +29,11 @@ use crate::folding::circuits::{
}, },
CF2, CF2,
}; };
use crate::folding::nova::{get_r1cs_from_cs, traits::NovaR1CS, PreprocessorParam};
use crate::folding::nova::{get_r1cs_from_cs, PreprocessorParam};
use crate::frontend::FCircuit; use crate::frontend::FCircuit;
use crate::utils::{get_cm_coordinates, pp_hash}; use crate::utils::{get_cm_coordinates, pp_hash};
use crate::Error; use crate::Error;
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
use crate::{ use crate::{
arith::{ arith::{
ccs::CCS, ccs::CCS,
@ -42,7 +42,8 @@ use crate::{
FoldingScheme, MultiFolding, FoldingScheme, MultiFolding,
}; };
struct HyperNovaCycleFoldConfig<C: CurveGroup, const MU: usize, const NU: usize> {
/// Configuration for HyperNova's CycleFold circuit
pub struct HyperNovaCycleFoldConfig<C: CurveGroup, const MU: usize, const NU: usize> {
_c: PhantomData<C>, _c: PhantomData<C>,
} }
@ -55,7 +56,9 @@ impl CycleFoldConfig
type F = C::BaseField; type F = C::BaseField;
} }
type HyperNovaCycleFoldCircuit<C, GC, const MU: usize, const NU: usize> =
/// CycleFold circuit for computing random linear combinations of group elements
/// in HyperNova instances.
pub type HyperNovaCycleFoldCircuit<C, GC, const MU: usize, const NU: usize> =
CycleFoldCircuit<HyperNovaCycleFoldConfig<C, MU, NU>, GC>; CycleFoldCircuit<HyperNovaCycleFoldConfig<C, MU, NU>, GC>;
/// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment. /// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment.
@ -76,6 +79,7 @@ impl Witness {
} }
} }
/// Proving parameters for HyperNova-based IVC
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool> pub struct ProverParams<C1, C2, CS1, CS2, const H: bool>
where where
@ -84,13 +88,18 @@ where
CS1: CommitmentScheme<C1, H>, CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>, CS2: CommitmentScheme<C2, H>,
{ {
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>, pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// Proving parameters of the underlying commitment scheme over C1
pub cs_pp: CS1::ProverParams, pub cs_pp: CS1::ProverParams,
/// Proving parameters of the underlying commitment scheme over C2
pub cf_cs_pp: CS2::ProverParams, pub cf_cs_pp: CS2::ProverParams,
// if ccs is set, it will be used, if not, it will be computed at runtime
/// CCS of the Augmented Function circuit
/// If ccs is set, it will be used, if not, it will be computed at runtime
pub ccs: Option<CCS<C1::ScalarField>>, pub ccs: Option<CCS<C1::ScalarField>>,
} }
/// Verification parameters for HyperNova-based IVC
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VerifierParams< pub struct VerifierParams<
C1: CurveGroup, C1: CurveGroup,
@ -99,10 +108,15 @@ pub struct VerifierParams<
CS2: CommitmentScheme<C2, H>, CS2: CommitmentScheme<C2, H>,
const H: bool, const H: bool,
> { > {
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>, pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// CCS of the Augmented step circuit
pub ccs: CCS<C1::ScalarField>, pub ccs: CCS<C1::ScalarField>,
/// R1CS of the CycleFold circuit
pub cf_r1cs: R1CS<C2::ScalarField>, pub cf_r1cs: R1CS<C2::ScalarField>,
/// Verification parameters of the underlying commitment scheme over C1
pub cs_vp: CS1::VerifierParams, pub cs_vp: CS1::VerifierParams,
/// Verification parameters of the underlying commitment scheme over C2
pub cf_cs_vp: CS2::VerifierParams, pub cf_cs_vp: CS2::VerifierParams,
} }
@ -282,7 +296,7 @@ where
let U_i = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s); let U_i = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
let mut u_i = CCCS::<C1>::dummy(self.ccs.l); let mut u_i = CCCS::<C1>::dummy(self.ccs.l);
let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) = let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
self.cf_r1cs.dummy_instance();
self.cf_r1cs.dummy_running_instance();
let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config); let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
@ -476,7 +490,7 @@ where
let w_dummy = W_dummy.clone(); let w_dummy = W_dummy.clone();
let mut u_dummy = CCCS::<C1>::dummy(ccs.l); let mut u_dummy = CCCS::<C1>::dummy(ccs.l);
let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) = let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
cf_r1cs.dummy_instance();
cf_r1cs.dummy_running_instance();
u_dummy.x = vec![ u_dummy.x = vec![
U_dummy.hash( U_dummy.hash(
&sponge, &sponge,
@ -884,8 +898,7 @@ where
u_i.check_relation(&vp.ccs, &w_i)?; u_i.check_relation(&vp.ccs, &w_i)?;
// check CycleFold's RelaxedR1CS satisfiability // check CycleFold's RelaxedR1CS satisfiability
vp.cf_r1cs
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?;
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
Ok(()) Ok(())
} }
@ -900,7 +913,7 @@ mod tests {
use super::*; use super::*;
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
#[test] #[test]

+ 21
- 21
folding-schemes/src/folding/nova/circuits.rs

@ -237,31 +237,31 @@ pub struct AugmentedFCircuit<
> where > where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{ {
pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub pp_hash: Option<CF1<C1>>,
pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>,
pub z_i: Option<Vec<C1::ScalarField>>,
pub external_inputs: Option<Vec<C1::ScalarField>>,
pub u_i_cmW: Option<C1>,
pub U_i: Option<CommittedInstance<C1>>,
pub U_i1_cmE: Option<C1>,
pub U_i1_cmW: Option<C1>,
pub cmT: Option<C1>,
pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
pub(super) _gc2: PhantomData<GC2>,
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
pub(super) pp_hash: Option<CF1<C1>>,
pub(super) i: Option<CF1<C1>>,
pub(super) i_usize: Option<usize>,
pub(super) z_0: Option<Vec<C1::ScalarField>>,
pub(super) z_i: Option<Vec<C1::ScalarField>>,
pub(super) external_inputs: Option<Vec<C1::ScalarField>>,
pub(super) u_i_cmW: Option<C1>,
pub(super) U_i: Option<CommittedInstance<C1>>,
pub(super) U_i1_cmE: Option<C1>,
pub(super) U_i1_cmW: Option<C1>,
pub(super) cmT: Option<C1>,
pub(super) F: FC, // F circuit
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
// cyclefold verifier on C1 // cyclefold verifier on C1
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
// cmE respectively // cmE respectively
pub cf1_u_i_cmW: Option<C2>, // input
pub cf2_u_i_cmW: Option<C2>, // input
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
pub cf1_cmT: Option<C2>,
pub cf2_cmT: Option<C2>,
pub cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
pub(super) cf1_u_i_cmW: Option<C2>, // input
pub(super) cf2_u_i_cmW: Option<C2>, // input
pub(super) cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
pub(super) cf1_cmT: Option<C2>,
pub(super) cf2_cmT: Option<C2>,
pub(super) 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>>> impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>

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

@ -342,7 +342,7 @@ pub mod tests {
use crate::folding::nova::{ use crate::folding::nova::{
PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams, PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams,
}; };
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
#[test] #[test]

+ 52
- 19
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -577,6 +577,8 @@ where
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use std::cmp::max;
use ark_crypto_primitives::crh::{ use ark_crypto_primitives::crh::{
sha256::{ sha256::{
constraints::{Sha256Gadget, UnitVar}, constraints::{Sha256Gadget, UnitVar},
@ -587,34 +589,61 @@ pub mod tests {
use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::bits::uint8::UInt8; use ark_r1cs_std::bits::uint8::UInt8;
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::{One, UniformRand};
use ark_std::{
rand::{thread_rng, Rng},
One, UniformRand,
};
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use super::*; use super::*;
use crate::arith::{ use crate::arith::{
r1cs::{ r1cs::{
extract_r1cs, extract_w_x,
tests::{get_test_r1cs, get_test_z}, tests::{get_test_r1cs, get_test_z},
{extract_r1cs, extract_w_x},
RelaxedR1CS,
}, },
Arith, Arith,
}; };
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::PreprocessorParam; use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
use crate::frontend::utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
use crate::FoldingScheme; use crate::FoldingScheme;
fn prepare_instances<C: CurveGroup, CS: CommitmentScheme<C>, R: Rng>(
mut rng: R,
r1cs: &R1CS<C::ScalarField>,
z: &[C::ScalarField],
) -> (Witness<C>, CommittedInstance<C>)
where
C::ScalarField: Absorb,
{
let (w, x) = r1cs.split_z(z);
let (cs_pp, _) = CS::setup(&mut rng, max(w.len(), r1cs.A.n_rows)).unwrap();
let mut w = Witness::new::<false>(w, r1cs.A.n_rows, &mut rng);
w.E = r1cs.eval_relation(z).unwrap();
let mut u = w.commit::<CS, false>(&cs_pp, x).unwrap();
u.u = z[0];
(w, u)
}
#[test] #[test]
fn test_relaxed_r1cs_small_gadget_handcrafted() { fn test_relaxed_r1cs_small_gadget_handcrafted() {
let rng = &mut thread_rng();
let r1cs: R1CS<Fr> = get_test_r1cs(); let r1cs: R1CS<Fr> = get_test_r1cs();
let rel_r1cs = r1cs.clone().relax();
let z = get_test_z(3);
let mut z = get_test_z(3);
z[0] = Fr::rand(rng);
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
let cs = ConstraintSystem::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap(); let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(rel_r1cs.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs.u)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(w.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap(); let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap(); RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@ -624,6 +653,8 @@ pub mod tests {
// gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been // gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been
// initialized. // initialized.
fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) { fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) {
let rng = &mut thread_rng();
let cs = ConstraintSystem::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();
circuit.generate_constraints(cs.clone()).unwrap(); circuit.generate_constraints(cs.clone()).unwrap();
@ -634,18 +665,19 @@ pub mod tests {
let r1cs = extract_r1cs::<Fr>(&cs); let r1cs = extract_r1cs::<Fr>(&cs);
let (w, x) = extract_w_x::<Fr>(&cs); let (w, x) = extract_w_x::<Fr>(&cs);
let z = [vec![Fr::one()], x, w].concat();
let mut z = [vec![Fr::one()], x, w].concat();
r1cs.check_relation(&z).unwrap(); r1cs.check_relation(&z).unwrap();
let relaxed_r1cs = r1cs.clone().relax();
relaxed_r1cs.check_relation(&z).unwrap();
z[0] = Fr::rand(rng);
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
r1cs.check_relaxed_relation(&w, &u).unwrap();
// set new CS for the circuit that checks the RelaxedR1CS of our original circuit // set new CS for the circuit that checks the RelaxedR1CS of our original circuit
let cs = ConstraintSystem::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();
// prepare the inputs for our circuit // prepare the inputs for our circuit
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap(); let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(w.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap(); let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap(); RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@ -709,6 +741,8 @@ pub mod tests {
#[test] #[test]
fn test_relaxed_r1cs_nonnative_circuit() { fn test_relaxed_r1cs_nonnative_circuit() {
let rng = &mut thread_rng();
let cs = ConstraintSystem::<Fq>::new_ref(); let cs = ConstraintSystem::<Fq>::new_ref();
// in practice we would use CycleFoldCircuit, but is a very big circuit (when computed // in practice we would use CycleFoldCircuit, but is a very big circuit (when computed
// non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a // non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a
@ -725,16 +759,15 @@ pub mod tests {
let cs = cs.into_inner().unwrap(); let cs = cs.into_inner().unwrap();
let r1cs = extract_r1cs::<Fq>(&cs); let r1cs = extract_r1cs::<Fq>(&cs);
let (w, x) = extract_w_x::<Fq>(&cs); let (w, x) = extract_w_x::<Fq>(&cs);
let z = [vec![Fq::one()], x, w].concat();
let z = [vec![Fq::rand(rng)], x, w].concat();
let relaxed_r1cs = r1cs.clone().relax();
let (w, u) = prepare_instances::<_, Pedersen<Projective2>, _>(rng, &r1cs, &z);
// natively // natively
let cs = ConstraintSystem::<Fq>::new_ref(); let cs = ConstraintSystem::<Fq>::new_ref();
let zVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(z.clone())).unwrap(); let zVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(z.clone())).unwrap();
let EVar =
Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.clone().E)).unwrap();
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let EVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(w.E.clone())).unwrap();
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar = let r1csVar =
R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap(); R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap(); RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@ -742,8 +775,8 @@ pub mod tests {
// non-natively // non-natively
let cs = ConstraintSystem::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::new_witness(cs.clone(), || Ok(z)).unwrap(); let zVar = Vec::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let EVar = Vec::new_witness(cs.clone(), || Ok(w.E)).unwrap();
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar = let r1csVar =
R1CSVar::<Fq, Fr, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap(); R1CSVar::<Fq, Fr, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_nonnative(r1csVar, EVar, uVar, zVar).unwrap(); RelaxedR1CSGadget::check_nonnative(r1csVar, EVar, uVar, zVar).unwrap();

+ 35
- 32
folding-schemes/src/folding/nova/mod.rs

@ -14,7 +14,6 @@ use ark_std::rand::RngCore;
use ark_std::{One, UniformRand, Zero}; use ark_std::{One, UniformRand, Zero};
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{ use crate::folding::circuits::cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig, fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness, CycleFoldWitness,
@ -25,6 +24,7 @@ use crate::transcript::{poseidon::poseidon_canonical_config, AbsorbNonNative, Tr
use crate::utils::vec::is_zero_vec; use crate::utils::vec::is_zero_vec;
use crate::Error; use crate::Error;
use crate::FoldingScheme; use crate::FoldingScheme;
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
use crate::{ use crate::{
arith::r1cs::{extract_r1cs, extract_w_x, R1CS}, arith::r1cs::{extract_r1cs, extract_w_x, R1CS},
constants::NOVA_N_BITS_RO, constants::NOVA_N_BITS_RO,
@ -40,8 +40,8 @@ pub mod traits;
pub mod zk; pub mod zk;
use circuits::{AugmentedFCircuit, ChallengeGadget}; use circuits::{AugmentedFCircuit, ChallengeGadget};
use nifs::NIFS; use nifs::NIFS;
use traits::NovaR1CS;
/// Configuration for Nova's CycleFold circuit
pub struct NovaCycleFoldConfig<C: CurveGroup> { pub struct NovaCycleFoldConfig<C: CurveGroup> {
_c: PhantomData<C>, _c: PhantomData<C>,
} }
@ -56,7 +56,9 @@ impl CycleFoldConfig for NovaCycleFoldConfig {
type F = C::BaseField; type F = C::BaseField;
} }
type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
/// CycleFold circuit for computing random linear combinations of group elements
/// in Nova instances.
pub type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct CommittedInstance<C: CurveGroup> { pub struct CommittedInstance<C: CurveGroup> {
@ -136,10 +138,7 @@ pub struct Witness {
pub rW: C::ScalarField, pub rW: C::ScalarField,
} }
impl<C: CurveGroup> Witness<C>
where
<C as Group>::ScalarField: Absorb,
{
impl<C: CurveGroup> Witness<C> {
pub fn new<const H: bool>(w: Vec<C::ScalarField>, e_len: usize, mut rng: impl RngCore) -> Self { pub fn new<const H: bool>(w: Vec<C::ScalarField>, e_len: usize, mut rng: impl RngCore) -> Self {
let (rW, rE) = if H { let (rW, rE) = if H {
( (
@ -227,6 +226,7 @@ where
} }
} }
/// Proving parameters for Nova-based IVC
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool = false> pub struct ProverParams<C1, C2, CS1, CS2, const H: bool = false>
where where
@ -235,8 +235,11 @@ where
CS1: CommitmentScheme<C1, H>, CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>, CS2: CommitmentScheme<C2, H>,
{ {
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>, pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// Proving parameters of the underlying commitment scheme over C1
pub cs_pp: CS1::ProverParams, pub cs_pp: CS1::ProverParams,
/// Proving parameters of the underlying commitment scheme over C2
pub cf_cs_pp: CS2::ProverParams, pub cf_cs_pp: CS2::ProverParams,
} }
@ -302,6 +305,7 @@ where
} }
} }
/// Verification parameters for Nova-based IVC
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool = false> pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool = false>
where where
@ -310,10 +314,15 @@ where
CS1: CommitmentScheme<C1, H>, CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>, CS2: CommitmentScheme<C2, H>,
{ {
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>, pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// R1CS of the Augmented step circuit
pub r1cs: R1CS<C1::ScalarField>, pub r1cs: R1CS<C1::ScalarField>,
/// R1CS of the CycleFold circuit
pub cf_r1cs: R1CS<C2::ScalarField>, pub cf_r1cs: R1CS<C2::ScalarField>,
/// Verification parameters of the underlying commitment scheme over C1
pub cs_vp: CS1::VerifierParams, pub cs_vp: CS1::VerifierParams,
/// Verification parameters of the underlying commitment scheme over C2
pub cf_cs_vp: CS2::VerifierParams, pub cf_cs_vp: CS2::VerifierParams,
} }
@ -553,8 +562,9 @@ where
let pp_hash = vp.pp_hash()?; let pp_hash = vp.pp_hash()?;
// setup the dummy instances // setup the dummy instances
let (w_dummy, u_dummy) = r1cs.dummy_instance();
let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance();
let (W_dummy, U_dummy) = r1cs.dummy_running_instance();
let (w_dummy, u_dummy) = r1cs.dummy_incoming_instance();
let (cf_W_dummy, cf_U_dummy) = cf_r1cs.dummy_running_instance();
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
// R1CS that we're working with. // R1CS that we're working with.
@ -572,13 +582,13 @@ where
i: C1::ScalarField::zero(), i: C1::ScalarField::zero(),
z_0: z_0.clone(), z_0: z_0.clone(),
z_i: z_0, z_i: z_0,
w_i: w_dummy.clone(),
u_i: u_dummy.clone(),
W_i: w_dummy,
U_i: u_dummy,
w_i: w_dummy,
u_i: u_dummy,
W_i: W_dummy,
U_i: U_dummy,
// cyclefold running instance // cyclefold running instance
cf_W_i: cf_w_dummy.clone(),
cf_U_i: cf_u_dummy.clone(),
cf_W_i: cf_W_dummy,
cf_U_i: cf_U_dummy,
}) })
} }
@ -805,10 +815,10 @@ where
#[cfg(test)] #[cfg(test)]
{ {
self.cf_r1cs.check_instance_relation(&_cfW_w_i, &cfW_u_i)?;
self.cf_r1cs.check_instance_relation(&_cfE_w_i, &cfE_u_i)?;
self.cf_r1cs.check_tight_relation(&_cfW_w_i, &cfW_u_i)?;
self.cf_r1cs.check_tight_relation(&_cfE_w_i, &cfE_u_i)?;
self.cf_r1cs self.cf_r1cs
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
.check_relaxed_relation(&self.cf_W_i, &self.cf_U_i)?;
} }
} }
@ -840,9 +850,8 @@ where
#[cfg(test)] #[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)?;
self.r1cs.check_tight_relation(&self.w_i, &self.u_i)?;
self.r1cs.check_relaxed_relation(&self.W_i, &self.U_i)?;
} }
Ok(()) Ok(())
@ -908,19 +917,13 @@ where
return Err(Error::IVCVerificationFail); 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() {
return Err(Error::IVCVerificationFail);
}
// check R1CS satisfiability
vp.r1cs.check_instance_relation(&w_i, &u_i)?;
// check R1CS satisfiability, which also enforces u_i.cmE==0, u_i.u==1
vp.r1cs.check_tight_relation(&w_i, &u_i)?;
// check RelaxedR1CS satisfiability // check RelaxedR1CS satisfiability
vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?;
vp.r1cs.check_relaxed_relation(&W_i, &U_i)?;
// check CycleFold RelaxedR1CS satisfiability // check CycleFold RelaxedR1CS satisfiability
vp.cf_r1cs
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?;
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
Ok(()) Ok(())
} }
@ -1077,7 +1080,7 @@ pub mod tests {
use super::*; use super::*;
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
/// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the /// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the

+ 14
- 15
folding-schemes/src/folding/nova/nifs.rs

@ -210,10 +210,12 @@ pub mod tests {
use ark_pallas::{Fr, Projective}; use ark_pallas::{Fr, Projective};
use ark_std::{ops::Mul, test_rng, UniformRand}; use ark_std::{ops::Mul, test_rng, UniformRand};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::arith::r1cs::{
tests::{get_test_r1cs, get_test_z},
RelaxedR1CS,
};
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
use crate::folding::nova::circuits::ChallengeGadget; use crate::folding::nova::circuits::ChallengeGadget;
use crate::folding::nova::traits::NovaR1CS;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@ -316,8 +318,8 @@ pub mod tests {
let u_i = u_dummy.clone(); let u_i = u_dummy.clone();
let W_i = w_dummy.clone(); let W_i = w_dummy.clone();
let U_i = u_dummy.clone(); let U_i = u_dummy.clone();
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
r1cs.check_relaxed_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_relation(&W_i, &U_i).unwrap();
let r_Fr = Fr::from(3_u32); let r_Fr = Fr::from(3_u32);
@ -334,7 +336,7 @@ pub mod tests {
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT, r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
) )
.unwrap(); .unwrap();
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
r1cs.check_relaxed_relation(&W_i1, &U_i1).unwrap();
} }
// fold 2 instances into one // fold 2 instances into one
@ -348,9 +350,9 @@ pub mod tests {
assert_eq!(ci3_v, ci3); assert_eq!(ci3_v, ci3);
// check that relations hold for the 2 inputted instances and the folded one // check that relations hold for the 2 inputted instances and the folded one
r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap();
r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap();
r1cs.check_relaxed_instance_relation(&w3, &ci3).unwrap();
r1cs.check_relaxed_relation(&w1, &ci1).unwrap();
r1cs.check_relaxed_relation(&w2, &ci2).unwrap();
r1cs.check_relaxed_relation(&w3, &ci3).unwrap();
// check that folded commitments from folded instance (ci) are equal to folding the // check that folded commitments from folded instance (ci) are equal to folding the
// use folded rE, rW to commit w3 // use folded rE, rW to commit w3
@ -425,7 +427,7 @@ pub mod tests {
.commit::<Pedersen<Projective>, false>(&pedersen_params, x) .commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap(); .unwrap();
r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance)
r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance)
.unwrap(); .unwrap();
let num_iters = 10; let num_iters = 10;
@ -438,11 +440,8 @@ pub mod tests {
let incoming_committed_instance = incoming_instance_w let incoming_committed_instance = incoming_instance_w
.commit::<Pedersen<Projective>, false>(&pedersen_params, x) .commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap(); .unwrap();
r1cs.check_relaxed_instance_relation(
&incoming_instance_w,
&incoming_committed_instance,
)
.unwrap();
r1cs.check_relaxed_relation(&incoming_instance_w, &incoming_committed_instance)
.unwrap();
let r = Fr::rand(&mut rng); // folding challenge would come from the RO let r = Fr::rand(&mut rng); // folding challenge would come from the RO
@ -475,7 +474,7 @@ pub mod tests {
&cmT, &cmT,
); );
r1cs.check_relaxed_instance_relation(&folded_w, &folded_committed_instance)
r1cs.check_relaxed_relation(&folded_w, &folded_committed_instance)
.unwrap(); .unwrap();
// set running_instance for next loop iteration // set running_instance for next loop iteration

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

@ -187,7 +187,7 @@ pub mod tests {
use crate::{ use crate::{
commitment::{kzg::KZG, pedersen::Pedersen}, commitment::{kzg::KZG, pedersen::Pedersen},
folding::nova::{Nova, PreprocessorParam}, folding::nova::{Nova, PreprocessorParam},
frontend::{tests::CubicFCircuit, FCircuit},
frontend::{utils::CubicFCircuit, FCircuit},
transcript::poseidon::poseidon_canonical_config, transcript::poseidon::poseidon_canonical_config,
FoldingScheme, FoldingScheme,
}; };

+ 72
- 51
folding-schemes/src/folding/nova/traits.rs

@ -1,69 +1,90 @@
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_std::One;
use ark_ec::CurveGroup;
use ark_std::{rand::RngCore, One, UniformRand};
use super::{CommittedInstance, Witness}; use super::{CommittedInstance, Witness};
use crate::arith::{r1cs::R1CS, Arith};
use crate::arith::r1cs::{RelaxedR1CS, R1CS};
use crate::Error; use crate::Error;
/// NovaR1CS extends R1CS methods with Nova specific methods
pub trait NovaR1CS<C: CurveGroup> {
/// returns a dummy instance (Witness and CommittedInstance) for the current R1CS structure
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>);
/// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance.
fn check_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error>;
/// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness
/// and CommittedInstance.
fn check_relaxed_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error>;
}
impl<C: CurveGroup> NovaR1CS<C> for R1CS<C::ScalarField>
where
<C as Group>::ScalarField: Absorb,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C>, CommittedInstance<C>> for R1CS<C::ScalarField> {
fn dummy_running_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
let w_len = self.A.n_cols - 1 - self.l; let w_len = self.A.n_cols - 1 - self.l;
let w_dummy = Witness::<C>::dummy(w_len, self.A.n_rows); let w_dummy = Witness::<C>::dummy(w_len, self.A.n_rows);
let u_dummy = CommittedInstance::<C>::dummy(self.l); let u_dummy = CommittedInstance::<C>::dummy(self.l);
(w_dummy, u_dummy) (w_dummy, u_dummy)
} }
// notice that this method does not check the commitment correctness
fn check_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
fn dummy_incoming_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
self.dummy_running_instance()
}
fn is_relaxed(_w: &Witness<C>, u: &CommittedInstance<C>) -> bool {
u.cmE != C::zero() || u.u != C::ScalarField::one()
}
fn extract_z(w: &Witness<C>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
[&[u.u][..], &u.x, &w.W].concat()
}
fn check_error_terms(
w: &Witness<C>,
_u: &CommittedInstance<C>,
e: Vec<C::ScalarField>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if U.cmE != C::zero() || U.u != C::ScalarField::one() {
return Err(Error::R1CSUnrelaxedFail);
if w.E == e {
Ok(())
} else {
Err(Error::NotSatisfied)
} }
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
self.check_relation(&Z)
} }
// notice that this method does not check the commitment correctness
fn check_relaxed_instance_relation(
fn sample<CS>(
&self, &self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error> {
let mut rel_r1cs = self.clone().relax();
rel_r1cs.u = U.u;
rel_r1cs.E = W.E.clone();
params: &CS::ProverParams,
mut rng: impl RngCore,
) -> Result<(Witness<C>, CommittedInstance<C>), Error>
where
CS: crate::commitment::CommitmentScheme<C, true>,
{
// Implements sampling a (committed) RelaxedR1CS
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
let u = C::ScalarField::rand(&mut rng);
let rE = C::ScalarField::rand(&mut rng);
let rW = C::ScalarField::rand(&mut rng);
let W = (0..self.A.n_cols - self.l - 1)
.map(|_| C::ScalarField::rand(&mut rng))
.collect();
let x = (0..self.l)
.map(|_| C::ScalarField::rand(&mut rng))
.collect::<Vec<C::ScalarField>>();
let mut z = vec![u];
z.extend(&x);
z.extend(&W);
let E = <Self as RelaxedR1CS<C, Witness<C>, CommittedInstance<C>>>::compute_E(
&self.A, &self.B, &self.C, &z, &u,
)?;
debug_assert!(
z.len() == self.A.n_cols,
"Length of z is {}, while A has {} columns.",
z.len(),
self.A.n_cols
);
let witness = Witness { E, rE, W, rW };
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
// witness.commit() sets u to 1, we set it to the sampled u value
cm_witness.u = u;
debug_assert!(
self.check_relaxed_relation(&witness, &cm_witness).is_ok(),
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
u,
witness.E
);
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
rel_r1cs.check_relation(&Z)
Ok((witness, cm_witness))
} }
} }

+ 9
- 26
folding-schemes/src/folding/nova/zk.rs

@ -30,7 +30,6 @@
/// paper). /// paper).
/// And the Use-case-2 would require a modified version of the Decider circuits. /// And the Use-case-2 would require a modified version of the Decider circuits.
/// ///
use crate::folding::nova::traits::NovaR1CS;
use ark_crypto_primitives::sponge::CryptographicSponge; use ark_crypto_primitives::sponge::CryptographicSponge;
use ark_ff::{BigInteger, PrimeField}; use ark_ff::{BigInteger, PrimeField};
use ark_std::{One, Zero}; use ark_std::{One, Zero};
@ -141,9 +140,8 @@ where
// d. Store folding proof // d. Store folding proof
let pi = FoldingProof { cmT }; let pi = FoldingProof { cmT };
// 2. Sample a satisfying relaxed R1CS instance-witness pair (U_r, W_r)
let relaxed_instance = nova.r1cs.clone().relax();
let (U_r, W_r) = relaxed_instance.sample::<C1, CS1>(&nova.cs_pp, &mut rng)?;
// 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r)
let (W_r, U_r) = nova.r1cs.sample::<CS1>(&nova.cs_pp, &mut rng)?;
// 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r) // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
// a. Compute T // a. Compute T
@ -280,21 +278,10 @@ where
); );
// 5. Check that W^{\prime}_i is a satisfying witness // 5. Check that W^{\prime}_i is a satisfying witness
let mut z = vec![U_i_prime.u];
z.extend(&U_i_prime.x);
z.extend(&proof.W_i_prime.W);
let relaxed_r1cs = RelaxedR1CS {
l: r1cs.l,
A: r1cs.A.clone(),
B: r1cs.B.clone(),
C: r1cs.C.clone(),
u: U_i_prime.u,
E: proof.W_i_prime.E.clone(),
};
relaxed_r1cs.check_relation(&z)?;
r1cs.check_relaxed_relation(&proof.W_i_prime, &U_i_prime)?;
// 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs // 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs
cf_r1cs.check_relaxed_instance_relation(&proof.cf_W_i, &proof.cf_U_i)?;
cf_r1cs.check_relaxed_relation(&proof.cf_W_i, &proof.cf_U_i)?;
Ok(()) Ok(())
} }
@ -305,7 +292,7 @@ pub mod tests {
use super::*; use super::*;
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::tests::test_ivc_opt; use crate::folding::nova::tests::test_ivc_opt;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
use ark_bn254::{Fr, G1Projective as Projective}; use ark_bn254::{Fr, G1Projective as Projective};
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
@ -380,11 +367,9 @@ pub mod tests {
F_circuit, F_circuit,
3, 3,
); );
let (sampled_committed_instance, _) = nova
let (_, sampled_committed_instance) = nova
.r1cs .r1cs
.clone()
.relax()
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap(); .unwrap();
// proof verification fails with incorrect running instance // proof verification fails with incorrect running instance
@ -419,11 +404,9 @@ pub mod tests {
F_circuit, F_circuit,
3, 3,
); );
let (_, sampled_committed_witness) = nova
let (sampled_committed_witness, _) = nova
.r1cs .r1cs
.clone()
.relax()
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap(); .unwrap();
// proof generation fails with incorrect running witness // proof generation fails with incorrect running witness

+ 392
- 29
folding-schemes/src/folding/protogalaxy/circuits.rs

@ -1,26 +1,46 @@
use ark_crypto_primitives::sponge::CryptographicSponge;
use ark_crypto_primitives::sponge::{
constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,
};
use ark_ec::CurveGroup; use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain}; use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain};
use ark_r1cs_std::{ use ark_r1cs_std::{
alloc::AllocVar, alloc::AllocVar,
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, FieldVar}, fields::{fp::FpVar, FieldVar},
groups::{CurveVar, GroupOpsBounds},
poly::polynomial::univariate::dense::DensePolynomialVar, poly::polynomial::univariate::dense::DensePolynomialVar,
R1CSVar, ToBitsGadget, ToConstraintFieldGadget,
}; };
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
use ark_std::{fmt::Debug, marker::PhantomData, One, Zero};
use super::{ use super::{
folding::lagrange_polys, folding::lagrange_polys,
utils::{all_powers_var, betas_star_var, exponential_powers_var}, utils::{all_powers_var, betas_star_var, exponential_powers_var},
CommittedInstanceVar,
CommittedInstance, CommittedInstanceVar, ProtoGalaxyCycleFoldConfig,
}; };
use crate::{ use crate::{
folding::circuits::nonnative::affine::NonNativeAffineVar, transcript::TranscriptVar,
folding::circuits::{
cyclefold::{
CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar,
CycleFoldConfig, NIFSFullGadget,
},
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
CF1, CF2,
},
frontend::FCircuit,
transcript::{AbsorbNonNativeGadget, TranscriptVar},
utils::gadgets::VectorGadget, utils::gadgets::VectorGadget,
}; };
pub struct FoldingGadget {} pub struct FoldingGadget {}
impl FoldingGadget { impl FoldingGadget {
#[allow(clippy::type_complexity)]
pub fn fold_committed_instance<C: CurveGroup, S: CryptographicSponge>( pub fn fold_committed_instance<C: CurveGroup, S: CryptographicSponge>(
transcript: &mut impl TranscriptVar<C::ScalarField, S>, transcript: &mut impl TranscriptVar<C::ScalarField, S>,
// running instance // running instance
@ -30,9 +50,8 @@ impl FoldingGadget {
// polys from P // polys from P
F_coeffs: Vec<FpVar<C::ScalarField>>, F_coeffs: Vec<FpVar<C::ScalarField>>,
K_coeffs: Vec<FpVar<C::ScalarField>>, K_coeffs: Vec<FpVar<C::ScalarField>>,
) -> Result<CommittedInstanceVar<C>, SynthesisError> {
) -> Result<(CommittedInstanceVar<C>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
let t = instance.betas.len(); let t = instance.betas.len();
let n = F_coeffs.len();
// absorb the committed instances // absorb the committed instances
transcript.absorb(instance)?; transcript.absorb(instance)?;
@ -44,7 +63,7 @@ impl FoldingGadget {
transcript.absorb(&F_coeffs)?; transcript.absorb(&F_coeffs)?;
let alpha = transcript.get_challenge()?; let alpha = transcript.get_challenge()?;
let alphas = all_powers_var(alpha.clone(), n);
let alphas = all_powers_var(alpha.clone(), t);
// F(alpha) = e + \sum_t F_i * alpha^i // F(alpha) = e + \sum_t F_i * alpha^i
let mut F_alpha = instance.e.clone(); let mut F_alpha = instance.e.clone();
@ -88,34 +107,377 @@ impl FoldingGadget {
let e_star = F_alpha * &L_X_evals[0] + Z_X.evaluate(&gamma)? * K_X.evaluate(&gamma)?; let e_star = F_alpha * &L_X_evals[0] + Z_X.evaluate(&gamma)? * K_X.evaluate(&gamma)?;
let mut u_star = &instance.u * &L_X_evals[0];
let mut x_star = instance.x.mul_scalar(&L_X_evals[0])?; let mut x_star = instance.x.mul_scalar(&L_X_evals[0])?;
for i in 0..k { for i in 0..k {
u_star += &vec_instances[i].u * &L_X_evals[i + 1];
x_star = x_star.add(&vec_instances[i].x.mul_scalar(&L_X_evals[i + 1])?)?; x_star = x_star.add(&vec_instances[i].x.mul_scalar(&L_X_evals[i + 1])?)?;
} }
// return the folded instance // return the folded instance
Ok(CommittedInstanceVar {
betas: betas_star,
// phi will be computed in CycleFold
phi: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
e: e_star,
u: u_star,
x: x_star,
})
Ok((
CommittedInstanceVar {
betas: betas_star,
// phi will be computed in CycleFold
phi: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
e: e_star,
x: x_star,
},
L_X_evals,
))
}
}
pub struct AugmentationGadget;
impl AugmentationGadget {
#[allow(clippy::type_complexity)]
pub fn prepare_and_fold_primary<C: CurveGroup, S: CryptographicSponge>(
transcript: &mut impl TranscriptVar<CF1<C>, S>,
U: CommittedInstanceVar<C>,
u_phis: Vec<NonNativeAffineVar<C>>,
u_xs: Vec<Vec<FpVar<CF1<C>>>>,
new_U_phi: NonNativeAffineVar<C>,
F_coeffs: Vec<FpVar<CF1<C>>>,
K_coeffs: Vec<FpVar<CF1<C>>>,
) -> Result<(CommittedInstanceVar<C>, Vec<FpVar<CF1<C>>>), SynthesisError> {
assert_eq!(u_phis.len(), u_xs.len());
// Prepare the incoming instances.
// For each instance `u`, we have `u.betas = []`, `u.e = 0`.
let us = u_phis
.into_iter()
.zip(u_xs)
.map(|(phi, x)| CommittedInstanceVar {
phi,
betas: vec![],
e: FpVar::zero(),
x,
})
.collect::<Vec<_>>();
// Fold the incoming instances `us` into the running instance `U`.
let (mut U, L_X_evals) =
FoldingGadget::fold_committed_instance(transcript, &U, &us, F_coeffs, K_coeffs)?;
// Notice that FoldingGadget::fold_committed_instance does not fold phi.
// We set `U.phi` to unconstrained witnesses `U_phi` here, whose
// correctness will be checked on the other curve.
U.phi = new_U_phi;
Ok((U, L_X_evals))
}
pub fn prepare_and_fold_cyclefold<
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
S: CryptographicSponge,
>(
transcript: &mut PoseidonSpongeVar<CF1<C1>>,
pp_hash: FpVar<CF1<C1>>,
mut cf_U: CycleFoldCommittedInstanceVar<C2, GC2>,
cf_u_cmWs: Vec<GC2>,
cf_u_xs: Vec<Vec<NonNativeUintVar<CF1<C1>>>>,
cf_cmTs: Vec<GC2>,
) -> Result<CycleFoldCommittedInstanceVar<C2, GC2>, SynthesisError>
where
C2::BaseField: PrimeField + Absorb,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
assert_eq!(cf_u_cmWs.len(), cf_u_xs.len());
assert_eq!(cf_u_xs.len(), cf_cmTs.len());
// Fold the incoming CycleFold instances into the running CycleFold
// instance in a iterative way, since `NIFSFullGadget` only supports
// folding one incoming instance at a time.
for ((cmW, x), cmT) in cf_u_cmWs.into_iter().zip(cf_u_xs).zip(cf_cmTs) {
// Prepare the incoming CycleFold instance `cf_u` for the current
// iteration.
// For each CycleFold instance `cf_u`, we have `cf_u.cmE = 0`, and
// `cf_u.u = 1`.
let cf_u = CycleFoldCommittedInstanceVar {
cmE: GC2::zero(),
u: NonNativeUintVar::new_constant(ConstraintSystemRef::None, C1::BaseField::one())?,
cmW,
x,
};
let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget(
transcript,
pp_hash.clone(),
cf_U.to_native_sponge_field_elements()?,
cf_u.clone(),
cmT.clone(),
)?;
// Fold the current incoming CycleFold instance `cf_u` into the
// running CycleFold instance `cf_U`.
cf_U = NIFSFullGadget::fold_committed_instance(cf_r_bits, cmT, cf_U, cf_u)?;
}
Ok(cf_U)
}
}
/// `AugmentedFCircuit` enhances the original step function `F`, so that it can
/// be used in recursive arguments such as IVC.
///
/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in
/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not
/// only invokes `F`, but also adds additional constraints for verifying the
/// correct folding of primary instances (i.e., the instances over `C1`).
/// In the paper, the primary instances are Nova's `CommittedInstance`, but we
/// extend this method to support using ProtoGalaxy's `CommittedInstance` as
/// primary instances.
///
/// Furthermore, to reduce circuit size over `C2`, we implement the constraints
/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra
/// constraints verify the correct folding of CycleFold instances.
#[derive(Debug, Clone)]
pub struct AugmentedFCircuit<
C1: CurveGroup,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<CF1<C1>>,
> {
pub(super) _gc2: PhantomData<GC2>,
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
pub(super) pp_hash: CF1<C1>,
pub(super) i: CF1<C1>,
pub(super) i_usize: usize,
pub(super) z_0: Vec<CF1<C1>>,
pub(super) z_i: Vec<CF1<C1>>,
pub(super) external_inputs: Vec<CF1<C1>>,
pub(super) F: FC, // F circuit
pub(super) u_i_phi: C1,
pub(super) U_i: CommittedInstance<C1>,
pub(super) U_i1_phi: C1,
pub(super) F_coeffs: Vec<CF1<C1>>,
pub(super) K_coeffs: Vec<CF1<C1>>,
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
pub(super) phi_stars: Vec<C1>,
pub(super) cf1_u_i_cmW: C2, // input
pub(super) cf2_u_i_cmW: C2, // input
pub(super) cf_U_i: CycleFoldCommittedInstance<C2>, // input
pub(super) cf1_cmT: C2,
pub(super) cf2_cmT: C2,
pub(super) 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>>>
AugmentedFCircuit<C1, C2, GC2, FC>
where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
pub fn empty(
poseidon_config: &PoseidonConfig<CF1<C1>>,
F_circuit: FC,
t: usize,
d: usize,
k: usize,
) -> Self {
let u_dummy = CommittedInstance::dummy_running(2, t);
let cf_u_dummy =
CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
Self {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
pp_hash: CF1::<C1>::zero(),
i: CF1::<C1>::zero(),
i_usize: 0,
z_0: vec![CF1::<C1>::zero(); F_circuit.state_len()],
z_i: vec![CF1::<C1>::zero(); F_circuit.state_len()],
external_inputs: vec![CF1::<C1>::zero(); F_circuit.external_inputs_len()],
u_i_phi: C1::zero(),
U_i: u_dummy,
U_i1_phi: C1::zero(),
F_coeffs: vec![CF1::<C1>::zero(); t],
K_coeffs: vec![CF1::<C1>::zero(); d * k + 1],
phi_stars: vec![C1::zero(); k],
F: F_circuit,
x: None,
// cyclefold values
cf1_u_i_cmW: C2::zero(),
cf2_u_i_cmW: C2::zero(),
cf_U_i: cf_u_dummy,
cf1_cmT: C2::zero(),
cf2_cmT: C2::zero(),
cf_x: None,
}
}
}
impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
where
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<CF1<C1>>,
C2::BaseField: PrimeField + Absorb,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
let pp_hash = FpVar::<CF1<C1>>::new_witness(cs.clone(), || Ok(self.pp_hash))?;
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || Ok(self.i))?;
let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.z_0))?;
let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.z_i))?;
let external_inputs =
Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.external_inputs))?;
let u_dummy = CommittedInstance::<C1>::dummy_running(2, self.U_i.betas.len());
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || Ok(self.U_i))?;
let u_i_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.u_i_phi))?;
let U_i1_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_phi))?;
let phi_stars =
Vec::<NonNativeAffineVar<C1>>::new_witness(cs.clone(), || Ok(self.phi_stars))?;
let cf_u_dummy =
CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
let cf_U_i =
CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || Ok(self.cf_U_i))?;
let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT))?;
let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT))?;
let F_coeffs = Vec::new_witness(cs.clone(), || Ok(self.F_coeffs))?;
let K_coeffs = Vec::new_witness(cs.clone(), || Ok(self.K_coeffs))?;
// `sponge` is for digest computation.
let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config);
// `transcript` is for challenge generation.
let mut transcript = sponge.clone();
// get z_{i+1} from the F circuit
let i_usize = self.i_usize;
let z_i1 =
self.F
.generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?;
let is_basecase = i.is_zero()?;
// Primary Part
// P.1. Compute u_i.x
// u_i.x[0] = H(i, z_0, z_i, U_i)
let (u_i_x, _) = U_i.clone().hash(
&sponge,
pp_hash.clone(),
i.clone(),
z_0.clone(),
z_i.clone(),
)?;
// u_i.x[1] = H(cf_U_i)
let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
// P.2. Prepare incoming primary instances
// P.3. Fold incoming primary instances into the running instance
let (U_i1, r) = AugmentationGadget::prepare_and_fold_primary(
&mut transcript,
U_i.clone(),
vec![u_i_phi.clone()],
vec![vec![u_i_x, cf_u_i_x]],
U_i1_phi,
F_coeffs,
K_coeffs,
)?;
// P.4.a compute and check the first output of F'
// Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
// Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
let (u_i1_x, _) = U_i1.clone().hash(
&sponge,
pp_hash.clone(),
i + FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
&sponge,
pp_hash.clone(),
FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?;
x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
// CycleFold part
// C.1. Compute cf1_u_i.x and cf2_u_i.x
let mut r0_bits = r[0].to_bits_le()?;
let mut r1_bits = r[1].to_bits_le()?;
r0_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
r1_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
let cf1_x = [
r0_bits
.chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
.map(|bits| {
let mut bits = bits.to_vec();
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
NonNativeUintVar::from(&bits)
})
.collect::<Vec<_>>(),
vec![
NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?,
NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?,
U_i.phi.x.clone(),
U_i.phi.y.clone(),
phi_stars[0].x.clone(),
phi_stars[0].y.clone(),
],
]
.concat();
let cf2_x = [
r1_bits
.chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
.map(|bits| {
let mut bits = bits.to_vec();
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
NonNativeUintVar::from(&bits)
})
.collect::<Vec<_>>(),
vec![
phi_stars[0].x.clone(),
phi_stars[0].y.clone(),
u_i_phi.x.clone(),
u_i_phi.y.clone(),
U_i1.phi.x.clone(),
U_i1.phi.y.clone(),
],
]
.concat();
// C.2. Prepare incoming CycleFold instances
// C.3. Fold incoming CycleFold instances into the running instance
let cf_U_i1 =
AugmentationGadget::prepare_and_fold_cyclefold::<C1, C2, GC2, PoseidonSponge<CF1<C1>>>(
&mut transcript,
pp_hash.clone(),
cf_U_i,
vec![
GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW))?,
GC2::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW))?,
],
vec![cf1_x, cf2_x],
vec![cf1_cmT, cf2_cmT],
)?;
// Back to Primary Part
// P.4.b compute and check the second output of F'
// Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
// Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?;
let (cf_u_i1_x_base, _) =
CycleFoldCommittedInstanceVar::<C2, GC2>::new_constant(cs.clone(), cf_u_dummy)?
.hash(&sponge, pp_hash.clone())?;
let cf_x = FpVar::new_input(cs.clone(), || {
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
})?;
cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?;
Ok(())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ark_crypto_primitives::sponge::{
constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge},
};
use ark_pallas::{Fr, Projective};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem;
use std::error::Error; use std::error::Error;
use super::*; use super::*;
@ -125,8 +487,11 @@ mod tests {
transcript::poseidon::poseidon_canonical_config, transcript::poseidon::poseidon_canonical_config,
}; };
use ark_bn254::{Fr, G1Projective as Projective};
use ark_relations::r1cs::ConstraintSystem;
#[test] #[test]
fn test_fold_gadget() -> Result<(), Box<dyn Error>> {
fn test_folding_gadget() -> Result<(), Box<dyn Error>> {
let k = 7; let k = 7;
let (witness, instance, witnesses, instances) = prepare_inputs(k); let (witness, instance, witnesses, instances) = prepare_inputs(k);
let r1cs = get_test_r1cs::<Fr>(); let r1cs = get_test_r1cs::<Fr>();
@ -136,7 +501,7 @@ mod tests {
let mut transcript_p = PoseidonSponge::new(&poseidon_config); let mut transcript_p = PoseidonSponge::new(&poseidon_config);
let mut transcript_v = PoseidonSponge::new(&poseidon_config); let mut transcript_v = PoseidonSponge::new(&poseidon_config);
let (_, _, F_coeffs, K_coeffs) = Folding::<Projective>::prove(
let (_, _, F_coeffs, K_coeffs, _, _) = Folding::<Projective>::prove(
&mut transcript_p, &mut transcript_p,
&r1cs, &r1cs,
&instance, &instance,
@ -147,7 +512,6 @@ mod tests {
let folded_instance = Folding::<Projective>::verify( let folded_instance = Folding::<Projective>::verify(
&mut transcript_v, &mut transcript_v,
&r1cs,
&instance, &instance,
&instances, &instances,
F_coeffs.clone(), F_coeffs.clone(),
@ -161,7 +525,7 @@ mod tests {
let F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(F_coeffs))?; let F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(F_coeffs))?;
let K_coeffs_var = Vec::new_witness(cs.clone(), || Ok(K_coeffs))?; let K_coeffs_var = Vec::new_witness(cs.clone(), || Ok(K_coeffs))?;
let folded_instance_var = FoldingGadget::fold_committed_instance(
let (folded_instance_var, _) = FoldingGadget::fold_committed_instance(
&mut transcript_var, &mut transcript_var,
&instance_var, &instance_var,
&instances_var, &instances_var,
@ -170,7 +534,6 @@ mod tests {
)?; )?;
assert_eq!(folded_instance.betas, folded_instance_var.betas.value()?); assert_eq!(folded_instance.betas, folded_instance_var.betas.value()?);
assert_eq!(folded_instance.e, folded_instance_var.e.value()?); assert_eq!(folded_instance.e, folded_instance_var.e.value()?);
assert_eq!(folded_instance.u, folded_instance_var.u.value()?);
assert_eq!(folded_instance.x, folded_instance_var.x.value()?); assert_eq!(folded_instance.x, folded_instance_var.x.value()?);
assert!(cs.is_satisfied()?); assert!(cs.is_satisfied()?);

+ 95
- 162
folding-schemes/src/folding/protogalaxy/folding.rs

@ -6,18 +6,19 @@ use ark_poly::{
univariate::{DensePolynomial, SparsePolynomial}, univariate::{DensePolynomial, SparsePolynomial},
DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial, DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial,
}; };
use ark_std::{cfg_into_iter, log2, Zero};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use ark_std::{cfg_into_iter, log2, One, Zero};
use rayon::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
use super::utils::{all_powers, betas_star, exponential_powers};
use super::utils::{all_powers, betas_star, exponential_powers, pow_i};
use super::ProtoGalaxyError; use super::ProtoGalaxyError;
use super::{CommittedInstance, Witness}; use super::{CommittedInstance, Witness};
use crate::arith::r1cs::R1CS;
#[cfg(test)]
use crate::arith::r1cs::RelaxedR1CS;
use crate::arith::{r1cs::R1CS, Arith};
use crate::transcript::Transcript; use crate::transcript::Transcript;
use crate::utils::vec::*; use crate::utils::vec::*;
use crate::utils::virtual_polynomial::bit_decompose;
use crate::Error; use crate::Error;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -48,6 +49,8 @@ where
Witness<C::ScalarField>, Witness<C::ScalarField>,
Vec<C::ScalarField>, // F_X coeffs Vec<C::ScalarField>, // F_X coeffs
Vec<C::ScalarField>, // K_X coeffs Vec<C::ScalarField>, // K_X coeffs
Vec<C::ScalarField>, // L_X evals
Vec<C>, // phi_stars
), ),
Error, Error,
> { > {
@ -63,19 +66,25 @@ where
let k = vec_instances.len(); let k = vec_instances.len();
let t = instance.betas.len(); let t = instance.betas.len();
let n = r1cs.A.n_cols; let n = r1cs.A.n_cols;
let m = r1cs.A.n_rows;
let z = [vec![instance.u], instance.x.clone(), w.w.clone()].concat();
let z = [vec![C::ScalarField::one()], instance.x.clone(), w.w.clone()].concat();
if z.len() != n { if z.len() != n {
return Err(Error::NotSameLength( return Err(Error::NotSameLength(
"z.len()".to_string(), "z.len()".to_string(),
z.len(), z.len(),
"n".to_string(),
"number of variables in R1CS".to_string(), // hardcoded to R1CS
n, n,
)); ));
} }
if log2(n) as usize != t {
return Err(Error::NotEqual);
if log2(m) as usize != t {
return Err(Error::NotSameLength(
"log2(number of constraints in R1CS)".to_string(),
log2(m) as usize,
"instance.betas.len()".to_string(),
t,
));
} }
if !(k + 1).is_power_of_two() { if !(k + 1).is_power_of_two() {
return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k))); return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k)));
@ -88,13 +97,24 @@ where
let delta = transcript.get_challenge(); let delta = transcript.get_challenge();
let deltas = exponential_powers(delta, t); let deltas = exponential_powers(delta, t);
let f_z = eval_f(r1cs, &z)?;
let mut f_z = r1cs.eval_relation(&z)?;
if f_z.len() != m {
return Err(Error::NotSameLength(
"number of constraints in R1CS".to_string(),
m,
"f_z.len()".to_string(),
f_z.len(),
));
}
f_z.resize(1 << t, C::ScalarField::zero());
// F(X) // F(X)
let F_X: SparsePolynomial<C::ScalarField> = let F_X: SparsePolynomial<C::ScalarField> =
calc_f_from_btree(&f_z, &instance.betas, &deltas).expect("Error calculating F[x]"); calc_f_from_btree(&f_z, &instance.betas, &deltas).expect("Error calculating F[x]");
let F_X_dense = DensePolynomial::from(F_X.clone()); let F_X_dense = DensePolynomial::from(F_X.clone());
transcript.absorb(&F_X_dense.coeffs);
let mut F_coeffs = F_X_dense.coeffs;
F_coeffs.resize(t, C::ScalarField::zero());
transcript.absorb(&F_coeffs);
let alpha = transcript.get_challenge(); let alpha = transcript.get_challenge();
@ -107,16 +127,14 @@ where
// sanity check: check that the new randomized instance (the original instance but with // sanity check: check that the new randomized instance (the original instance but with
// 'refreshed' randomness) satisfies the relation. // 'refreshed' randomness) satisfies the relation.
#[cfg(test)] #[cfg(test)]
tests::check_instance(
r1cs,
r1cs.check_relaxed_relation(
w,
&CommittedInstance { &CommittedInstance {
phi: instance.phi, phi: instance.phi,
betas: betas_star.clone(), betas: betas_star.clone(),
e: F_alpha, e: F_alpha,
u: instance.u,
x: instance.x.clone(), x: instance.x.clone(),
}, },
w,
)?; )?;
let zs: Vec<Vec<C::ScalarField>> = std::iter::once(z.clone()) let zs: Vec<Vec<C::ScalarField>> = std::iter::once(z.clone())
@ -125,12 +143,12 @@ where
.iter() .iter()
.zip(vec_instances) .zip(vec_instances)
.map(|(wj, uj)| { .map(|(wj, uj)| {
let zj = [vec![uj.u], uj.x.clone(), wj.w.clone()].concat();
let zj = [vec![C::ScalarField::one()], uj.x.clone(), wj.w.clone()].concat();
if zj.len() != n { if zj.len() != n {
return Err(Error::NotSameLength( return Err(Error::NotSameLength(
"zj.len()".to_string(), "zj.len()".to_string(),
zj.len(), zj.len(),
"n".to_string(),
"number of variables in R1CS".to_string(),
n, n,
)); ));
} }
@ -153,26 +171,19 @@ where
// each iteration evaluates G(h) // each iteration evaluates G(h)
// inner = L_0(x) * z + \sum_k L_i(x) * z_j // inner = L_0(x) * z + \sum_k L_i(x) * z_j
let mut inner: Vec<C::ScalarField> = vec![C::ScalarField::zero(); zs[0].len()]; let mut inner: Vec<C::ScalarField> = vec![C::ScalarField::zero(); zs[0].len()];
for (i, z) in zs.iter().enumerate() {
for (z, L) in zs.iter().zip(&L_X) {
// Li_z_h = (Li(X)*zj)(h) = Li(h) * zj // Li_z_h = (Li(X)*zj)(h) = Li(h) * zj
let mut Liz_h: Vec<C::ScalarField> = vec![C::ScalarField::zero(); z.len()];
let Lh = L.evaluate(&h);
for (j, zj) in z.iter().enumerate() { for (j, zj) in z.iter().enumerate() {
Liz_h[j] = (&L_X[i] * *zj).evaluate(&h);
}
for j in 0..inner.len() {
inner[j] += Liz_h[j];
inner[j] += Lh * zj;
} }
} }
let f_ev = eval_f(r1cs, &inner)?;
let f_ev = r1cs.eval_relation(&inner)?;
let mut Gsum = C::ScalarField::zero();
for (i, f_ev_i) in f_ev.iter().enumerate() {
let pow_i_betas = pow_i(i, &betas_star);
let curr = pow_i_betas * f_ev_i;
Gsum += curr;
}
G_evals[hi] = Gsum;
G_evals[hi] = cfg_into_iter!(f_ev)
.enumerate()
.map(|(i, f_ev_i)| pow_i(i, &betas_star) * f_ev_i)
.sum();
} }
let G_X: DensePolynomial<C::ScalarField> = let G_X: DensePolynomial<C::ScalarField> =
Evaluations::<C::ScalarField>::from_vec_and_domain(G_evals, G_domain).interpolate(); Evaluations::<C::ScalarField>::from_vec_and_domain(G_evals, G_domain).interpolate();
@ -190,7 +201,9 @@ where
return Err(Error::ProtoGalaxy(ProtoGalaxyError::RemainderNotZero)); return Err(Error::ProtoGalaxy(ProtoGalaxyError::RemainderNotZero));
} }
transcript.absorb(&K_X.coeffs);
let mut K_coeffs = K_X.coeffs.clone();
K_coeffs.resize(d * k + 1, C::ScalarField::zero());
transcript.absorb(&K_coeffs);
let gamma = transcript.get_challenge(); let gamma = transcript.get_challenge();
@ -200,17 +213,18 @@ where
.map(|L| L.evaluate(&gamma)) .map(|L| L.evaluate(&gamma))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut phi_stars = vec![];
let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma); let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma);
let mut w_star = vec_scalar_mul(&w.w, &L_X_evals[0]); let mut w_star = vec_scalar_mul(&w.w, &L_X_evals[0]);
let mut r_w_star = w.r_w * L_X_evals[0]; let mut r_w_star = w.r_w * L_X_evals[0];
let mut phi_star = instance.phi * L_X_evals[0]; let mut phi_star = instance.phi * L_X_evals[0];
let mut u_star = instance.u * L_X_evals[0];
let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]); let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]);
for i in 0..k { for i in 0..k {
w_star = vec_add(&w_star, &vec_scalar_mul(&vec_w[i].w, &L_X_evals[i + 1]))?; w_star = vec_add(&w_star, &vec_scalar_mul(&vec_w[i].w, &L_X_evals[i + 1]))?;
r_w_star += vec_w[i].r_w * L_X_evals[i + 1]; r_w_star += vec_w[i].r_w * L_X_evals[i + 1];
phi_stars.push(phi_star); // Push before updating. We don't need the last one
phi_star += vec_instances[i].phi * L_X_evals[i + 1]; phi_star += vec_instances[i].phi * L_X_evals[i + 1];
u_star += vec_instances[i].u * L_X_evals[i + 1];
x_star = vec_add( x_star = vec_add(
&x_star, &x_star,
&vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]), &vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]),
@ -222,22 +236,22 @@ where
betas: betas_star, betas: betas_star,
phi: phi_star, phi: phi_star,
e: e_star, e: e_star,
u: u_star,
x: x_star, x: x_star,
}, },
Witness { Witness {
w: w_star, w: w_star,
r_w: r_w_star, r_w: r_w_star,
}, },
F_X_dense.coeffs,
K_X.coeffs,
F_coeffs,
K_coeffs,
L_X_evals,
phi_stars,
)) ))
} }
/// implements the non-interactive Verifier from the folding scheme described in section 4 /// implements the non-interactive Verifier from the folding scheme described in section 4
pub fn verify( pub fn verify(
transcript: &mut impl Transcript<C::ScalarField>, transcript: &mut impl Transcript<C::ScalarField>,
r1cs: &R1CS<C::ScalarField>,
// running instance // running instance
instance: &CommittedInstance<C>, instance: &CommittedInstance<C>,
// incoming instances // incoming instances
@ -247,7 +261,6 @@ where
K_coeffs: Vec<C::ScalarField>, K_coeffs: Vec<C::ScalarField>,
) -> Result<CommittedInstance<C>, Error> { ) -> Result<CommittedInstance<C>, Error> {
let t = instance.betas.len(); let t = instance.betas.len();
let n = r1cs.A.n_cols;
// absorb the committed instances // absorb the committed instances
transcript.absorb(instance); transcript.absorb(instance);
@ -259,7 +272,7 @@ where
transcript.absorb(&F_coeffs); transcript.absorb(&F_coeffs);
let alpha = transcript.get_challenge(); let alpha = transcript.get_challenge();
let alphas = all_powers(alpha, n);
let alphas = all_powers(alpha, t);
// F(alpha) = e + \sum_t F_i * alpha^i // F(alpha) = e + \sum_t F_i * alpha^i
let mut F_alpha = instance.e; let mut F_alpha = instance.e;
@ -269,6 +282,8 @@ where
let betas_star = betas_star(&instance.betas, &deltas, alpha); let betas_star = betas_star(&instance.betas, &deltas, alpha);
transcript.absorb(&K_coeffs);
let k = vec_instances.len(); let k = vec_instances.len();
let H = let H =
GeneralEvaluationDomain::<C::ScalarField>::new(k + 1).ok_or(Error::NewDomainFail)?; GeneralEvaluationDomain::<C::ScalarField>::new(k + 1).ok_or(Error::NewDomainFail)?;
@ -277,8 +292,6 @@ where
let K_X: DensePolynomial<C::ScalarField> = let K_X: DensePolynomial<C::ScalarField> =
DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs); DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs);
transcript.absorb(&K_X.coeffs);
let gamma = transcript.get_challenge(); let gamma = transcript.get_challenge();
let L_X_evals = L_X let L_X_evals = L_X
@ -290,11 +303,9 @@ where
let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma); let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma);
let mut phi_star = instance.phi * L_X_evals[0]; let mut phi_star = instance.phi * L_X_evals[0];
let mut u_star = instance.u * L_X_evals[0];
let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]); let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]);
for i in 0..k { for i in 0..k {
phi_star += vec_instances[i].phi * L_X_evals[i + 1]; phi_star += vec_instances[i].phi * L_X_evals[i + 1];
u_star += vec_instances[i].u * L_X_evals[i + 1];
x_star = vec_add( x_star = vec_add(
&x_star, &x_star,
&vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]), &vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]),
@ -306,30 +317,11 @@ where
betas: betas_star, betas: betas_star,
phi: phi_star, phi: phi_star,
e: e_star, e: e_star,
u: u_star,
x: x_star, x: x_star,
}) })
} }
} }
// naive impl of pow_i for betas, assuming that betas=(b, b^2, b^4, ..., b^{2^{t-1}})
fn pow_i<F: PrimeField>(i: usize, betas: &[F]) -> F {
// WIP check if makes more sense to do it with ifs instead of arithmetic
let n = 2_u64.pow(betas.len() as u32);
let b = bit_decompose(i as u64, n as usize);
let mut r: F = F::one();
for (j, beta_j) in betas.iter().enumerate() {
let mut b_j = F::zero();
if b[j] {
b_j = F::one();
}
r *= (F::one() - b_j) + b_j * beta_j;
}
r
}
/// calculates F[x] using the optimized binary-tree technique /// calculates F[x] using the optimized binary-tree technique
/// described in Claim 4.4 /// described in Claim 4.4
/// of [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf) /// of [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
@ -394,16 +386,6 @@ pub fn lagrange_polys(
lagrange_polynomials lagrange_polynomials
} }
// f(w) in R1CS context. For the moment we use R1CS, in the future we will abstract this with a
// trait
fn eval_f<F: PrimeField>(r1cs: &R1CS<F>, z: &[F]) -> Result<Vec<F>, Error> {
let Az = mat_vec_mul(&r1cs.A, z)?;
let Bz = mat_vec_mul(&r1cs.B, z)?;
let Cz = mat_vec_mul(&r1cs.C, z)?;
let AzBz = hadamard(&Az, &Bz)?;
vec_sub(&AzBz, &Cz)
}
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
@ -412,38 +394,10 @@ pub mod tests {
use ark_pallas::{Fr, Projective}; use ark_pallas::{Fr, Projective};
use ark_std::{rand::Rng, UniformRand}; use ark_std::{rand::Rng, UniformRand};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z, get_test_z_split};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z_split};
use crate::commitment::{pedersen::Pedersen, CommitmentScheme}; use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
pub(crate) fn check_instance<C: CurveGroup>(
r1cs: &R1CS<C::ScalarField>,
instance: &CommittedInstance<C>,
w: &Witness<C::ScalarField>,
) -> Result<(), Error> {
let z = [vec![instance.u], instance.x.clone(), w.w.clone()].concat();
if instance.betas.len() != log2(z.len()) as usize {
return Err(Error::NotSameLength(
"instance.betas.len()".to_string(),
instance.betas.len(),
"log2(z.len())".to_string(),
log2(z.len()) as usize,
));
}
let f_z = eval_f(r1cs, &z)?; // f(z)
let mut r = C::ScalarField::zero();
for (i, f_z_i) in f_z.iter().enumerate() {
r += pow_i(i, &instance.betas) * f_z_i;
}
if instance.e == r {
return Ok(());
}
Err(Error::NotSatisfied)
}
#[test] #[test]
fn test_pow_i() { fn test_pow_i() {
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
@ -459,76 +413,54 @@ pub mod tests {
} }
} }
#[test]
fn test_eval_f() {
let mut rng = ark_std::test_rng();
let r1cs = get_test_r1cs::<Fr>();
let mut z = get_test_z::<Fr>(rng.gen::<u16>() as usize);
let f_w = eval_f(&r1cs, &z).unwrap();
assert!(is_zero_vec(&f_w));
z[1] = Fr::from(111);
let f_w = eval_f(&r1cs, &z).unwrap();
assert!(!is_zero_vec(&f_w));
}
// k represents the number of instances to be fold, apart from the running instance // k represents the number of instances to be fold, apart from the running instance
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn prepare_inputs(
pub fn prepare_inputs<C: CurveGroup>(
k: usize, k: usize,
) -> ( ) -> (
Witness<Fr>,
CommittedInstance<Projective>,
Vec<Witness<Fr>>,
Vec<CommittedInstance<Projective>>,
Witness<C::ScalarField>,
CommittedInstance<C>,
Vec<Witness<C::ScalarField>>,
Vec<CommittedInstance<C>>,
) { ) {
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
let (u, x, w) = get_test_z_split::<Fr>(rng.gen::<u16>() as usize);
let (_, x, w) = get_test_z_split::<C::ScalarField>(rng.gen::<u16>() as usize);
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, w.len()).unwrap();
let (pedersen_params, _) = Pedersen::<C>::setup(&mut rng, w.len()).unwrap();
let n = 1 + x.len() + w.len();
let t = log2(n) as usize;
let t = log2(get_test_r1cs::<C::ScalarField>().A.n_rows) as usize;
let beta = Fr::rand(&mut rng);
let beta = C::ScalarField::rand(&mut rng);
let betas = exponential_powers(beta, t); let betas = exponential_powers(beta, t);
let witness = Witness::<Fr> {
let witness = Witness::<C::ScalarField> {
w, w,
r_w: Fr::rand(&mut rng),
r_w: C::ScalarField::zero(),
}; };
let phi = Pedersen::<Projective, true>::commit(&pedersen_params, &witness.w, &witness.r_w)
.unwrap();
let instance = CommittedInstance::<Projective> {
let phi = Pedersen::<C>::commit(&pedersen_params, &witness.w, &witness.r_w).unwrap();
let instance = CommittedInstance::<C> {
phi, phi,
betas: betas.clone(), betas: betas.clone(),
e: Fr::zero(),
u,
e: C::ScalarField::zero(),
x, x,
}; };
// same for the other instances // same for the other instances
let mut witnesses: Vec<Witness<Fr>> = Vec::new();
let mut instances: Vec<CommittedInstance<Projective>> = Vec::new();
let mut witnesses: Vec<Witness<C::ScalarField>> = Vec::new();
let mut instances: Vec<CommittedInstance<C>> = Vec::new();
#[allow(clippy::needless_range_loop)] #[allow(clippy::needless_range_loop)]
for _ in 0..k { for _ in 0..k {
let (u_i, x_i, w_i) = get_test_z_split::<Fr>(rng.gen::<u16>() as usize);
let witness_i = Witness::<Fr> {
let (_, x_i, w_i) = get_test_z_split::<C::ScalarField>(rng.gen::<u16>() as usize);
let witness_i = Witness::<C::ScalarField> {
w: w_i, w: w_i,
r_w: Fr::rand(&mut rng),
r_w: C::ScalarField::zero(),
}; };
let phi_i = Pedersen::<Projective, true>::commit(
&pedersen_params,
&witness_i.w,
&witness_i.r_w,
)
.unwrap();
let instance_i = CommittedInstance::<Projective> {
let phi_i =
Pedersen::<C>::commit(&pedersen_params, &witness_i.w, &witness_i.r_w).unwrap();
let instance_i = CommittedInstance::<C> {
phi: phi_i, phi: phi_i,
betas: vec![], betas: vec![],
e: Fr::zero(),
u: u_i,
e: C::ScalarField::zero(),
x: x_i, x: x_i,
}; };
witnesses.push(witness_i); witnesses.push(witness_i);
@ -549,20 +481,20 @@ pub mod tests {
let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config); let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config); let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config);
let (folded_instance, folded_witness, F_coeffs, K_coeffs) = Folding::<Projective>::prove(
&mut transcript_p,
&r1cs,
&instance,
&witness,
&instances,
&witnesses,
)
.unwrap();
let (folded_instance, folded_witness, F_coeffs, K_coeffs, _, _) =
Folding::<Projective>::prove(
&mut transcript_p,
&r1cs,
&instance,
&witness,
&instances,
&witnesses,
)
.unwrap();
// verifier // verifier
let folded_instance_v = Folding::<Projective>::verify( let folded_instance_v = Folding::<Projective>::verify(
&mut transcript_v, &mut transcript_v,
&r1cs,
&instance, &instance,
&instances, &instances,
F_coeffs, F_coeffs,
@ -577,7 +509,8 @@ pub mod tests {
assert!(!folded_instance.e.is_zero()); assert!(!folded_instance.e.is_zero());
// check that the folded instance satisfies the relation // check that the folded instance satisfies the relation
check_instance(&r1cs, &folded_instance, &folded_witness).unwrap();
r1cs.check_relaxed_relation(&folded_witness, &folded_instance)
.unwrap();
} }
#[test] #[test]
@ -598,7 +531,7 @@ pub mod tests {
// generate the instances to be fold // generate the instances to be fold
let (_, _, witnesses, instances) = prepare_inputs(k); let (_, _, witnesses, instances) = prepare_inputs(k);
let (folded_instance, folded_witness, F_coeffs, K_coeffs) =
let (folded_instance, folded_witness, F_coeffs, K_coeffs, _, _) =
Folding::<Projective>::prove( Folding::<Projective>::prove(
&mut transcript_p, &mut transcript_p,
&r1cs, &r1cs,
@ -612,7 +545,6 @@ pub mod tests {
// verifier // verifier
let folded_instance_v = Folding::<Projective>::verify( let folded_instance_v = Folding::<Projective>::verify(
&mut transcript_v, &mut transcript_v,
&r1cs,
&running_instance, &running_instance,
&instances, &instances,
F_coeffs, F_coeffs,
@ -626,7 +558,8 @@ pub mod tests {
assert!(!folded_instance.e.is_zero()); assert!(!folded_instance.e.is_zero());
// check that the folded instance satisfies the relation // check that the folded instance satisfies the relation
check_instance(&r1cs, &folded_instance, &folded_witness).unwrap();
r1cs.check_relaxed_relation(&folded_witness, &folded_instance)
.unwrap();
running_witness = folded_witness; running_witness = folded_witness;
running_instance = folded_instance; running_instance = folded_instance;

+ 990
- 11
folding-schemes/src/folding/protogalaxy/mod.rs
File diff suppressed because it is too large
View File


+ 74
- 4
folding-schemes/src/folding/protogalaxy/traits.rs

@ -3,9 +3,15 @@ use ark_ec::CurveGroup;
use ark_ff::PrimeField; use ark_ff::PrimeField;
use ark_r1cs_std::{fields::fp::FpVar, uint8::UInt8, ToConstraintFieldGadget}; use ark_r1cs_std::{fields::fp::FpVar, uint8::UInt8, ToConstraintFieldGadget};
use ark_relations::r1cs::SynthesisError; use ark_relations::r1cs::SynthesisError;
use ark_std::{cfg_iter, log2, rand::RngCore, One, Zero};
use rayon::prelude::*;
use super::{CommittedInstance, CommittedInstanceVar};
use crate::transcript::AbsorbNonNative;
use super::{utils::pow_i, CommittedInstance, CommittedInstanceVar, Witness};
use crate::{
arith::r1cs::{RelaxedR1CS, R1CS},
transcript::AbsorbNonNative,
Error,
};
// Implements the trait for absorbing ProtoGalaxy's CommittedInstance. // Implements the trait for absorbing ProtoGalaxy's CommittedInstance.
impl<C: CurveGroup> Absorb for CommittedInstance<C> impl<C: CurveGroup> Absorb for CommittedInstance<C>
@ -22,7 +28,6 @@ where
.to_sponge_field_elements(dest); .to_sponge_field_elements(dest);
self.betas.to_sponge_field_elements(dest); self.betas.to_sponge_field_elements(dest);
self.e.to_sponge_field_elements(dest); self.e.to_sponge_field_elements(dest);
self.u.to_sponge_field_elements(dest);
self.x.to_sponge_field_elements(dest); self.x.to_sponge_field_elements(dest);
} }
} }
@ -38,9 +43,74 @@ impl AbsorbGadget for CommittedInstanceVar {
self.phi.to_constraint_field()?, self.phi.to_constraint_field()?,
self.betas.to_sponge_field_elements()?, self.betas.to_sponge_field_elements()?,
self.e.to_sponge_field_elements()?, self.e.to_sponge_field_elements()?,
self.u.to_sponge_field_elements()?,
self.x.to_sponge_field_elements()?, self.x.to_sponge_field_elements()?,
] ]
.concat()) .concat())
} }
} }
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C::ScalarField>, CommittedInstance<C>>
for R1CS<C::ScalarField>
{
fn dummy_running_instance(&self) -> (Witness<C::ScalarField>, CommittedInstance<C>) {
let w_len = self.A.n_cols - 1 - self.l;
let w_dummy = Witness::new(vec![C::ScalarField::zero(); w_len]);
let u_dummy = CommittedInstance::<C>::dummy_running(self.l, log2(self.A.n_rows) as usize);
(w_dummy, u_dummy)
}
fn dummy_incoming_instance(&self) -> (Witness<C::ScalarField>, CommittedInstance<C>) {
let w_len = self.A.n_cols - 1 - self.l;
let w_dummy = Witness::new(vec![C::ScalarField::zero(); w_len]);
let u_dummy = CommittedInstance::<C>::dummy_incoming(self.l);
(w_dummy, u_dummy)
}
fn is_relaxed(_w: &Witness<C::ScalarField>, u: &CommittedInstance<C>) -> bool {
u.e != C::ScalarField::zero() || !u.betas.is_empty()
}
fn extract_z(w: &Witness<C::ScalarField>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
[&[C::ScalarField::one()][..], &u.x, &w.w].concat()
}
fn check_error_terms(
_w: &Witness<C::ScalarField>,
u: &CommittedInstance<C>,
e: Vec<C::ScalarField>,
) -> Result<(), Error> {
if u.betas.len() != log2(e.len()) as usize {
return Err(Error::NotSameLength(
"instance.betas.len()".to_string(),
u.betas.len(),
"log2(e.len())".to_string(),
log2(e.len()) as usize,
));
}
let r = cfg_iter!(e)
.enumerate()
.map(|(i, e_i)| pow_i(i, &u.betas) * e_i)
.sum();
if u.e == r {
Ok(())
} else {
Err(Error::NotSatisfied)
}
}
fn sample<CS>(
&self,
_params: &CS::ProverParams,
_rng: impl RngCore,
) -> Result<(Witness<C::ScalarField>, CommittedInstance<C>), Error>
where
CS: crate::commitment::CommitmentScheme<C, true>,
{
// Sampling a random pair of witness and committed instance is required
// for the zero-knowledge layer for ProtoGalaxy, which is not supported
// yet.
// Tracking issue: https://github.com/privacy-scaling-explorations/sonobe/issues/82
unimplemented!()
}
}

+ 57
- 0
folding-schemes/src/folding/protogalaxy/utils.rs

@ -1,5 +1,6 @@
use ark_ff::PrimeField; use ark_ff::PrimeField;
use ark_r1cs_std::fields::{fp::FpVar, FieldVar}; use ark_r1cs_std::fields::{fp::FpVar, FieldVar};
use num_integer::Integer;
/// Returns (b, b^2, b^4, ..., b^{2^{t-1}}) /// Returns (b, b^2, b^4, ..., b^{2^{t-1}})
pub fn exponential_powers<F: PrimeField>(b: F, t: usize) -> Vec<F> { pub fn exponential_powers<F: PrimeField>(b: F, t: usize) -> Vec<F> {
@ -70,6 +71,40 @@ pub fn betas_star_var(
.collect::<Vec<FpVar<F>>>() .collect::<Vec<FpVar<F>>>()
} }
/// Returns the product of selected elements in `betas`.
/// For every index `j`, whether `betas[j]` is selected is determined by the
/// `j`-th bit in the binary (little endian) representation of `i`.
///
/// If `betas = (β, β^2, β^4, ..., β^{2^{t-1}})`, then the result is equal to
/// `β^i`.
pub fn pow_i<F: PrimeField>(mut i: usize, betas: &[F]) -> F {
let mut j = 0;
let mut r = F::one();
while i > 0 {
if i.is_odd() {
r *= betas[j];
}
i >>= 1;
j += 1;
}
r
}
/// The in-circuit version of `pow_i`
#[allow(dead_code)] // Will remove this once we have the decider circuit for Protogalaxy
pub fn pow_i_var<F: PrimeField>(mut i: usize, betas: &[FpVar<F>]) -> FpVar<F> {
let mut j = 0;
let mut r = FieldVar::one();
while i > 0 {
if i.is_odd() {
r *= &betas[j];
}
i >>= 1;
j += 1;
}
r
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::error::Error; use std::error::Error;
@ -78,6 +113,7 @@ mod tests {
use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_std::{test_rng, UniformRand}; use ark_std::{test_rng, UniformRand};
use rand::Rng;
use super::*; use super::*;
@ -144,4 +180,25 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn test_pow_i() -> Result<(), Box<dyn Error>> {
let rng = &mut test_rng();
for t in 1..10 {
let cs = ConstraintSystem::<Fr>::new_ref();
let betas = (0..t).map(|_| Fr::rand(rng)).collect::<Vec<_>>();
let i = rng.gen_range(0..(1 << t));
let betas_var = Vec::new_witness(cs.clone(), || Ok(betas.clone()))?;
let r = pow_i(i, &betas);
let r_var = pow_i_var(i, &betas_var);
assert_eq!(r, r_var.value()?);
assert!(cs.is_satisfied()?);
}
Ok(())
}
} }

+ 2
- 2
folding-schemes/src/frontend/circom/mod.rs

@ -258,7 +258,7 @@ pub mod tests {
// Allocates z_i1 by using step_native function. // Allocates z_i1 by using step_native function.
let z_i = vec![Fr::from(3_u32)]; let z_i = vec![Fr::from(3_u32)];
let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
let wrapper_circuit = crate::frontend::utils::WrapperCircuit {
FC: circom_fcircuit.clone(), FC: circom_fcircuit.clone(),
z_i: Some(z_i.clone()), z_i: Some(z_i.clone()),
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()), z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
@ -367,7 +367,7 @@ pub mod tests {
// Allocates z_i1 by using step_native function. // Allocates z_i1 by using step_native function.
let z_i = vec![Fr::from(3_u32)]; let z_i = vec![Fr::from(3_u32)];
let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
let wrapper_circuit = crate::frontend::utils::WrapperCircuit {
FC: circom_fcircuit.clone(), FC: circom_fcircuit.clone(),
z_i: Some(z_i.clone()), z_i: Some(z_i.clone()),
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()), z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),

+ 2
- 123
folding-schemes/src/frontend/mod.rs

@ -7,6 +7,7 @@ use ark_std::fmt::Debug;
pub mod circom; pub mod circom;
pub mod noir; pub mod noir;
pub mod noname; pub mod noname;
pub mod utils;
/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie. /// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
/// inside the agmented F' function). /// inside the agmented F' function).
@ -53,131 +54,9 @@ pub trait FCircuit: Clone + Debug {
pub mod tests { pub mod tests {
use super::*; use super::*;
use ark_bn254::Fr; use ark_bn254::Fr;
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
use core::marker::PhantomData;
/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`.
/// `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next step, `z_{i+1}` will be
/// assigned to `z_i`, and a new `z+{i+1}` will be computted.
#[derive(Clone, Copy, Debug)]
pub struct CubicFCircuit<F: PrimeField> {
_f: PhantomData<F>,
}
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
type Params = ();
fn new(_params: Self::Params) -> Result<Self, Error> {
Ok(Self { _f: PhantomData })
}
fn state_len(&self) -> usize {
1
}
fn external_inputs_len(&self) -> usize {
0
}
fn step_native(
&self,
_i: usize,
z_i: Vec<F>,
_external_inputs: Vec<F>,
) -> Result<Vec<F>, Error> {
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
}
fn generate_step_constraints(
&self,
cs: ConstraintSystemRef<F>,
_i: usize,
z_i: Vec<FpVar<F>>,
_external_inputs: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
let z_i = z_i[0].clone();
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
}
}
/// CustomFCircuit is a circuit that has the number of constraints specified in the
/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices.
#[derive(Clone, Copy, Debug)]
pub struct CustomFCircuit<F: PrimeField> {
_f: PhantomData<F>,
pub n_constraints: usize,
}
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> {
type Params = usize;
fn new(params: Self::Params) -> Result<Self, Error> {
Ok(Self {
_f: PhantomData,
n_constraints: params,
})
}
fn state_len(&self) -> usize {
1
}
fn external_inputs_len(&self) -> usize {
0
}
fn step_native(
&self,
_i: usize,
z_i: Vec<F>,
_external_inputs: Vec<F>,
) -> Result<Vec<F>, Error> {
let mut z_i1 = F::one();
for _ in 0..self.n_constraints - 1 {
z_i1 *= z_i[0];
}
Ok(vec![z_i1])
}
fn generate_step_constraints(
&self,
cs: ConstraintSystemRef<F>,
_i: usize,
z_i: Vec<FpVar<F>>,
_external_inputs: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
for _ in 0..self.n_constraints - 1 {
z_i1 *= z_i[0].clone();
}
Ok(vec![z_i1])
}
}
/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This
/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping
/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints
/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in
/// the the AugmentedF logic but in the wrapping of the circuits.
pub struct WrapperCircuit<F: PrimeField, FC: FCircuit<F>> {
pub FC: FC, // F circuit
pub z_i: Option<Vec<F>>,
pub z_i1: Option<Vec<F>>,
}
impl<F, FC> ConstraintSynthesizer<F> for WrapperCircuit<F, FC>
where
F: PrimeField,
FC: FCircuit<F>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let z_i = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
Ok(self.z_i.unwrap_or(vec![F::zero()]))
})?;
let z_i1 = Vec::<FpVar<F>>::new_input(cs.clone(), || {
Ok(self.z_i1.unwrap_or(vec![F::zero()]))
})?;
let computed_z_i1 =
self.FC
.generate_step_constraints(cs.clone(), 0, z_i.clone(), vec![])?;
computed_z_i1.enforce_equal(&z_i1)?;
Ok(())
}
}
use utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
#[test] #[test]
fn test_testfcircuit() { fn test_testfcircuit() {

+ 181
- 0
folding-schemes/src/frontend/utils.rs

@ -0,0 +1,181 @@
use ark_ff::PrimeField;
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar};
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
#[cfg(test)]
use ark_std::marker::PhantomData;
use ark_std::{fmt::Debug, Zero};
use super::FCircuit;
use crate::Error;
/// DummyCircuit is a circuit that has dummy state and external inputs whose
/// lengths are specified in the `state_len` and `external_inputs_len`
/// parameters, without any constraints.
#[derive(Clone, Debug)]
pub struct DummyCircuit {
state_len: usize,
external_inputs_len: usize,
}
impl<F: PrimeField> FCircuit<F> for DummyCircuit {
type Params = (usize, usize);
fn new((state_len, external_inputs_len): Self::Params) -> Result<Self, Error> {
Ok(Self {
state_len,
external_inputs_len,
})
}
fn state_len(&self) -> usize {
self.state_len
}
fn external_inputs_len(&self) -> usize {
self.external_inputs_len
}
fn step_native(
&self,
_i: usize,
_z_i: Vec<F>,
_external_inputs: Vec<F>,
) -> Result<Vec<F>, Error> {
Ok(vec![F::zero(); self.state_len])
}
fn generate_step_constraints(
&self,
cs: ConstraintSystemRef<F>,
_i: usize,
_z_i: Vec<FpVar<F>>,
_external_inputs: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
Vec::new_witness(cs.clone(), || Ok(vec![Zero::zero(); self.state_len]))
}
}
/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`.
/// `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next step, `z_{i+1}` will be
/// assigned to `z_i`, and a new `z+{i+1}` will be computted.
#[cfg(test)]
#[derive(Clone, Copy, Debug)]
pub struct CubicFCircuit<F: PrimeField> {
_f: PhantomData<F>,
}
#[cfg(test)]
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
type Params = ();
fn new(_params: Self::Params) -> Result<Self, Error> {
Ok(Self { _f: PhantomData })
}
fn state_len(&self) -> usize {
1
}
fn external_inputs_len(&self) -> usize {
0
}
fn step_native(
&self,
_i: usize,
z_i: Vec<F>,
_external_inputs: Vec<F>,
) -> Result<Vec<F>, Error> {
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
}
fn generate_step_constraints(
&self,
cs: ConstraintSystemRef<F>,
_i: usize,
z_i: Vec<FpVar<F>>,
_external_inputs: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
let z_i = z_i[0].clone();
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
}
}
/// CustomFCircuit is a circuit that has the number of constraints specified in the
/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices.
#[cfg(test)]
#[derive(Clone, Copy, Debug)]
pub struct CustomFCircuit<F: PrimeField> {
_f: PhantomData<F>,
pub n_constraints: usize,
}
#[cfg(test)]
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> {
type Params = usize;
fn new(params: Self::Params) -> Result<Self, Error> {
Ok(Self {
_f: PhantomData,
n_constraints: params,
})
}
fn state_len(&self) -> usize {
1
}
fn external_inputs_len(&self) -> usize {
0
}
fn step_native(
&self,
_i: usize,
z_i: Vec<F>,
_external_inputs: Vec<F>,
) -> Result<Vec<F>, Error> {
let mut z_i1 = F::one();
for _ in 0..self.n_constraints - 1 {
z_i1 *= z_i[0];
}
Ok(vec![z_i1])
}
fn generate_step_constraints(
&self,
cs: ConstraintSystemRef<F>,
_i: usize,
z_i: Vec<FpVar<F>>,
_external_inputs: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
for _ in 0..self.n_constraints - 1 {
z_i1 *= z_i[0].clone();
}
Ok(vec![z_i1])
}
}
/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This
/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping
/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints
/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in
/// the the AugmentedF logic but in the wrapping of the circuits.
#[cfg(test)]
pub struct WrapperCircuit<F: PrimeField, FC: FCircuit<F>> {
pub FC: FC, // F circuit
pub z_i: Option<Vec<F>>,
pub z_i1: Option<Vec<F>>,
}
#[cfg(test)]
impl<F, FC> ark_relations::r1cs::ConstraintSynthesizer<F> for WrapperCircuit<F, FC>
where
F: PrimeField,
FC: FCircuit<F>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let z_i =
Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(self.z_i.unwrap_or(vec![F::zero()])))?;
let z_i1 =
Vec::<FpVar<F>>::new_input(cs.clone(), || Ok(self.z_i1.unwrap_or(vec![F::zero()])))?;
let computed_z_i1 =
self.FC
.generate_step_constraints(cs.clone(), 0, z_i.clone(), vec![])?;
use ark_r1cs_std::eq::EqGadget;
computed_z_i1.enforce_equal(&z_i1)?;
Ok(())
}
}

+ 10
- 18
folding-schemes/src/utils/vec.rs

@ -82,7 +82,7 @@ pub fn vec_add(a: &[F], b: &[F]) -> Result, Error> {
b.len(), b.len(),
)); ));
} }
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x + y).collect())
Ok(cfg_iter!(a).zip(b).map(|(x, y)| *x + y).collect())
} }
pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> { pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
@ -94,15 +94,15 @@ pub fn vec_sub(a: &[F], b: &[F]) -> Result, Error> {
b.len(), b.len(),
)); ));
} }
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x - y).collect())
Ok(cfg_iter!(a).zip(b).map(|(x, y)| *x - y).collect())
} }
pub fn vec_scalar_mul<F: PrimeField>(vec: &[F], c: &F) -> Vec<F> { pub fn vec_scalar_mul<F: PrimeField>(vec: &[F], c: &F) -> Vec<F> {
vec.iter().map(|a| *a * c).collect()
cfg_iter!(vec).map(|a| *a * c).collect()
} }
pub fn is_zero_vec<F: PrimeField>(vec: &[F]) -> bool { pub fn is_zero_vec<F: PrimeField>(vec: &[F]) -> bool {
vec.iter().all(|a| a.is_zero())
cfg_iter!(vec).all(|a| a.is_zero())
} }
pub fn mat_vec_mul_dense<F: PrimeField>(M: &[Vec<F>], z: &[F]) -> Result<Vec<F>, Error> { pub fn mat_vec_mul_dense<F: PrimeField>(M: &[Vec<F>], z: &[F]) -> Result<Vec<F>, Error> {
@ -118,13 +118,9 @@ pub fn mat_vec_mul_dense(M: &[Vec], z: &[F]) -> Result,
)); ));
} }
let mut r: Vec<F> = vec![F::zero(); M.len()];
for (i, M_i) in M.iter().enumerate() {
for (j, M_ij) in M_i.iter().enumerate() {
r[i] += *M_ij * z[j];
}
}
Ok(r)
Ok(cfg_iter!(M)
.map(|row| row.iter().zip(z).map(|(a, b)| *a * b).sum())
.collect())
} }
pub fn mat_vec_mul<F: PrimeField>(M: &SparseMatrix<F>, z: &[F]) -> Result<Vec<F>, Error> { pub fn mat_vec_mul<F: PrimeField>(M: &SparseMatrix<F>, z: &[F]) -> Result<Vec<F>, Error> {
@ -136,13 +132,9 @@ pub fn mat_vec_mul(M: &SparseMatrix, z: &[F]) -> Result
z.len(), 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 * z[col_i];
}
}
Ok(res)
Ok(cfg_iter!(M.coeffs)
.map(|row| row.iter().map(|(value, col_i)| *value * z[*col_i]).sum())
.collect())
} }
pub fn mat_from_str_mat<F: PrimeField>(str_mat: Vec<Vec<&str>>) -> Result<Vec<Vec<F>>, Error> { pub fn mat_from_str_mat<F: PrimeField>(str_mat: Vec<Vec<&str>>) -> Result<Vec<Vec<F>>, Error> {

Loading…
Cancel
Save