Browse Source

Merge branch 'privacy-scaling-explorations:main' into introducing_mova

update-nifs-interface
Nick Dimitriou 1 month ago
committed by GitHub
parent
commit
3305e18f44
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
44 changed files with 3726 additions and 795 deletions
  1. +2
    -2
      examples/circom_full_flow.rs
  2. +1
    -1
      examples/full_flow.rs
  3. +1
    -1
      examples/noir_full_flow.rs
  4. +1
    -1
      examples/noname_full_flow.rs
  5. +2
    -2
      folding-schemes/Cargo.toml
  6. +20
    -12
      folding-schemes/src/arith/ccs.rs
  7. +15
    -2
      folding-schemes/src/arith/mod.rs
  8. +83
    -115
      folding-schemes/src/arith/r1cs.rs
  9. +2
    -1
      folding-schemes/src/commitment/ipa.rs
  10. +1
    -1
      folding-schemes/src/commitment/kzg.rs
  11. +2
    -2
      folding-schemes/src/commitment/mod.rs
  12. +1
    -1
      folding-schemes/src/commitment/pedersen.rs
  13. +7
    -2
      folding-schemes/src/folding/circuits/cyclefold.rs
  14. +42
    -4
      folding-schemes/src/folding/circuits/nonnative/affine.rs
  15. +3
    -1
      folding-schemes/src/folding/hypernova/cccs.rs
  16. +26
    -29
      folding-schemes/src/folding/hypernova/circuits.rs
  17. +496
    -0
      folding-schemes/src/folding/hypernova/decider_eth.rs
  18. +2
    -2
      folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
  19. +3
    -1
      folding-schemes/src/folding/hypernova/lcccs.rs
  20. +64
    -28
      folding-schemes/src/folding/hypernova/mod.rs
  21. +2
    -2
      folding-schemes/src/folding/hypernova/nimfs.rs
  22. +420
    -0
      folding-schemes/src/folding/hypernova/serialize.rs
  23. +21
    -21
      folding-schemes/src/folding/nova/circuits.rs
  24. +220
    -21
      folding-schemes/src/folding/nova/decider_eth.rs
  25. +52
    -19
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  26. +175
    -37
      folding-schemes/src/folding/nova/mod.rs
  27. +14
    -15
      folding-schemes/src/folding/nova/nifs.rs
  28. +1
    -1
      folding-schemes/src/folding/nova/serialize.rs
  29. +72
    -51
      folding-schemes/src/folding/nova/traits.rs
  30. +41
    -27
      folding-schemes/src/folding/nova/zk.rs
  31. +392
    -29
      folding-schemes/src/folding/protogalaxy/circuits.rs
  32. +95
    -162
      folding-schemes/src/folding/protogalaxy/folding.rs
  33. +990
    -11
      folding-schemes/src/folding/protogalaxy/mod.rs
  34. +74
    -4
      folding-schemes/src/folding/protogalaxy/traits.rs
  35. +57
    -0
      folding-schemes/src/folding/protogalaxy/utils.rs
  36. +18
    -11
      folding-schemes/src/frontend/circom/mod.rs
  37. +39
    -17
      folding-schemes/src/frontend/circom/utils.rs
  38. +2
    -123
      folding-schemes/src/frontend/mod.rs
  39. +11
    -6
      folding-schemes/src/frontend/noir/mod.rs
  40. +181
    -0
      folding-schemes/src/frontend/utils.rs
  41. +1
    -1
      folding-schemes/src/lib.rs
  42. +31
    -0
      folding-schemes/src/utils/mod.rs
  43. +17
    -18
      folding-schemes/src/utils/vec.rs
  44. +26
    -11
      solidity-verifiers/src/verifiers/nova_cyclefold.rs

+ 2
- 2
examples/circom_full_flow.rs

@ -61,7 +61,7 @@ fn main() {
"./folding-schemes/src/frontend/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
);
let f_circuit_params = (r1cs_path, wasm_path, 1, 2);
let f_circuit_params = (r1cs_path.into(), wasm_path.into(), 1, 2);
let f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap();
pub type N =
@ -89,7 +89,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {

+ 1
- 1
examples/full_flow.rs

@ -106,7 +106,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap();
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration
for i in 0..n_steps {

+ 1
- 1
examples/noir_full_flow.rs

@ -79,7 +79,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration
for i in 0..5 {

+ 1
- 1
examples/noname_full_flow.rs

@ -89,7 +89,7 @@ fn main() {
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {

+ 2
- 2
folding-schemes/Cargo.toml

@ -25,12 +25,12 @@ num-bigint = "0.4"
num-integer = "0.1"
color-eyre = "=0.6.2"
sha3 = "0.10"
ark-noname = { git = "https://github.com/dmpierre/ark-noname", branch="feat/sonobe-integration" }
ark-noname = { git = "https://github.com/dmpierre/ark-noname", branch = "feat/sonobe-integration" }
noname = { git = "https://github.com/dmpierre/noname" }
serde_json = "1.0.85" # to (de)serialize JSON
serde = "1.0.203"
acvm = { git = "https://github.com/noir-lang/noir", rev="2b4853e", default-features = false }
arkworks_backend = { git = "https://github.com/dmpierre/arkworks_backend", branch="feat/sonobe-integration" }
noir_arkworks_backend = { package="arkworks_backend", git = "https://github.com/dmpierre/arkworks_backend", branch = "feat/sonobe-integration" }
log = "0.4"
# tmp import for espresso's sumcheck

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

@ -36,8 +36,7 @@ pub struct CCS {
}
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];
for i in 0..self.q {
@ -57,14 +56,7 @@ impl Arith for CCS {
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> {
@ -113,7 +105,10 @@ impl CCS {
#[cfg(test)]
pub mod tests {
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;
pub fn get_test_ccs<F: PrimeField>() -> CCS<F> {
@ -124,9 +119,22 @@ pub mod tests {
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]
fn test_ccs_relation() {
fn test_check_ccs_relation() {
let ccs = get_test_ccs::<Fr>();
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 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
/// public inputs, etc, without the matrices/polynomials values.

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

@ -1,15 +1,13 @@
use crate::commitment::CommitmentScheme;
use crate::folding::nova::{CommittedInstance, Witness};
use crate::RngCore;
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_relations::r1cs::ConstraintSystem;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::rand::Rng;
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;
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
@ -21,16 +19,24 @@ pub struct R1CS {
}
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 Bz = mat_vec_mul(&self.B, 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)?;
if AzBz != Cz {
return Err(Error::NotSatisfied);
}
Ok(())
vec_sub(&AzBz, &uCz)
}
fn params_to_le_bytes(&self) -> Vec<u8> {
@ -44,6 +50,14 @@ impl Arith for R1CS {
}
impl<F: PrimeField> R1CS<F> {
pub fn empty() -> Self {
R1CS {
l: 0,
A: SparseMatrix::empty(),
B: SparseMatrix::empty(),
C: SparseMatrix::empty(),
}
}
pub fn rand<R: Rng>(rng: &mut R, n_rows: usize, n_cols: usize) -> Self {
Self {
l: 1,
@ -57,55 +71,50 @@ impl R1CS {
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
(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
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 Bz = mat_vec_mul(B, z)?;
let AzBz = hadamard(&Az, &Bz)?;
@ -115,66 +124,9 @@ impl RelaxedR1CS {
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
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
@ -221,9 +173,13 @@ pub fn extract_w_x(cs: &ConstraintSystem) -> (Vec, Vec)
#[cfg(test)]
pub mod tests {
use super::*;
use crate::folding::nova::{CommittedInstance, Witness};
use crate::{
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};
@ -234,9 +190,8 @@ pub mod tests {
let r1cs = get_test_r1cs::<Fr>();
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());
}
@ -294,10 +249,23 @@ pub mod tests {
}
#[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 z = get_test_z(5);
r1cs.check_relation(&z).unwrap();
r1cs.relax().check_relation(&z).unwrap();
}
}

+ 2
- 1
folding-schemes/src/commitment/ipa.rs

@ -18,6 +18,7 @@ use ark_r1cs_std::{
ToBitsGadget,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{cfg_iter, rand::RngCore, UniformRand, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
@ -30,7 +31,7 @@ use crate::utils::{
};
use crate::Error;
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Proof<C: CurveGroup> {
a: C::ScalarField,
l: Vec<C::ScalarField>,

+ 1
- 1
folding-schemes/src/commitment/kzg.rs

@ -70,7 +70,7 @@ impl<'a, C: CurveGroup> Valid for ProverKey<'a, C> {
}
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
#[derive(Debug, Clone, Default, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Proof<C: CurveGroup> {
pub eval: C::ScalarField,
pub proof: C,

+ 2
- 2
folding-schemes/src/commitment/mod.rs

@ -13,9 +13,9 @@ pub mod pedersen;
/// CommitmentScheme defines the vector commitment scheme trait. Where `H` indicates if to use the
/// commitment in hiding mode or not.
pub trait CommitmentScheme<C: CurveGroup, const H: bool = false>: Clone + Debug {
type ProverParams: Clone + Debug;
type ProverParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize;
type VerifierParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize;
type Proof: Clone + Debug;
type Proof: Clone + Debug + CanonicalSerialize + CanonicalDeserialize;
type ProverChallenge: Clone + Debug;
type Challenge: Clone + Debug;

+ 1
- 1
folding-schemes/src/commitment/pedersen.rs

@ -12,7 +12,7 @@ use crate::transcript::Transcript;
use crate::utils::vec::{vec_add, vec_scalar_mul};
use crate::Error;
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Proof<C: CurveGroup> {
pub R: C,
pub u: Vec<C::ScalarField>,

+ 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 {
/// `N_INPUT_POINTS` specifies the number of input points that are folded in
/// [`CycleFoldCircuit`] via random linear combinations.
@ -465,7 +467,10 @@ where
// In multifolding schemes such as HyperNova, this is:
// 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()
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
.iter()
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec()))
@ -475,7 +480,7 @@ where
.collect();
let computed_x: Vec<FpVar<CFG::F>> = [
vec![r_fp],
r_fp,
points_aux,
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::{
alloc::{AllocVar, AllocationMode},
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 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> {
// Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
// the circuit.
@ -83,6 +118,10 @@ impl NonNativeAffineVar {
let y = NonNativeUintVar::inputize(*y);
Ok((x, y))
}
pub fn zero() -> Self {
Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap()
}
}
impl<C: CurveGroup> AbsorbNonNative<C::ScalarField> for C {
@ -105,7 +144,6 @@ impl AbsorbNonNativeGadget for NonNativeAffineVar
mod tests {
use super::*;
use ark_pallas::{Fr, Projective};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;

+ 3
- 1
folding-schemes/src/folding/hypernova/cccs.rs

@ -1,6 +1,8 @@
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_serialize::CanonicalDeserialize;
use ark_serialize::CanonicalSerialize;
use ark_std::One;
use ark_std::Zero;
use std::sync::Arc;
@ -17,7 +19,7 @@ use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};
use crate::Error;
/// Committed CCS instance
#[derive(Debug, Clone)]
#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)]
pub struct CCCS<C: CurveGroup> {
// Commitment to witness
pub C: C,

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

@ -475,30 +475,30 @@ pub struct AugmentedFCircuit<
> where
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
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>
@ -891,7 +891,7 @@ mod tests {
use crate::{
arith::{
ccs::tests::{get_test_ccs, get_test_z},
r1cs::extract_w_x,
r1cs::{extract_w_x, RelaxedR1CS},
},
commitment::{pedersen::Pedersen, CommitmentScheme},
folding::{
@ -900,9 +900,8 @@ mod tests {
utils::{compute_c, compute_sigmas_thetas},
HyperNovaCycleFoldCircuit,
},
nova::traits::NovaR1CS,
},
frontend::tests::CubicFCircuit,
frontend::utils::CubicFCircuit,
transcript::poseidon::poseidon_canonical_config,
utils::get_cm_coordinates,
};
@ -1216,7 +1215,7 @@ mod tests {
let (cf_W_dummy, cf_U_dummy): (
CycleFoldWitness<Projective2>,
CycleFoldCommittedInstance<Projective2>,
) = cf_r1cs.dummy_instance();
) = cf_r1cs.dummy_running_instance();
// set the initial dummy instances
let mut W_i = W_dummy.clone();
@ -1455,9 +1454,7 @@ mod tests {
u_i.check_relation(&ccs, &w_i).unwrap();
// 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());
}

+ 496
- 0
folding-schemes/src/folding/hypernova/decider_eth.rs

@ -0,0 +1,496 @@
/// This file implements the HyperNova's onchain (Ethereum's EVM) decider.
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_snark::SNARK;
use ark_std::rand::{CryptoRng, RngCore};
use ark_std::{One, Zero};
use core::marker::PhantomData;
pub use super::decider_eth_circuit::DeciderEthCircuit;
use super::{lcccs::LCCCS, HyperNova};
use crate::commitment::{
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
};
use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
use crate::folding::nova::decider_eth::VerifierParam;
use crate::frontend::FCircuit;
use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme};
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Proof<C1, CS1, S>
where
C1: CurveGroup,
CS1: CommitmentScheme<C1, ProverChallenge = C1::ScalarField, Challenge = C1::ScalarField>,
S: SNARK<C1::ScalarField>,
{
snark_proof: S::Proof,
kzg_proof: CS1::Proof,
// rho used at the last fold, U_{i+1}=NIMFS.V(rho, U_i, u_i), it is checked in-circuit
rho: C1::ScalarField,
U_i1: LCCCS<C1>, // U_{i+1}, which is checked in-circuit
// the KZG challenge is provided by the prover, but in-circuit it is checked to match
// the in-circuit computed computed one.
kzg_challenge: C1::ScalarField,
}
/// Onchain Decider, for ethereum use cases
#[derive(Clone, Debug)]
pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, const MU: usize, const NU: usize> {
_c1: PhantomData<C1>,
_gc1: PhantomData<GC1>,
_c2: PhantomData<C2>,
_gc2: PhantomData<GC2>,
_fc: PhantomData<FC>,
_cs1: PhantomData<CS1>,
_cs2: PhantomData<CS2>,
_s: PhantomData<S>,
_fs: PhantomData<FS>,
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, const MU: usize, const NU: usize>
DeciderTrait<C1, C2, FC, FS> for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, MU, NU>
where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
// CS1 is a KZG commitment, where challenge is C1::Fr elem
CS1: CommitmentScheme<
C1,
ProverChallenge = C1::ScalarField,
Challenge = C1::ScalarField,
Proof = KZGProof<C1>,
>,
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
CS2: CommitmentScheme<C2, ProverParams = PedersenParams<C2>>,
S: SNARK<C1::ScalarField>,
FS: FoldingScheme<C1, C2, FC>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
// constrain FS into HyperNova, since this is a Decider specifically for HyperNova
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false>: From<FS>,
crate::folding::hypernova::ProverParams<C1, C2, CS1, CS2, false>:
From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
crate::folding::hypernova::VerifierParams<C1, C2, CS1, CS2, false>:
From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
{
type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
type ProverParam = (S::ProvingKey, CS1::ProverParams);
type Proof = Proof<C1, CS1, S>;
type VerifierParam = VerifierParam<C1, CS1::VerifierParams, S::VerifyingKey>;
type PublicInput = Vec<C1::ScalarField>;
type CommittedInstance = ();
fn preprocess(
mut rng: impl RngCore + CryptoRng,
prep_param: Self::PreprocessorParam,
fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
let circuit =
DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
fs.into(),
)
.unwrap();
// get the Groth16 specific setup for the circuit
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
// get the FoldingScheme prover & verifier params from HyperNova
#[allow(clippy::type_complexity)]
let hypernova_pp: <HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false> as FoldingScheme<
C1,
C2,
FC,
>>::ProverParam = prep_param.0.into();
#[allow(clippy::type_complexity)]
let hypernova_vp: <HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false> as FoldingScheme<
C1,
C2,
FC,
>>::VerifierParam = prep_param.1.into();
let pp_hash = hypernova_vp.pp_hash()?;
let pp = (g16_pk, hypernova_pp.cs_pp);
let vp = VerifierParam {
pp_hash,
snark_vp: g16_vk,
cs_vp: hypernova_vp.cs_vp,
};
Ok((pp, vp))
}
fn prove(
mut rng: impl RngCore + CryptoRng,
pp: Self::ProverParam,
folding_scheme: FS,
) -> Result<Self::Proof, Error> {
let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
folding_scheme.into(),
)?;
let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
.map_err(|e| Error::Other(e.to_string()))?;
// Notice that since the `circuit` has been constructed at the `from_hypernova` call, which
// in case of failure it would have returned an error there, the next two unwraps should
// never reach an error.
let rho_Fr = circuit.rho.ok_or(Error::Empty)?;
let W_i1 = circuit.W_i1.ok_or(Error::Empty)?;
// get the challenges that have been already computed when preparing the circuit inputs in
// the above `from_hypernova` call
let challenge_W = circuit
.kzg_challenge
.ok_or(Error::MissingValue("kzg_challenge".to_string()))?;
// generate KZG proofs
let U_cmW_proof = CS1::prove_with_challenge(
&cs_pk,
challenge_W,
&W_i1.w,
&C1::ScalarField::zero(),
None,
)?;
Ok(Self::Proof {
snark_proof,
kzg_proof: U_cmW_proof,
rho: rho_Fr,
U_i1: circuit.U_i1.ok_or(Error::Empty)?,
kzg_challenge: challenge_W,
})
}
fn verify(
vp: Self::VerifierParam,
i: C1::ScalarField,
z_0: Vec<C1::ScalarField>,
z_i: Vec<C1::ScalarField>,
// we don't use the instances at the verifier level, since we check them in-circuit
_running_instance: &Self::CommittedInstance,
_incoming_instance: &Self::CommittedInstance,
proof: &Self::Proof,
) -> Result<bool, Error> {
if i <= C1::ScalarField::one() {
return Err(Error::NotEnoughSteps);
}
let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
(vp.pp_hash, vp.snark_vp, vp.cs_vp);
// Note: the NIMFS proof is checked inside the DeciderEthCircuit, which ensures that the
// 'proof.U_i1' is correctly computed
let (cmC_x, cmC_y) = NonNativeAffineVar::inputize(proof.U_i1.C)?;
let public_input: Vec<C1::ScalarField> = [
vec![pp_hash, i],
z_0,
z_i,
// U_i+1:
cmC_x,
cmC_y,
vec![proof.U_i1.u],
proof.U_i1.x.clone(),
proof.U_i1.r_x.clone(),
proof.U_i1.v.clone(),
vec![proof.kzg_challenge, proof.kzg_proof.eval, proof.rho],
]
.concat();
let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
.map_err(|e| Error::Other(e.to_string()))?;
if !snark_v {
return Err(Error::SNARKVerificationFail);
}
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
CS1::verify_with_challenge(&cs_vk, proof.kzg_challenge, &proof.U_i1.C, &proof.kzg_proof)?;
Ok(true)
}
}
#[cfg(test)]
pub mod tests {
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
use ark_groth16::Groth16;
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
use ark_serialize::{Compress, Validate};
use super::*;
use crate::commitment::{kzg::KZG, pedersen::Pedersen};
use crate::folding::hypernova::cccs::CCCS;
use crate::folding::hypernova::{
PreprocessorParam, ProverParams, VerifierParams as HyperNovaVerifierParams,
};
use crate::folding::nova::decider_eth::VerifierParam;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
#[test]
fn test_decider() {
const MU: usize = 1;
const NU: usize = 1;
// use HyperNova as FoldingScheme
type HN = HyperNova<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
MU,
NU,
false,
>;
type D = Decider<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
Groth16<Bn254>, // here we define the Snark to use in the decider
HN, // here we define the FoldingScheme to use
MU,
NU,
>;
let mut rng = rand::rngs::OsRng;
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
hypernova
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
.unwrap();
hypernova
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
.unwrap(); // do a 2nd step
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) =
D::preprocess(&mut rng, hypernova_params, hypernova.clone()).unwrap();
// decider proof generation
let proof = D::prove(rng, decider_pp, hypernova.clone()).unwrap();
// decider proof verification
let verified = D::verify(
decider_vp,
hypernova.i,
hypernova.z_0,
hypernova.z_i,
&(),
&(),
&proof,
)
.unwrap();
assert!(verified);
}
#[test]
fn test_decider_serialization() {
const MU: usize = 1;
const NU: usize = 1;
// use HyperNova as FoldingScheme
type HN = HyperNova<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
MU,
NU,
false,
>;
type D = Decider<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
Groth16<Bn254>, // here we define the Snark to use in the decider
HN, // here we define the FoldingScheme to use
MU,
NU,
>;
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let prep_param = PreprocessorParam::new(poseidon_config.clone(), F_circuit);
let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
let hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
let mut rng = rand::rngs::OsRng;
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) =
D::preprocess(&mut rng, hypernova_params.clone(), hypernova.clone()).unwrap();
let mut hypernova_pp_serialized = vec![];
hypernova_params
.0
.clone()
.serialize_compressed(&mut hypernova_pp_serialized)
.unwrap();
let mut hypernova_vp_serialized = vec![];
hypernova_params
.1
.clone()
.serialize_compressed(&mut hypernova_vp_serialized)
.unwrap();
let hypernova_pp_deserialized = ProverParams::<
Projective,
Projective2,
KZG<'static, Bn254>,
Pedersen<Projective2>,
false,
>::deserialize_prover_params(
hypernova_pp_serialized.as_slice(),
Compress::Yes,
Validate::No,
&hypernova_params.0.ccs,
&poseidon_config,
)
.unwrap();
let hypernova_vp_deserialized = HyperNovaVerifierParams::<
Projective,
Projective2,
KZG<'static, Bn254>,
Pedersen<Projective2>,
false,
>::deserialize_verifier_params(
hypernova_vp_serialized.as_slice(),
Compress::Yes,
Validate::No,
&hypernova_params.0.ccs.unwrap(),
&poseidon_config,
)
.unwrap();
let hypernova_params = (hypernova_pp_deserialized, hypernova_vp_deserialized);
let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
hypernova
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
.unwrap();
hypernova
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
.unwrap();
// decider proof generation
let proof = D::prove(rng, decider_pp, hypernova.clone()).unwrap();
let verified = D::verify(
decider_vp.clone(),
hypernova.i.clone(),
hypernova.z_0.clone(),
hypernova.z_i.clone(),
&(),
&(),
&proof,
)
.unwrap();
assert!(verified);
// The rest of this test will serialize the data and deserialize it back, and use it to
// verify the proof:
// serialize the verifier_params, proof and public inputs
let mut decider_vp_serialized = vec![];
decider_vp
.serialize_compressed(&mut decider_vp_serialized)
.unwrap();
let mut proof_serialized = vec![];
proof.serialize_compressed(&mut proof_serialized).unwrap();
// serialize the public inputs in a single packet
let mut public_inputs_serialized = vec![];
hypernova
.i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
hypernova
.z_0
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
hypernova
.z_i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
hypernova
.U_i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
hypernova
.u_i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
// deserialize back the verifier_params, proof and public inputs
let decider_vp_deserialized =
VerifierParam::<
Projective,
<KZG<'static, Bn254> as CommitmentScheme<Projective>>::VerifierParams,
<Groth16<Bn254> as SNARK<Fr>>::VerifyingKey,
>::deserialize_compressed(&mut decider_vp_serialized.as_slice())
.unwrap();
let proof_deserialized =
Proof::<Projective, KZG<'static, Bn254>, Groth16<Bn254>>::deserialize_compressed(
&mut proof_serialized.as_slice(),
)
.unwrap();
let mut reader = public_inputs_serialized.as_slice();
let i_deserialized = Fr::deserialize_compressed(&mut reader).unwrap();
let z_0_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
let z_i_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
let _U_i = LCCCS::<Projective>::deserialize_compressed(&mut reader).unwrap();
let _u_i = CCCS::<Projective>::deserialize_compressed(&mut reader).unwrap();
let verified = D::verify(
decider_vp_deserialized,
i_deserialized.clone(),
z_0_deserialized.clone(),
z_i_deserialized.clone(),
&(),
&(),
&proof_deserialized,
)
.unwrap();
assert!(verified);
}
}

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

@ -231,7 +231,7 @@ where
cf_E_len: hn.cf_W_i.E.len(),
ccs: hn.ccs,
cf_r1cs: hn.cf_r1cs,
cf_pedersen_params: hn.cf_cs_params,
cf_pedersen_params: hn.cf_cs_pp,
poseidon_config: hn.poseidon_config,
pp_hash: Some(hn.pp_hash),
i: Some(hn.i),
@ -517,7 +517,7 @@ pub mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::FoldingScheme;

+ 3
- 1
folding-schemes/src/folding/hypernova/lcccs.rs

@ -3,6 +3,8 @@ use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_poly::DenseMultilinearExtension;
use ark_poly::MultilinearExtension;
use ark_serialize::CanonicalDeserialize;
use ark_serialize::CanonicalSerialize;
use ark_std::rand::Rng;
use ark_std::Zero;
@ -15,7 +17,7 @@ use crate::utils::vec::mat_vec_mul;
use crate::Error;
/// Linearized Committed CCS instance
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct LCCCS<C: CurveGroup> {
// Commitment to witness
pub C: C,

+ 64
- 28
folding-schemes/src/folding/hypernova/mod.rs

@ -6,13 +6,16 @@ use ark_crypto_primitives::sponge::{
use ark_ec::{CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero};
pub mod cccs;
pub mod circuits;
pub mod decider_eth;
pub mod decider_eth_circuit;
pub mod lcccs;
pub mod nimfs;
pub mod serialize;
pub mod utils;
use cccs::CCCS;
@ -20,7 +23,6 @@ use circuits::AugmentedFCircuit;
use lcccs::LCCCS;
use nimfs::NIMFS;
use crate::commitment::CommitmentScheme;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::circuits::{
cyclefold::{
@ -29,10 +31,11 @@ use crate::folding::circuits::{
},
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::utils::{get_cm_coordinates, pp_hash};
use crate::Error;
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
use crate::{
arith::{
ccs::CCS,
@ -41,7 +44,8 @@ use crate::{
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>,
}
@ -54,11 +58,13 @@ impl CycleFoldConfig
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>;
/// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Witness<F: PrimeField> {
pub w: Vec<F>,
pub r_w: F,
@ -75,6 +81,7 @@ impl Witness {
}
}
/// Proving parameters for HyperNova-based IVC
#[derive(Debug, Clone)]
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool>
where
@ -83,13 +90,18 @@ where
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
pub cs_params: CS1::ProverParams,
pub cf_cs_params: CS2::ProverParams,
// if ccs is set, it will be used, if not, it will be computed at runtime
/// Proving parameters of the underlying commitment scheme over C1
pub cs_pp: CS1::ProverParams,
/// Proving parameters of the underlying commitment scheme over C2
pub cf_cs_pp: CS2::ProverParams,
/// 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>>,
}
/// Verification parameters for HyperNova-based IVC
#[derive(Debug, Clone)]
pub struct VerifierParams<
C1: CurveGroup,
@ -98,10 +110,15 @@ pub struct VerifierParams<
CS2: CommitmentScheme<C2, H>,
const H: bool,
> {
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// CCS of the Augmented step circuit
pub ccs: CCS<C1::ScalarField>,
/// R1CS of the CycleFold circuit
pub cf_r1cs: R1CS<C2::ScalarField>,
/// Verification parameters of the underlying commitment scheme over C1
pub cs_vp: CS1::VerifierParams,
/// Verification parameters of the underlying commitment scheme over C2
pub cf_cs_vp: CS2::VerifierParams,
}
@ -162,9 +179,9 @@ pub struct HyperNova<
pub cf_r1cs: R1CS<C2::ScalarField>,
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// CommitmentScheme::ProverParams over C1
pub cs_params: CS1::ProverParams,
pub cs_pp: CS1::ProverParams,
/// CycleFold CommitmentScheme::ProverParams, over C2
pub cf_cs_params: CS2::ProverParams,
pub cf_cs_pp: CS2::ProverParams,
/// F circuit, the circuit that is being folded
pub F: FC,
/// public params hash
@ -221,7 +238,7 @@ where
// assign them directly to w_i, u_i.
let (U_i, W_i) = self
.ccs
.to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_params, &r1cs_z)?;
.to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
#[cfg(test)]
U_i.check_relation(&self.ccs, &W_i)?;
@ -243,7 +260,7 @@ where
// assign them directly to w_i, u_i.
let (u_i, w_i) = self
.ccs
.to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_params, &r1cs_z)?;
.to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
#[cfg(test)]
u_i.check_relation(&self.ccs, &w_i)?;
@ -281,7 +298,7 @@ where
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 (_, 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);
@ -426,8 +443,8 @@ where
let pp = ProverParams::<C1, C2, CS1, CS2, H> {
poseidon_config: prep_param.poseidon_config.clone(),
cs_params: cs_pp.clone(),
cf_cs_params: cf_cs_pp.clone(),
cs_pp,
cf_cs_pp,
ccs: Some(ccs.clone()),
};
let vp = VerifierParams::<C1, C2, CS1, CS2, H> {
@ -475,7 +492,7 @@ where
let w_dummy = W_dummy.clone();
let mut u_dummy = CCCS::<C1>::dummy(ccs.l);
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.hash(
&sponge,
@ -496,8 +513,8 @@ where
ccs,
cf_r1cs,
poseidon_config: pp.poseidon_config.clone(),
cs_params: pp.cs_params.clone(),
cf_cs_params: pp.cf_cs_params.clone(),
cs_pp: pp.cs_pp.clone(),
cf_cs_pp: pp.cf_cs_pp.clone(),
F,
pp_hash,
i: C1::ScalarField::zero(),
@ -736,7 +753,7 @@ where
>(
&mut transcript_p,
self.cf_r1cs.clone(),
self.cf_cs_params.clone(),
self.cf_cs_pp.clone(),
self.pp_hash,
self.cf_W_i.clone(), // CycleFold running instance witness
self.cf_U_i.clone(), // CycleFold running instance
@ -796,7 +813,7 @@ where
// assign them directly to w_i, u_i.
let (u_i, w_i) = self
.ccs
.to_cccs::<_, C1, CS1, H>(&mut rng, &self.cs_params, &r1cs_z)?;
.to_cccs::<_, C1, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
self.u_i = u_i.clone();
self.w_i = w_i.clone();
@ -883,8 +900,7 @@ where
u_i.check_relation(&vp.ccs, &w_i)?;
// 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(())
}
@ -899,7 +915,7 @@ mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
#[test]
@ -925,13 +941,25 @@ mod tests {
}
// test_ivc allowing to choose the CommitmentSchemes
fn test_ivc_opt<
pub fn test_ivc_opt<
CS1: CommitmentScheme<Projective, H>,
CS2: CommitmentScheme<Projective2, H>,
const H: bool,
>(
poseidon_config: PoseidonConfig<Fr>,
F_circuit: CubicFCircuit<Fr>,
) -> (
HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, 2, 3, H>,
(
ProverParams<Projective, Projective2, CS1, CS2, H>,
VerifierParams<Projective, Projective2, CS1, CS2, H>,
),
(LCCCS<Projective>, Witness<Fr>),
(CCCS<Projective>, Witness<Fr>),
(
CycleFoldCommittedInstance<Projective2>,
CycleFoldWitness<Projective2>,
),
) {
let mut rng = ark_std::test_rng();
@ -987,14 +1015,22 @@ mod tests {
let (running_instance, incoming_instance, cyclefold_instance) = hypernova.instances();
HN::verify(
hypernova_params.1, // verifier_params
hypernova_params.1.clone(), // verifier_params
z_0,
hypernova.z_i,
hypernova.i,
hypernova.z_i.clone(),
hypernova.i.clone(),
running_instance.clone(),
incoming_instance.clone(),
cyclefold_instance.clone(),
)
.unwrap();
(
hypernova,
hypernova_params,
running_instance,
incoming_instance,
cyclefold_instance,
)
.unwrap();
}
}

+ 2
- 2
folding-schemes/src/folding/hypernova/nimfs.rs

@ -23,7 +23,7 @@ use std::fmt::Debug;
use std::marker::PhantomData;
/// NIMFSProof defines a multifolding proof
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NIMFSProof<C: CurveGroup> {
pub sc_proof: SumCheckProof<C::ScalarField>,
pub sigmas_thetas: SigmasThetas<C::ScalarField>,
@ -51,7 +51,7 @@ impl NIMFSProof {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SigmasThetas<F: PrimeField>(pub Vec<Vec<F>>, pub Vec<Vec<F>>);
#[derive(Debug)]

+ 420
- 0
folding-schemes/src/folding/hypernova/serialize.rs

@ -0,0 +1,420 @@
use crate::arith::ccs::CCS;
use crate::arith::r1cs::R1CS;
use crate::folding::hypernova::ProverParams;
use crate::folding::hypernova::VerifierParams;
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_r1cs_std::groups::{CurveVar, GroupOpsBounds};
use ark_r1cs_std::ToConstraintFieldGadget;
use ark_serialize::CanonicalDeserialize;
use ark_serialize::{CanonicalSerialize, Compress, SerializationError, Validate};
use ark_std::marker::PhantomData;
use crate::folding::hypernova::cccs::CCCS;
use crate::folding::hypernova::lcccs::LCCCS;
use crate::folding::hypernova::Witness;
use crate::folding::nova::{
CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness,
};
use crate::FoldingScheme;
use crate::{
commitment::CommitmentScheme,
folding::{circuits::CF2, nova::PreprocessorParam},
frontend::FCircuit,
};
use super::HyperNova;
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
CanonicalSerialize for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn serialize_compressed<W: std::io::prelude::Write>(
&self,
writer: W,
) -> Result<(), ark_serialize::SerializationError> {
self.serialize_with_mode(writer, ark_serialize::Compress::Yes)
}
fn compressed_size(&self) -> usize {
self.serialized_size(ark_serialize::Compress::Yes)
}
fn serialize_uncompressed<W: std::io::prelude::Write>(
&self,
writer: W,
) -> Result<(), ark_serialize::SerializationError> {
self.serialize_with_mode(writer, ark_serialize::Compress::No)
}
fn uncompressed_size(&self) -> usize {
self.serialized_size(ark_serialize::Compress::No)
}
fn serialize_with_mode<W: std::io::prelude::Write>(
&self,
mut writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.pp_hash.serialize_with_mode(&mut writer, compress)?;
self.i.serialize_with_mode(&mut writer, compress)?;
self.z_0.serialize_with_mode(&mut writer, compress)?;
self.z_i.serialize_with_mode(&mut writer, compress)?;
self.W_i.serialize_with_mode(&mut writer, compress)?;
self.U_i.serialize_with_mode(&mut writer, compress)?;
self.w_i.serialize_with_mode(&mut writer, compress)?;
self.u_i.serialize_with_mode(&mut writer, compress)?;
self.cf_W_i.serialize_with_mode(&mut writer, compress)?;
self.cf_U_i.serialize_with_mode(&mut writer, compress)
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.pp_hash.serialized_size(compress)
+ self.i.serialized_size(compress)
+ self.z_0.serialized_size(compress)
+ self.z_i.serialized_size(compress)
+ self.W_i.serialized_size(compress)
+ self.U_i.serialized_size(compress)
+ self.w_i.serialized_size(compress)
+ self.u_i.serialized_size(compress)
+ self.cf_W_i.serialized_size(compress)
+ self.cf_U_i.serialized_size(compress)
}
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField, Params = ()>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
#[allow(clippy::too_many_arguments)]
pub fn deserialize_hypernova<R: std::io::prelude::Read>(
mut reader: R,
compress: Compress,
validate: Validate,
poseidon_config: PoseidonConfig<C1::ScalarField>,
cs_pp: CS1::ProverParams,
cs_vp: CS1::VerifierParams,
cf_cs_pp: CS2::ProverParams,
cf_cs_vp: CS2::VerifierParams,
) -> Result<Self, SerializationError> {
let f_circuit = FC::new(()).unwrap();
let prep_param = PreprocessorParam {
poseidon_config: poseidon_config.clone(),
F: f_circuit.clone(),
cs_pp: Some(cs_pp.clone()),
cs_vp: Some(cs_vp.clone()),
cf_cs_pp: Some(cf_cs_pp.clone()),
cf_cs_vp: Some(cf_cs_vp.clone()),
};
// `test_rng` won't be used in `preprocess`, since parameters have already been initialized
let (prover_params, verifier_params) = Self::preprocess(ark_std::test_rng(), &prep_param)
.or(Err(SerializationError::InvalidData))?;
let pp_hash = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?;
let i = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?;
let z_0 = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let z_i = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let W_i =
Witness::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let U_i = LCCCS::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let w_i =
Witness::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let u_i = CCCS::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_W_i =
CycleFoldWitness::<C2>::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_U_i = CycleFoldCommittedInstance::<C2>::deserialize_with_mode(
&mut reader,
compress,
validate,
)?;
let ccs = prover_params.ccs.ok_or(SerializationError::InvalidData)?;
Ok(HyperNova {
_gc1: PhantomData,
_c2: PhantomData,
_gc2: PhantomData,
ccs,
cf_r1cs: verifier_params.cf_r1cs,
poseidon_config,
cs_pp,
cf_cs_pp,
F: f_circuit,
pp_hash,
i,
z_0,
z_i,
W_i,
U_i,
w_i,
u_i,
cf_W_i,
cf_U_i,
})
}
}
impl<
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2, H>
{
fn serialize_compressed<W: std::io::prelude::Write>(
&self,
writer: W,
) -> Result<(), SerializationError> {
self.serialize_with_mode(writer, Compress::Yes)
}
fn compressed_size(&self) -> usize {
self.serialized_size(Compress::Yes)
}
fn serialize_uncompressed<W: std::io::prelude::Write>(
&self,
writer: W,
) -> Result<(), SerializationError> {
self.serialize_with_mode(writer, Compress::No)
}
fn uncompressed_size(&self) -> usize {
self.serialized_size(Compress::No)
}
fn serialize_with_mode<W: std::io::prelude::Write>(
&self,
mut writer: W,
compress: Compress,
) -> Result<(), SerializationError> {
self.cs_pp.serialize_with_mode(&mut writer, compress)?;
self.cf_cs_pp.serialize_with_mode(&mut writer, compress)
}
fn serialized_size(&self, compress: Compress) -> usize {
self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress)
}
}
impl<
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> ProverParams<C1, C2, CS1, CS2, H>
{
pub fn deserialize_prover_params<R: std::io::prelude::Read>(
mut reader: R,
compress: Compress,
validate: Validate,
ccs: &Option<CCS<C1::ScalarField>>,
poseidon_config: &PoseidonConfig<C1::ScalarField>,
) -> Result<Self, SerializationError> {
let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
Ok(ProverParams {
cs_pp,
cf_cs_pp,
ccs: ccs.clone(),
poseidon_config: poseidon_config.clone(),
})
}
}
impl<
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2, H>
{
fn serialize_compressed<W: std::io::prelude::Write>(
&self,
writer: W,
) -> Result<(), SerializationError> {
self.serialize_with_mode(writer, Compress::Yes)
}
fn compressed_size(&self) -> usize {
self.serialized_size(Compress::Yes)
}
fn serialize_uncompressed<W: std::io::prelude::Write>(
&self,
writer: W,
) -> Result<(), SerializationError> {
self.serialize_with_mode(writer, Compress::No)
}
fn uncompressed_size(&self) -> usize {
self.serialized_size(Compress::No)
}
fn serialize_with_mode<W: std::io::prelude::Write>(
&self,
mut writer: W,
compress: Compress,
) -> Result<(), SerializationError> {
self.cf_r1cs.serialize_with_mode(&mut writer, compress)?;
self.cs_vp.serialize_with_mode(&mut writer, compress)?;
self.cf_cs_vp.serialize_with_mode(&mut writer, compress)
}
fn serialized_size(&self, compress: Compress) -> usize {
self.cf_r1cs.serialized_size(compress)
+ self.cs_vp.serialized_size(compress)
+ self.cf_cs_vp.serialized_size(compress)
}
}
impl<
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> VerifierParams<C1, C2, CS1, CS2, H>
{
pub fn deserialize_verifier_params<R: std::io::Read>(
mut reader: R,
compress: Compress,
validate: Validate,
ccs: &CCS<C1::ScalarField>,
poseidon_config: &PoseidonConfig<C1::ScalarField>,
) -> Result<Self, SerializationError> {
let cf_r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?;
let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
Ok(VerifierParams {
ccs: ccs.clone(),
poseidon_config: poseidon_config.clone(),
cf_r1cs,
cs_vp,
cf_cs_vp,
})
}
}
#[cfg(test)]
pub mod tests {
use crate::FoldingScheme;
use crate::MultiFolding;
use ark_serialize::{Compress, Validate, Write};
use std::fs;
use crate::{
commitment::{kzg::KZG, pedersen::Pedersen},
folding::hypernova::{tests::test_ivc_opt, HyperNova},
frontend::{utils::CubicFCircuit, FCircuit},
transcript::poseidon::poseidon_canonical_config,
};
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
use ark_serialize::CanonicalSerialize;
#[test]
fn test_serde_hypernova() {
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let (mut hn, (_, verifier_params), _, _, _) = test_ivc_opt::<
KZG<Bn254>,
Pedersen<Projective2>,
false,
>(poseidon_config.clone(), F_circuit);
let mut writer = vec![];
assert!(hn.serialize_compressed(&mut writer).is_ok());
let mut writer = vec![];
assert!(hn.serialize_uncompressed(&mut writer).is_ok());
let mut file = fs::OpenOptions::new()
.create(true)
.write(true)
.open("./hypernova.serde")
.unwrap();
file.write_all(&writer).unwrap();
let bytes = fs::read("./hypernova.serde").unwrap();
let mut hn_deserialized = HyperNova::<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<Bn254>,
Pedersen<Projective2>,
2,
3,
false,
>::deserialize_hypernova(
bytes.as_slice(),
Compress::No,
Validate::No,
poseidon_config,
hn.cs_pp.clone(),
verifier_params.cs_vp,
hn.cf_cs_pp.clone(),
verifier_params.cf_cs_vp,
)
.unwrap();
assert_eq!(hn.i, hn_deserialized.i);
let mut rng = ark_std::test_rng();
for _ in 0..3 {
// prepare some new instances to fold in the multifolding step
let mut lcccs = vec![];
for j in 0..1 {
let instance_state = vec![Fr::from(j as u32 + 85_u32)];
let (U, W) = hn
.new_running_instance(&mut rng, instance_state, vec![])
.unwrap();
lcccs.push((U, W));
}
let mut cccs = vec![];
for j in 0..2 {
let instance_state = vec![Fr::from(j as u32 + 15_u32)];
let (u, w) = hn
.new_incoming_instance(&mut rng, instance_state, vec![])
.unwrap();
cccs.push((u, w));
}
hn.prove_step(&mut rng, vec![], Some((lcccs.clone(), cccs.clone())))
.unwrap();
hn_deserialized
.prove_step(&mut rng, vec![], Some((lcccs, cccs)))
.unwrap();
}
assert_eq!(hn.z_i, hn_deserialized.z_i);
}
}

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

@ -237,31 +237,31 @@ pub struct AugmentedFCircuit<
> where
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
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
// 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>>>

+ 220
- 21
folding-schemes/src/folding/nova/decider_eth.rs

@ -1,16 +1,17 @@
/// This file implements the onchain (Ethereum's EVM) decider.
/// This file implements the Nova's onchain (Ethereum's EVM) decider.
use ark_bn254::Bn254;
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_groth16::Groth16;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_snark::SNARK;
use ark_std::rand::{CryptoRng, RngCore};
use ark_std::{One, Zero};
use core::marker::PhantomData;
pub use super::decider_eth_circuit::{DeciderEthCircuit, KZGChallengesGadget};
pub use super::decider_eth_circuit::DeciderEthCircuit;
use super::{nifs::NIFS, CommittedInstance, Nova};
use crate::commitment::{
kzg::{Proof as KZGProof, KZG},
@ -22,7 +23,7 @@ use crate::frontend::FCircuit;
use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme};
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Proof<C1, CS1, S>
where
C1: CurveGroup,
@ -40,6 +41,18 @@ where
kzg_challenges: [C1::ScalarField; 2],
}
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct VerifierParam<C1, CS_VerifyingKey, S_VerifyingKey>
where
C1: CurveGroup,
CS_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
S_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
{
pub pp_hash: C1::ScalarField,
pub snark_vp: S_VerifyingKey,
pub cs_vp: CS_VerifyingKey,
}
/// Onchain Decider, for ethereum use cases
#[derive(Clone, Debug)]
pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS> {
@ -90,14 +103,13 @@ where
type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
type ProverParam = (S::ProvingKey, CS1::ProverParams);
type Proof = Proof<C1, CS1, S>;
/// VerifierParam = (pp_hash, snark::vk, commitment_scheme::vk)
type VerifierParam = (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams);
type VerifierParam = VerifierParam<C1, CS1::VerifierParams, S::VerifyingKey>;
type PublicInput = Vec<C1::ScalarField>;
type CommittedInstance = CommittedInstance<C1>;
fn preprocess(
mut rng: impl RngCore + CryptoRng,
prep_param: &Self::PreprocessorParam,
prep_param: Self::PreprocessorParam,
fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
let circuit =
@ -122,7 +134,11 @@ where
let pp_hash = nova_vp.pp_hash()?;
let pp = (g16_pk, nova_pp.cs_pp);
let vp = (pp_hash, g16_vk, nova_vp.cs_vp);
let vp = Self::VerifierParam {
pp_hash,
snark_vp: g16_vk,
cs_vp: nova_vp.cs_vp,
};
Ok((pp, vp))
}
@ -191,9 +207,6 @@ where
return Err(Error::NotEnoughSteps);
}
let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
vp;
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
@ -202,7 +215,7 @@ where
let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
let public_input: Vec<C1::ScalarField> = [
vec![pp_hash, i],
vec![vp.pp_hash, i],
z_0,
z_i,
vec![U.u],
@ -222,7 +235,7 @@ where
]
.concat();
let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
let snark_v = S::verify(&vp.snark_vp, &public_input, &proof.snark_proof)
.map_err(|e| Error::Other(e.to_string()))?;
if !snark_v {
return Err(Error::SNARKVerificationFail);
@ -230,13 +243,13 @@ where
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
CS1::verify_with_challenge(
&cs_vk,
&vp.cs_vp,
proof.kzg_challenges[0],
&U.cmW,
&proof.kzg_proofs[0],
)?;
CS1::verify_with_challenge(
&cs_vk,
&vp.cs_vp,
proof.kzg_challenges[1],
&U.cmE,
&proof.kzg_proofs[1],
@ -326,8 +339,10 @@ pub mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::CubicFCircuit;
use crate::folding::nova::{
PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams,
};
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
#[test]
@ -355,27 +370,140 @@ pub mod tests {
N, // here we define the FoldingScheme to use
>;
let mut rng = ark_std::test_rng();
let mut rng = rand::rngs::OsRng;
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
let preprocessor_param = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &preprocessor_param).unwrap();
let start = Instant::now();
let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
println!("Nova initialized, {:?}", start.elapsed());
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
let start = Instant::now();
nova.prove_step(&mut rng, vec![], None).unwrap();
println!("prove_step, {:?}", start.elapsed());
nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
// decider proof generation
let start = Instant::now();
let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
println!("Decider prove, {:?}", start.elapsed());
// decider proof verification
let start = Instant::now();
let verified = D::verify(
decider_vp.clone(),
nova.i.clone(),
nova.z_0.clone(),
nova.z_i.clone(),
&nova.U_i,
&nova.u_i,
&proof,
)
.unwrap();
assert!(verified);
println!("Decider verify, {:?}", start.elapsed());
// decider proof verification using the deserialized data
let verified = D::verify(
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
)
.unwrap();
assert!(verified);
}
// Test to check the serialization and deserialization of diverse Decider related parameters.
// This test is the same test as `test_decider` but it serializes values and then uses the
// deserialized values to continue the checks.
#[test]
fn test_decider_serialization() {
// use Nova as FoldingScheme
type N = Nova<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
false,
>;
type D = Decider<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
Groth16<Bn254>, // here we define the Snark to use in the decider
N, // here we define the FoldingScheme to use
>;
let mut rng = rand::rngs::OsRng;
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let preprocessor_param = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &preprocessor_param).unwrap();
let start = Instant::now();
let nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
println!("Nova initialized, {:?}", start.elapsed());
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
let (decider_pp, decider_vp) =
D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
// serialize the Nova params. These params are the trusted setup of the commitment schemes used
// (ie. KZG & Pedersen in this case)
let mut nova_pp_serialized = vec![];
nova_params
.0
.serialize_compressed(&mut nova_pp_serialized)
.unwrap();
let mut nova_vp_serialized = vec![];
nova_params
.1
.serialize_compressed(&mut nova_vp_serialized)
.unwrap();
// deserialize the Nova params. This would be done by the client reading from a file
let nova_pp_deserialized = NovaProverParams::<
Projective,
Projective2,
KZG<'static, Bn254>,
Pedersen<Projective2>,
>::deserialize_compressed(
&mut nova_pp_serialized.as_slice()
)
.unwrap();
let nova_vp_deserialized = NovaVerifierParams::<
Projective,
Projective2,
KZG<'static, Bn254>,
Pedersen<Projective2>,
>::deserialize_compressed(
&mut nova_vp_serialized.as_slice()
)
.unwrap();
// initialize nova again, but from the deserialized parameters
let nova_params = (nova_pp_deserialized, nova_vp_deserialized);
let mut nova = N::init(&nova_params, F_circuit, z_0).unwrap();
let start = Instant::now();
nova.prove_step(&mut rng, vec![], None).unwrap();
println!("prove_step, {:?}", start.elapsed());
nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
// decider proof generation
let start = Instant::now();
@ -385,10 +513,81 @@ pub mod tests {
// decider proof verification
let start = Instant::now();
let verified = D::verify(
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
decider_vp.clone(),
nova.i.clone(),
nova.z_0.clone(),
nova.z_i.clone(),
&nova.U_i,
&nova.u_i,
&proof,
)
.unwrap();
assert!(verified);
println!("Decider verify, {:?}", start.elapsed());
// The rest of this test will serialize the data and deserialize it back, and use it to
// verify the proof:
// serialize the verifier_params, proof and public inputs
let mut decider_vp_serialized = vec![];
decider_vp
.serialize_compressed(&mut decider_vp_serialized)
.unwrap();
let mut proof_serialized = vec![];
proof.serialize_compressed(&mut proof_serialized).unwrap();
// serialize the public inputs in a single packet
let mut public_inputs_serialized = vec![];
nova.i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
nova.z_0
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
nova.z_i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
nova.U_i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
nova.u_i
.serialize_compressed(&mut public_inputs_serialized)
.unwrap();
// deserialize back the verifier_params, proof and public inputs
let decider_vp_deserialized =
VerifierParam::<
Projective,
<KZG<'static, Bn254> as CommitmentScheme<Projective>>::VerifierParams,
<Groth16<Bn254> as SNARK<Fr>>::VerifyingKey,
>::deserialize_compressed(&mut decider_vp_serialized.as_slice())
.unwrap();
let proof_deserialized =
Proof::<Projective, KZG<'static, Bn254>, Groth16<Bn254>>::deserialize_compressed(
&mut proof_serialized.as_slice(),
)
.unwrap();
// deserialize the public inputs from the single packet 'public_inputs_serialized'
let mut reader = public_inputs_serialized.as_slice();
let i_deserialized = Fr::deserialize_compressed(&mut reader).unwrap();
let z_0_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
let z_i_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
let U_i_deserialized =
CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
let u_i_deserialized =
CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
// decider proof verification using the deserialized data
let verified = D::verify(
decider_vp_deserialized,
i_deserialized,
z_0_deserialized,
z_i_deserialized,
&U_i_deserialized,
&u_i_deserialized,
&proof_deserialized,
)
.unwrap();
assert!(verified);
}
}

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

@ -577,6 +577,8 @@ where
#[cfg(test)]
pub mod tests {
use std::cmp::max;
use ark_crypto_primitives::crh::{
sha256::{
constraints::{Sha256Gadget, UnitVar},
@ -587,34 +589,61 @@ pub mod tests {
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::bits::uint8::UInt8;
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 super::*;
use crate::arith::{
r1cs::{
extract_r1cs, extract_w_x,
tests::{get_test_r1cs, get_test_z},
{extract_r1cs, extract_w_x},
RelaxedR1CS,
},
Arith,
};
use crate::commitment::pedersen::Pedersen;
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::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]
fn test_relaxed_r1cs_small_gadget_handcrafted() {
let rng = &mut thread_rng();
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 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();
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
// initialized.
fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) {
let rng = &mut thread_rng();
let cs = ConstraintSystem::<Fr>::new_ref();
circuit.generate_constraints(cs.clone()).unwrap();
@ -634,18 +665,19 @@ pub mod tests {
let r1cs = extract_r1cs::<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();
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
let cs = ConstraintSystem::<Fr>::new_ref();
// prepare the inputs for our circuit
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();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@ -709,6 +741,8 @@ pub mod tests {
#[test]
fn test_relaxed_r1cs_nonnative_circuit() {
let rng = &mut thread_rng();
let cs = ConstraintSystem::<Fq>::new_ref();
// 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
@ -725,16 +759,15 @@ pub mod tests {
let cs = cs.into_inner().unwrap();
let r1cs = extract_r1cs::<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
let cs = ConstraintSystem::<Fq>::new_ref();
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 =
R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@ -742,8 +775,8 @@ pub mod tests {
// non-natively
let cs = ConstraintSystem::<Fr>::new_ref();
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 =
R1CSVar::<Fq, Fr, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_nonnative(r1csVar, EVar, uVar, zVar).unwrap();

+ 175
- 37
folding-schemes/src/folding/nova/mod.rs

@ -8,23 +8,23 @@ use ark_ec::{CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid};
use ark_std::fmt::Debug;
use ark_std::rand::RngCore;
use ark_std::{One, UniformRand, Zero};
use core::marker::PhantomData;
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
};
use crate::folding::circuits::CF2;
use crate::frontend::FCircuit;
use crate::transcript::{AbsorbNonNative, Transcript};
use crate::transcript::{poseidon::poseidon_canonical_config, AbsorbNonNative, Transcript};
use crate::utils::vec::is_zero_vec;
use crate::Error;
use crate::FoldingScheme;
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
use crate::{
arith::r1cs::{extract_r1cs, extract_w_x, R1CS},
constants::NOVA_N_BITS_RO,
@ -40,8 +40,8 @@ pub mod traits;
pub mod zk;
use circuits::{AugmentedFCircuit, ChallengeGadget};
use nifs::NIFS;
use traits::NovaR1CS;
/// Configuration for Nova's CycleFold circuit
pub struct NovaCycleFoldConfig<C: CurveGroup> {
_c: PhantomData<C>,
}
@ -56,7 +56,9 @@ impl CycleFoldConfig for NovaCycleFoldConfig {
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)]
pub struct CommittedInstance<C: CurveGroup> {
@ -136,10 +138,7 @@ pub struct Witness {
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 {
let (rW, rE) = if H {
(
@ -190,7 +189,7 @@ where
}
#[derive(Debug, Clone)]
pub struct PreprocessorParam<C1, C2, FC, CS1, CS2, const H: bool>
pub struct PreprocessorParam<C1, C2, FC, CS1, CS2, const H: bool = false>
where
C1: CurveGroup,
C2: CurveGroup,
@ -227,34 +226,179 @@ where
}
}
/// Proving parameters for Nova-based IVC
#[derive(Debug, Clone)]
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool>
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool = false>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// Proving parameters of the underlying commitment scheme over C1
pub cs_pp: CS1::ProverParams,
/// Proving parameters of the underlying commitment scheme over C2
pub cf_cs_pp: CS2::ProverParams,
}
impl<C1, C2, CS1, CS2, const H: bool> Valid for ProverParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.poseidon_config.full_rounds.check()?;
self.poseidon_config.partial_rounds.check()?;
self.poseidon_config.alpha.check()?;
self.poseidon_config.ark.check()?;
self.poseidon_config.mds.check()?;
self.poseidon_config.rate.check()?;
self.poseidon_config.capacity.check()?;
self.cs_pp.check()?;
self.cf_cs_pp.check()?;
Ok(())
}
}
impl<C1, C2, CS1, CS2, const H: bool> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn serialize_with_mode<W: std::io::prelude::Write>(
&self,
mut writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.cs_pp.serialize_with_mode(&mut writer, compress)?;
self.cf_cs_pp.serialize_with_mode(&mut writer, compress)
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress)
}
}
impl<C1, C2, CS1, CS2, const H: bool> CanonicalDeserialize for ProverParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
Ok(ProverParams {
poseidon_config: poseidon_canonical_config::<C1::ScalarField>(),
cs_pp,
cf_cs_pp,
})
}
}
/// Verification parameters for Nova-based IVC
#[derive(Debug, Clone)]
pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool>
pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool = false>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// R1CS of the Augmented step circuit
pub r1cs: R1CS<C1::ScalarField>,
/// R1CS of the CycleFold circuit
pub cf_r1cs: R1CS<C2::ScalarField>,
/// Verification parameters of the underlying commitment scheme over C1
pub cs_vp: CS1::VerifierParams,
/// Verification parameters of the underlying commitment scheme over C2
pub cf_cs_vp: CS2::VerifierParams,
}
impl<C1, C2, CS1, CS2, const H: bool> Valid for VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.poseidon_config.full_rounds.check()?;
self.poseidon_config.partial_rounds.check()?;
self.poseidon_config.alpha.check()?;
self.poseidon_config.ark.check()?;
self.poseidon_config.mds.check()?;
self.poseidon_config.rate.check()?;
self.poseidon_config.capacity.check()?;
self.r1cs.check()?;
self.cf_r1cs.check()?;
self.cs_vp.check()?;
self.cf_cs_vp.check()?;
Ok(())
}
}
impl<C1, C2, CS1, CS2, const H: bool> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn serialize_with_mode<W: std::io::prelude::Write>(
&self,
mut writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.r1cs.serialize_with_mode(&mut writer, compress)?;
self.cf_r1cs.serialize_with_mode(&mut writer, compress)?;
self.cs_vp.serialize_with_mode(&mut writer, compress)?;
self.cf_cs_vp.serialize_with_mode(&mut writer, compress)
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.r1cs.serialized_size(compress)
+ self.cf_r1cs.serialized_size(compress)
+ self.cs_vp.serialized_size(compress)
+ self.cf_cs_vp.serialized_size(compress)
}
}
impl<C1, C2, CS1, CS2, const H: bool> CanonicalDeserialize for VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
let r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?;
let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
Ok(VerifierParams {
poseidon_config: poseidon_canonical_config::<C1::ScalarField>(),
r1cs,
cf_r1cs,
cs_vp,
cf_cs_vp,
})
}
}
impl<C1, C2, CS1, CS2, const H: bool> VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
@ -418,8 +562,9 @@ where
let pp_hash = vp.pp_hash()?;
// 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
// R1CS that we're working with.
@ -437,13 +582,13 @@ where
i: C1::ScalarField::zero(),
z_0: z_0.clone(),
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
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,
})
}
@ -670,10 +815,10 @@ where
#[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
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
.check_relaxed_relation(&self.cf_W_i, &self.cf_U_i)?;
}
}
@ -705,9 +850,8 @@ where
#[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(())
@ -773,19 +917,13 @@ where
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
vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?;
vp.r1cs.check_relaxed_relation(&W_i, &U_i)?;
// 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(())
}
@ -942,7 +1080,7 @@ pub mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
/// 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_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::folding::nova::circuits::ChallengeGadget;
use crate::folding::nova::traits::NovaR1CS;
use crate::transcript::poseidon::poseidon_canonical_config;
#[allow(clippy::type_complexity)]
@ -316,8 +318,8 @@ pub mod tests {
let u_i = u_dummy.clone();
let W_i = w_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);
@ -334,7 +336,7 @@ pub mod tests {
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
)
.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
@ -348,9 +350,9 @@ pub mod tests {
assert_eq!(ci3_v, ci3);
// 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
// use folded rE, rW to commit w3
@ -425,7 +427,7 @@ pub mod tests {
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap();
r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance)
r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance)
.unwrap();
let num_iters = 10;
@ -438,11 +440,8 @@ pub mod tests {
let incoming_committed_instance = incoming_instance_w
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.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
@ -475,7 +474,7 @@ pub mod tests {
&cmT,
);
r1cs.check_relaxed_instance_relation(&folded_w, &folded_committed_instance)
r1cs.check_relaxed_relation(&folded_w, &folded_committed_instance)
.unwrap();
// 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::{
commitment::{kzg::KZG, pedersen::Pedersen},
folding::nova::{Nova, PreprocessorParam},
frontend::{tests::CubicFCircuit, FCircuit},
frontend::{utils::CubicFCircuit, FCircuit},
transcript::poseidon::poseidon_canonical_config,
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 crate::arith::{r1cs::R1CS, Arith};
use crate::arith::r1cs::{RelaxedR1CS, R1CS};
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_dummy = Witness::<C>::dummy(w_len, self.A.n_rows);
let u_dummy = CommittedInstance::<C>::dummy(self.l);
(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> {
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,
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))
}
}

+ 41
- 27
folding-schemes/src/folding/nova/zk.rs

@ -1,5 +1,35 @@
// Implements nova's zero-knowledge layer, as described in https://eprint.iacr.org/2023/573.pdf
use crate::folding::nova::traits::NovaR1CS;
/// Implements Nova's zero-knowledge layer, as described in https://eprint.iacr.org/2023/573.pdf.
///
/// Remark: this zk layer implementation only covers a subset of the use cases:
///
/// We identify 3 interesting places to use the nova zk-layer: one before all the folding pipeline
/// (Use-case-1), one at the end of the folding pipeline right before the final Decider SNARK
/// proof (Use-case-2), and a third one for cases where compressed SNARK proofs are not needed, and
/// just IVC proofs (bigger than SNARK proofs) suffice (Use-case-3):
///
/// * Use-case-1: at the beginning of the folding pipeline, right when the user has their original
/// instance prior to be folded into the running instance, the user can fold it with the
/// random-satisfying-instance to then have a blinded instance that can be sent to a server that
/// will fold it with the running instance.
/// --> In this one, the user could externalize all the IVC folding and also the Decider
/// final proof generation to a server.
/// * Use-case-2: at the end of all the IVC folding steps (after n iterations of nova.prove_step),
/// to 'blind' the IVC proof so then it can be sent to a server that will generate the final
/// decider snark proof.
/// --> In this one, the user could externalize the Decider final proof generation to a
/// server.
/// * Use-case-3: the user does not care about the Decider (final compressed SNARK proof), and
/// wants to generate a zk-proof of the IVC state to an IVC verifier (without any SNARK proof
/// involved). Note that this proof will be much bigger and expensive to verify than a Decider
/// SNARK proof.
///
/// The current implementation covers the Use-case-3.
/// Use-case-1 can be achieved directly by a simpler version of the zk IVC scheme skipping steps
/// and implemented directly at the app level by folding the original instance with a randomized
/// instance (steps 2,3,4 from section D.4 of the [HyperNova](https://eprint.iacr.org/2023/573.pdf)
/// paper).
/// And the Use-case-2 would require a modified version of the Decider circuits.
///
use ark_crypto_primitives::sponge::CryptographicSponge;
use ark_ff::{BigInteger, PrimeField};
use ark_std::{One, Zero};
@ -110,9 +140,8 @@ where
// d. Store folding proof
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)
// a. Compute T
@ -249,21 +278,10 @@ where
);
// 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
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(())
}
@ -274,7 +292,7 @@ pub mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
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 ark_bn254::{Fr, G1Projective as Projective};
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
@ -349,11 +367,9 @@ pub mod tests {
F_circuit,
3,
);
let (sampled_committed_instance, _) = nova
let (_, sampled_committed_instance) = nova
.r1cs
.clone()
.relax()
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap();
// proof verification fails with incorrect running instance
@ -388,11 +404,9 @@ pub mod tests {
F_circuit,
3,
);
let (_, sampled_committed_witness) = nova
let (sampled_committed_witness, _) = nova
.r1cs
.clone()
.relax()
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap();
// 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_ff::PrimeField;
use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain};
use ark_r1cs_std::{
alloc::AllocVar,
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, FieldVar},
groups::{CurveVar, GroupOpsBounds},
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::{
folding::lagrange_polys,
utils::{all_powers_var, betas_star_var, exponential_powers_var},
CommittedInstanceVar,
CommittedInstance, CommittedInstanceVar, ProtoGalaxyCycleFoldConfig,
};
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,
};
pub struct FoldingGadget {}
impl FoldingGadget {
#[allow(clippy::type_complexity)]
pub fn fold_committed_instance<C: CurveGroup, S: CryptographicSponge>(
transcript: &mut impl TranscriptVar<C::ScalarField, S>,
// running instance
@ -30,9 +50,8 @@ impl FoldingGadget {
// polys from P
F_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 n = F_coeffs.len();
// absorb the committed instances
transcript.absorb(instance)?;
@ -44,7 +63,7 @@ impl FoldingGadget {
transcript.absorb(&F_coeffs)?;
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
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 mut u_star = &instance.u * &L_X_evals[0];
let mut x_star = instance.x.mul_scalar(&L_X_evals[0])?;
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])?)?;
}
// 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)]
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 super::*;
@ -125,8 +487,11 @@ mod tests {
transcript::poseidon::poseidon_canonical_config,
};
use ark_bn254::{Fr, G1Projective as Projective};
use ark_relations::r1cs::ConstraintSystem;
#[test]
fn test_fold_gadget() -> Result<(), Box<dyn Error>> {
fn test_folding_gadget() -> Result<(), Box<dyn Error>> {
let k = 7;
let (witness, instance, witnesses, instances) = prepare_inputs(k);
let r1cs = get_test_r1cs::<Fr>();
@ -136,7 +501,7 @@ mod tests {
let mut transcript_p = 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,
&r1cs,
&instance,
@ -147,7 +512,6 @@ mod tests {
let folded_instance = Folding::<Projective>::verify(
&mut transcript_v,
&r1cs,
&instance,
&instances,
F_coeffs.clone(),
@ -161,7 +525,7 @@ mod tests {
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 folded_instance_var = FoldingGadget::fold_committed_instance(
let (folded_instance_var, _) = FoldingGadget::fold_committed_instance(
&mut transcript_var,
&instance_var,
&instances_var,
@ -170,7 +534,6 @@ mod tests {
)?;
assert_eq!(folded_instance.betas, folded_instance_var.betas.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!(cs.is_satisfied()?);

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

@ -6,18 +6,19 @@ use ark_poly::{
univariate::{DensePolynomial, SparsePolynomial},
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 super::utils::{all_powers, betas_star, exponential_powers};
use super::utils::{all_powers, betas_star, exponential_powers, pow_i};
use super::ProtoGalaxyError;
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::utils::vec::*;
use crate::utils::virtual_polynomial::bit_decompose;
use crate::Error;
#[derive(Clone, Debug)]
@ -48,6 +49,8 @@ where
Witness<C::ScalarField>,
Vec<C::ScalarField>, // F_X coeffs
Vec<C::ScalarField>, // K_X coeffs
Vec<C::ScalarField>, // L_X evals
Vec<C>, // phi_stars
),
Error,
> {
@ -63,19 +66,25 @@ where
let k = vec_instances.len();
let t = instance.betas.len();
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 {
return Err(Error::NotSameLength(
"z.len()".to_string(),
z.len(),
"n".to_string(),
"number of variables in R1CS".to_string(), // hardcoded to R1CS
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() {
return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k)));
@ -88,13 +97,24 @@ where
let delta = transcript.get_challenge();
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)
let F_X: SparsePolynomial<C::ScalarField> =
calc_f_from_btree(&f_z, &instance.betas, &deltas).expect("Error calculating F[x]");
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();
@ -107,16 +127,14 @@ where
// sanity check: check that the new randomized instance (the original instance but with
// 'refreshed' randomness) satisfies the relation.
#[cfg(test)]
tests::check_instance(
r1cs,
r1cs.check_relaxed_relation(
w,
&CommittedInstance {
phi: instance.phi,
betas: betas_star.clone(),
e: F_alpha,
u: instance.u,
x: instance.x.clone(),
},
w,
)?;
let zs: Vec<Vec<C::ScalarField>> = std::iter::once(z.clone())
@ -125,12 +143,12 @@ where
.iter()
.zip(vec_instances)
.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 {
return Err(Error::NotSameLength(
"zj.len()".to_string(),
zj.len(),
"n".to_string(),
"number of variables in R1CS".to_string(),
n,
));
}
@ -153,26 +171,19 @@ where
// each iteration evaluates G(h)
// 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()];
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
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() {
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> =
Evaluations::<C::ScalarField>::from_vec_and_domain(G_evals, G_domain).interpolate();
@ -190,7 +201,9 @@ where
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();
@ -200,17 +213,18 @@ where
.map(|L| L.evaluate(&gamma))
.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 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 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]);
for i in 0..k {
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];
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];
u_star += vec_instances[i].u * L_X_evals[i + 1];
x_star = vec_add(
&x_star,
&vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]),
@ -222,22 +236,22 @@ where
betas: betas_star,
phi: phi_star,
e: e_star,
u: u_star,
x: x_star,
},
Witness {
w: 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
pub fn verify(
transcript: &mut impl Transcript<C::ScalarField>,
r1cs: &R1CS<C::ScalarField>,
// running instance
instance: &CommittedInstance<C>,
// incoming instances
@ -247,7 +261,6 @@ where
K_coeffs: Vec<C::ScalarField>,
) -> Result<CommittedInstance<C>, Error> {
let t = instance.betas.len();
let n = r1cs.A.n_cols;
// absorb the committed instances
transcript.absorb(instance);
@ -259,7 +272,7 @@ where
transcript.absorb(&F_coeffs);
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
let mut F_alpha = instance.e;
@ -269,6 +282,8 @@ where
let betas_star = betas_star(&instance.betas, &deltas, alpha);
transcript.absorb(&K_coeffs);
let k = vec_instances.len();
let H =
GeneralEvaluationDomain::<C::ScalarField>::new(k + 1).ok_or(Error::NewDomainFail)?;
@ -277,8 +292,6 @@ where
let K_X: DensePolynomial<C::ScalarField> =
DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs);
transcript.absorb(&K_X.coeffs);
let gamma = transcript.get_challenge();
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 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]);
for i in 0..k {
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_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]),
@ -306,30 +317,11 @@ where
betas: betas_star,
phi: phi_star,
e: e_star,
u: u_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
/// described in Claim 4.4
/// of [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
@ -394,16 +386,6 @@ pub fn lagrange_polys(
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)]
pub mod tests {
use super::*;
@ -412,38 +394,10 @@ pub mod tests {
use ark_pallas::{Fr, Projective};
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::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]
fn test_pow_i() {
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
#[allow(clippy::type_complexity)]
pub fn prepare_inputs(
pub fn prepare_inputs<C: CurveGroup>(
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 (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 witness = Witness::<Fr> {
let witness = Witness::<C::ScalarField> {
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,
betas: betas.clone(),
e: Fr::zero(),
u,
e: C::ScalarField::zero(),
x,
};
// 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)]
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,
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,
betas: vec![],
e: Fr::zero(),
u: u_i,
e: C::ScalarField::zero(),
x: x_i,
};
witnesses.push(witness_i);
@ -549,20 +481,20 @@ pub mod tests {
let mut transcript_p = 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
let folded_instance_v = Folding::<Projective>::verify(
&mut transcript_v,
&r1cs,
&instance,
&instances,
F_coeffs,
@ -577,7 +509,8 @@ pub mod tests {
assert!(!folded_instance.e.is_zero());
// 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]
@ -598,7 +531,7 @@ pub mod tests {
// generate the instances to be fold
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(
&mut transcript_p,
&r1cs,
@ -612,7 +545,6 @@ pub mod tests {
// verifier
let folded_instance_v = Folding::<Projective>::verify(
&mut transcript_v,
&r1cs,
&running_instance,
&instances,
F_coeffs,
@ -626,7 +558,8 @@ pub mod tests {
assert!(!folded_instance.e.is_zero());
// 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_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_r1cs_std::{fields::fp::FpVar, uint8::UInt8, ToConstraintFieldGadget};
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.
impl<C: CurveGroup> Absorb for CommittedInstance<C>
@ -22,7 +28,6 @@ where
.to_sponge_field_elements(dest);
self.betas.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);
}
}
@ -38,9 +43,74 @@ impl AbsorbGadget for CommittedInstanceVar {
self.phi.to_constraint_field()?,
self.betas.to_sponge_field_elements()?,
self.e.to_sponge_field_elements()?,
self.u.to_sponge_field_elements()?,
self.x.to_sponge_field_elements()?,
]
.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_r1cs_std::fields::{fp::FpVar, FieldVar};
use num_integer::Integer;
/// Returns (b, b^2, b^4, ..., b^{2^{t-1}})
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>>>()
}
/// 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)]
mod tests {
use std::error::Error;
@ -78,6 +113,7 @@ mod tests {
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::{test_rng, UniformRand};
use rand::Rng;
use super::*;
@ -144,4 +180,25 @@ mod tests {
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(())
}
}

+ 18
- 11
folding-schemes/src/frontend/circom/mod.rs

@ -1,5 +1,6 @@
use crate::frontend::FCircuit;
use crate::frontend::FpVar::Var;
use crate::utils::PathOrBin;
use crate::Error;
use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS};
use ark_ff::PrimeField;
@ -9,7 +10,6 @@ use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
use ark_std::fmt::Debug;
use num_bigint::BigInt;
use std::path::PathBuf;
use std::rc::Rc;
use std::{fmt, usize};
@ -93,11 +93,11 @@ impl CircomFCircuit {
impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> {
/// (r1cs_path, wasm_path, state_len, external_inputs_len)
type Params = (PathBuf, PathBuf, usize, usize);
type Params = (PathOrBin, PathOrBin, usize, usize);
fn new(params: Self::Params) -> Result<Self, Error> {
let (r1cs_path, wasm_path, state_len, external_inputs_len) = params;
let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path);
let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path)?;
let r1cs = circom_wrapper.extract_r1cs()?;
Ok(Self {
@ -208,6 +208,7 @@ pub mod tests {
use super::*;
use ark_bn254::Fr;
use ark_relations::r1cs::ConstraintSystem;
use std::path::PathBuf;
// Tests the step_native function of CircomFCircuit.
#[test]
@ -216,7 +217,8 @@ pub mod tests {
let wasm_path =
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
let circom_fcircuit =
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
let z_i = vec![Fr::from(3u32)];
let z_i1 = circom_fcircuit.step_native(1, z_i, vec![]).unwrap();
@ -230,7 +232,8 @@ pub mod tests {
let wasm_path =
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
let circom_fcircuit =
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
let cs = ConstraintSystem::<Fr>::new_ref();
@ -250,11 +253,12 @@ pub mod tests {
let wasm_path =
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
let circom_fcircuit =
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
// Allocates z_i1 by using step_native function.
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(),
z_i: Some(z_i.clone()),
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
@ -276,7 +280,8 @@ pub mod tests {
let wasm_path = PathBuf::from(
"./src/frontend/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
);
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
let circom_fcircuit =
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
let cs = ConstraintSystem::<Fr>::new_ref();
let z_i = vec![Fr::from(3u32)];
let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)];
@ -319,7 +324,8 @@ pub mod tests {
let wasm_path = PathBuf::from(
"./src/frontend/circom/test_folder/no_external_inputs_js/no_external_inputs.wasm",
);
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 3, 0)).unwrap();
let circom_fcircuit =
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 3, 0)).unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let z_i = vec![Fr::from(3u32), Fr::from(4u32), Fr::from(5u32)];
let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
@ -351,7 +357,8 @@ pub mod tests {
let wasm_path =
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
let mut circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
let mut circom_fcircuit =
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
let z = z_i[0];
@ -360,7 +367,7 @@ pub mod tests {
// Allocates z_i1 by using step_native function.
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(),
z_i: Some(z_i.clone()),
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),

+ 39
- 17
folding-schemes/src/frontend/circom/utils.rs

@ -3,40 +3,64 @@ use ark_circom::{
WitnessCalculator,
};
use ark_ff::{BigInteger, PrimeField};
use ark_serialize::Read;
use color_eyre::Result;
use num_bigint::{BigInt, Sign};
use std::{fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
use std::{fs::File, io::Cursor, marker::PhantomData, path::PathBuf};
use crate::Error;
use crate::{utils::PathOrBin, Error};
// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses
// based on file paths to Circom's .r1cs and .wasm.
#[derive(Clone, Debug)]
pub struct CircomWrapper<F: PrimeField> {
r1cs_filepath: PathBuf,
wasm_filepath: PathBuf,
r1csfile_bytes: Vec<u8>,
wasmfile_bytes: Vec<u8>,
_marker: PhantomData<F>,
}
impl<F: PrimeField> CircomWrapper<F> {
// Creates a new instance of the CircomWrapper with the file paths.
pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
CircomWrapper {
r1cs_filepath,
wasm_filepath,
_marker: PhantomData,
pub fn new(r1cs: PathOrBin, wasm: PathOrBin) -> Result<Self, Error> {
match (r1cs, wasm) {
(PathOrBin::Path(r1cs_path), PathOrBin::Path(wasm_path)) => {
Self::new_from_path(r1cs_path, wasm_path)
}
(PathOrBin::Bin(r1cs_bin), PathOrBin::Bin(wasm_bin)) => Ok(Self {
r1csfile_bytes: r1cs_bin,
wasmfile_bytes: wasm_bin,
_marker: PhantomData,
}),
_ => unreachable!("You should pass the same enum branch for both inputs"),
}
}
// Creates a new instance of the CircomWrapper with the file paths.
fn new_from_path(r1cs_file_path: PathBuf, wasm_file_path: PathBuf) -> Result<Self, Error> {
let mut file = File::open(r1cs_file_path)?;
let metadata = File::metadata(&file)?;
let mut r1csfile_bytes = vec![0; metadata.len() as usize];
file.read_exact(&mut r1csfile_bytes)?;
let mut file = File::open(wasm_file_path)?;
let metadata = File::metadata(&file)?;
let mut wasmfile_bytes = vec![0; metadata.len() as usize];
file.read_exact(&mut wasmfile_bytes)?;
Ok(CircomWrapper {
r1csfile_bytes,
wasmfile_bytes,
_marker: PhantomData,
})
}
// Aggregated function to obtain R1CS and witness from Circom.
pub fn extract_r1cs_and_witness(
&self,
inputs: &[(String, Vec<BigInt>)],
) -> Result<(R1CS<F>, Option<Vec<F>>), Error> {
// Extracts the R1CS
let file = File::open(&self.r1cs_filepath)?;
let reader = BufReader::new(file);
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(Cursor::new(&self.r1csfile_bytes))?;
let r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
// Extracts the witness vector
@ -46,9 +70,7 @@ impl CircomWrapper {
}
pub fn extract_r1cs(&self) -> Result<R1CS<F>, Error> {
let file = File::open(&self.r1cs_filepath)?;
let reader = BufReader::new(file);
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(Cursor::new(&self.r1csfile_bytes))?;
let mut r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
r1cs.wire_mapping = None;
Ok(r1cs)
@ -75,7 +97,7 @@ impl CircomWrapper {
&self,
inputs: &[(String, Vec<BigInt>)],
) -> Result<Vec<BigInt>, Error> {
let mut calculator = WitnessCalculator::new(&self.wasm_filepath).map_err(|e| {
let mut calculator = WitnessCalculator::from_binary(&self.wasmfile_bytes).map_err(|e| {
Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e))
})?;
calculator
@ -139,7 +161,7 @@ mod tests {
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])];
let wrapper = CircomWrapper::<Fr>::new(r1cs_path, wasm_path);
let wrapper = CircomWrapper::<Fr>::new(r1cs_path.into(), wasm_path.into()).unwrap();
let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();

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

@ -7,6 +7,7 @@ use ark_std::fmt::Debug;
pub mod circom;
pub mod noir;
pub mod noname;
pub mod utils;
/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
/// inside the agmented F' function).
@ -53,131 +54,9 @@ pub trait FCircuit: Clone + Debug {
pub mod tests {
use super::*;
use ark_bn254::Fr;
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
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]
fn test_testfcircuit() {

+ 11
- 6
folding-schemes/src/frontend/noir/mod.rs

@ -1,6 +1,6 @@
use std::collections::HashMap;
use crate::Error;
use crate::{utils::PathOrBin, Error};
use super::FCircuit;
use acvm::{
@ -16,7 +16,9 @@ use ark_ff::PrimeField;
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar};
use ark_relations::r1cs::ConstraintSynthesizer;
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
use arkworks_backend::{read_program_from_file, sonobe_bridge::AcirCircuitSonobe};
use noir_arkworks_backend::{
read_program_from_binary, read_program_from_file, sonobe_bridge::AcirCircuitSonobe,
};
#[derive(Clone, Debug)]
pub struct NoirFCircuit<F: PrimeField> {
@ -26,12 +28,15 @@ pub struct NoirFCircuit {
}
impl<F: PrimeField> FCircuit<F> for NoirFCircuit<F> {
type Params = (String, usize, usize);
type Params = (PathOrBin, usize, usize);
fn new(params: Self::Params) -> Result<Self, crate::Error> {
let (path, state_len, external_inputs_len) = params;
let program =
read_program_from_file(path).map_err(|ee| Error::Other(format!("{:?}", ee)))?;
let (source, state_len, external_inputs_len) = params;
let program = match source {
PathOrBin::Path(path) => read_program_from_file(path),
PathOrBin::Bin(bytes) => read_program_from_binary(&bytes),
}
.map_err(|ee| Error::Other(format!("{:?}", ee)))?;
let circuit: Circuit<GenericFieldElement<F>> = program.functions[0].clone();
let ivc_input_length = circuit.public_parameters.0.len();
let ivc_return_length = circuit.return_values.0.len();

+ 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(())
}
}

+ 1
- 1
folding-schemes/src/lib.rs

@ -219,7 +219,7 @@ pub trait Decider<
fn preprocess(
rng: impl RngCore + CryptoRng,
prep_param: &Self::PreprocessorParam,
prep_param: Self::PreprocessorParam,
fs: FS,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error>;

+ 31
- 0
folding-schemes/src/utils/mod.rs

@ -1,3 +1,6 @@
use std::path::Path;
use std::path::PathBuf;
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
@ -100,3 +103,31 @@ where
&public_params_hash,
))
}
/// Tiny utility enum that allows to import circuits and wasm modules from files by passing their path
/// or passing their content already read.
///
/// This enum implements the [`From`] trait for both [`Path`], [`PathBuf`] and [`Vec<u8>`].
#[derive(Debug, Clone)]
pub enum PathOrBin {
Path(PathBuf),
Bin(Vec<u8>),
}
impl From<&Path> for PathOrBin {
fn from(value: &Path) -> Self {
PathOrBin::Path(value.into())
}
}
impl From<PathBuf> for PathOrBin {
fn from(value: PathBuf) -> Self {
PathOrBin::Path(value)
}
}
impl From<Vec<u8>> for PathOrBin {
fn from(value: Vec<u8>) -> Self {
PathOrBin::Bin(value)
}
}

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

@ -20,6 +20,13 @@ pub struct SparseMatrix {
}
impl<F: PrimeField> SparseMatrix<F> {
pub fn empty() -> Self {
Self {
n_rows: 0,
n_cols: 0,
coeffs: vec![],
}
}
pub fn rand<R: Rng>(rng: &mut R, n_rows: usize, n_cols: usize) -> Self {
const ZERO_VAL_PROBABILITY: f64 = 0.8f64;
@ -75,7 +82,7 @@ pub fn vec_add(a: &[F], b: &[F]) -> Result, Error> {
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> {
@ -87,15 +94,15 @@ pub fn vec_sub(a: &[F], b: &[F]) -> Result, Error> {
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> {
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 {
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> {
@ -111,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> {
@ -129,13 +132,9 @@ pub fn mat_vec_mul(M: &SparseMatrix, z: &[F]) -> Result
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> {

+ 26
- 11
solidity-verifiers/src/verifiers/nova_cyclefold.rs

@ -2,13 +2,14 @@
#![allow(non_camel_case_types)]
#![allow(clippy::upper_case_acronyms)]
use ark_bn254::{Bn254, Fq, Fr, G1Affine};
use ark_bn254::{Bn254, Fq, Fr, G1Affine, G1Projective};
use ark_groth16::VerifyingKey as ArkG16VerifierKey;
use ark_poly_commit::kzg10::VerifierKey as ArkKZG10VerifierKey;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use askama::Template;
use folding_schemes::folding::circuits::nonnative::uint::NonNativeUintVar;
use folding_schemes::folding::nova::decider_eth::VerifierParam as DeciderVerifierParam;
use super::g16::Groth16Verifier;
use super::kzg::KZG10Verifier;
@ -92,22 +93,26 @@ impl From<(Fr, Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVe
// in the NovaCycleFoldDecider verifier contract
impl
From<(
(Fr, ArkG16VerifierKey<Bn254>, ArkKZG10VerifierKey<Bn254>),
DeciderVerifierParam<G1Projective, ArkKZG10VerifierKey<Bn254>, ArkG16VerifierKey<Bn254>>,
usize,
)> for NovaCycleFoldVerifierKey
{
fn from(
value: (
(Fr, ArkG16VerifierKey<Bn254>, ArkKZG10VerifierKey<Bn254>),
DeciderVerifierParam<
G1Projective,
ArkKZG10VerifierKey<Bn254>,
ArkG16VerifierKey<Bn254>,
>,
usize,
),
) -> Self {
let decider_vp = value.0;
let g16_vk = Groth16VerifierKey::from(decider_vp.1);
let g16_vk = Groth16VerifierKey::from(decider_vp.snark_vp);
// pass `Vec::new()` since batchCheck will not be used
let kzg_vk = KZG10VerifierKey::from((decider_vp.2, Vec::new()));
let kzg_vk = KZG10VerifierKey::from((decider_vp.cs_vp, Vec::new()));
Self {
pp_hash: decider_vp.0,
pp_hash: decider_vp.pp_hash,
g16_vk,
kzg_vk,
z_len: value.1,
@ -157,7 +162,7 @@ mod tests {
Decider, Error, FoldingScheme,
};
use super::NovaCycleFoldDecider;
use super::{DeciderVerifierParam, NovaCycleFoldDecider};
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
use crate::{
evm::{compile_solidity, save_solidity, Evm},
@ -286,9 +291,14 @@ mod tests {
fn nova_cyclefold_vk_serde_roundtrip() {
let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
let mut bytes = vec![];
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from(((pp_hash, g16_vk, kzg_vk), 1));
let decider_vp = DeciderVerifierParam {
pp_hash,
snark_vp: g16_vk,
cs_vp: kzg_vk,
};
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, 1));
let mut bytes = vec![];
nova_cyclefold_vk
.serialize_protocol_verifier_key(&mut bytes)
.unwrap();
@ -301,7 +311,12 @@ mod tests {
#[test]
fn nova_cyclefold_decider_template_renders() {
let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from(((pp_hash, g16_vk, kzg_vk), 1));
let decider_vp = DeciderVerifierParam {
pp_hash,
snark_vp: g16_vk,
cs_vp: kzg_vk,
};
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, 1));
let decider_solidity_code = HeaderInclusion::<NovaCycleFoldDecider>::builder()
.template(nova_cyclefold_vk)
@ -331,7 +346,7 @@ mod tests {
)
.unwrap();
let decider_params =
DECIDER::preprocess(&mut rng, &nova_params.clone(), nova.clone()).unwrap();
DECIDER::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
(nova_params, decider_params)
}

Loading…
Cancel
Save