Browse Source

Refactor `Arith` trait (#162)

* `Dummy` trait

* More generic design for `Arith`

* Distinguish between incoming and running instances in ProtoGalaxy

* Format

* Fix unit test

* Fix incorrect arguments supplied to `CycleFoldWitness::dummy`

* `RUNNING` and `INCOMING` constants

* Better name and docs for `eval_core`

* More docs  for `Arith` methods and implementations

* Fix missing imports
main
winderica 1 month ago
committed by GitHub
parent
commit
f1d82418ba
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
24 changed files with 723 additions and 531 deletions
  1. +39
    -26
      folding-schemes/src/arith/ccs.rs
  2. +142
    -14
      folding-schemes/src/arith/mod.rs
  3. +59
    -84
      folding-schemes/src/arith/r1cs.rs
  4. +26
    -27
      folding-schemes/src/folding/hypernova/cccs.rs
  5. +21
    -20
      folding-schemes/src/folding/hypernova/circuits.rs
  6. +9
    -10
      folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
  7. +33
    -30
      folding-schemes/src/folding/hypernova/lcccs.rs
  8. +32
    -26
      folding-schemes/src/folding/hypernova/mod.rs
  9. +15
    -11
      folding-schemes/src/folding/hypernova/nimfs.rs
  10. +8
    -4
      folding-schemes/src/folding/hypernova/utils.rs
  11. +4
    -1
      folding-schemes/src/folding/nova/circuits.rs
  12. +5
    -9
      folding-schemes/src/folding/nova/decider_circuits.rs
  13. +20
    -28
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  14. +43
    -31
      folding-schemes/src/folding/nova/mod.rs
  15. +18
    -15
      folding-schemes/src/folding/nova/nifs.rs
  16. +49
    -42
      folding-schemes/src/folding/nova/traits.rs
  17. +8
    -6
      folding-schemes/src/folding/nova/zk.rs
  18. +10
    -10
      folding-schemes/src/folding/protogalaxy/circuits.rs
  19. +4
    -0
      folding-schemes/src/folding/protogalaxy/constants.rs
  20. +29
    -27
      folding-schemes/src/folding/protogalaxy/folding.rs
  21. +89
    -45
      folding-schemes/src/folding/protogalaxy/mod.rs
  22. +47
    -62
      folding-schemes/src/folding/protogalaxy/traits.rs
  23. +10
    -0
      folding-schemes/src/folding/traits.rs
  24. +3
    -3
      folding-schemes/src/utils/mod.rs

+ 39
- 26
folding-schemes/src/arith/ccs.rs

@ -1,9 +1,12 @@
use ark_ff::PrimeField;
use ark_std::log2;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, SparseMatrix};
use crate::utils::vec::{
hadamard, is_zero_vec, mat_vec_mul, vec_add, vec_scalar_mul, SparseMatrix,
};
use crate::Error;
use super::ArithSerializer;
use super::{r1cs::R1CS, Arith};
/// CCS represents the Customizable Constraint Systems structure defined in
@ -35,8 +38,9 @@ pub struct CCS {
pub c: Vec<F>,
}
impl<F: PrimeField> Arith<F> for CCS<F> {
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error> {
impl<F: PrimeField> CCS<F> {
/// Evaluates the CCS relation at a given vector of assignments `z`
pub fn eval_at_z(&self, z: &[F]) -> Result<Vec<F>, Error> {
let mut result = vec![F::zero(); self.m];
for i in 0..self.q {
@ -59,6 +63,25 @@ impl Arith for CCS {
Ok(result)
}
/// returns a tuple containing (w, x) (witness and public inputs respectively)
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
}
}
impl<F: PrimeField, W: AsRef<[F]>, U: AsRef<[F]>> Arith<W, U> for CCS<F> {
type Evaluation = Vec<F>;
fn eval_relation(&self, w: &W, u: &U) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[F::one()], u.as_ref(), w.as_ref()].concat())
}
fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> {
is_zero_vec(&e).then_some(()).ok_or(Error::NotSatisfied)
}
}
impl<F: PrimeField> ArithSerializer for CCS<F> {
fn params_to_le_bytes(&self) -> Vec<u8> {
[
self.l.to_le_bytes(),
@ -72,14 +95,14 @@ impl Arith for CCS {
}
}
impl<F: PrimeField> CCS<F> {
pub fn from_r1cs(r1cs: R1CS<F>) -> Self {
let m = r1cs.A.n_rows;
let n = r1cs.A.n_cols;
impl<F: PrimeField> From<R1CS<F>> for CCS<F> {
fn from(r1cs: R1CS<F>) -> Self {
let m = r1cs.num_constraints();
let n = r1cs.num_variables();
CCS {
m,
n,
l: r1cs.l,
l: r1cs.num_public_inputs(),
s: log2(m) as usize,
s_prime: log2(n) as usize,
t: 3,
@ -91,29 +114,19 @@ impl CCS {
M: vec![r1cs.A, r1cs.B, r1cs.C],
}
}
pub fn to_r1cs(self) -> R1CS<F> {
R1CS::<F> {
l: self.l,
A: self.M[0].clone(),
B: self.M[1].clone(),
C: self.M[2].clone(),
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::{
arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z},
arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z, get_test_z_split},
utils::vec::is_zero_vec,
};
use ark_pallas::Fr;
pub fn get_test_ccs<F: PrimeField>() -> CCS<F> {
let r1cs = get_test_r1cs::<F>();
CCS::<F>::from_r1cs(r1cs)
get_test_r1cs::<F>().into()
}
pub fn get_test_z<F: PrimeField>(input: usize) -> Vec<F> {
r1cs_get_test_z(input)
@ -122,13 +135,13 @@ pub mod tests {
#[test]
fn test_eval_ccs_relation() {
let ccs = get_test_ccs::<Fr>();
let mut z = get_test_z(3);
let (_, x, mut w) = get_test_z_split(3);
let f_w = ccs.eval_relation(&z).unwrap();
let f_w = ccs.eval_relation(&w, &x).unwrap();
assert!(is_zero_vec(&f_w));
z[1] = Fr::from(111);
let f_w = ccs.eval_relation(&z).unwrap();
w[1] = Fr::from(111);
let f_w = ccs.eval_relation(&w, &x).unwrap();
assert!(!is_zero_vec(&f_w));
}
@ -136,8 +149,8 @@ pub mod tests {
#[test]
fn test_check_ccs_relation() {
let ccs = get_test_ccs::<Fr>();
let z = get_test_z(3);
let (_, x, w) = get_test_z_split(3);
ccs.check_relation(&z).unwrap();
ccs.check_relation(&w, &x).unwrap();
}
}

+ 142
- 14
folding-schemes/src/arith/mod.rs

@ -1,28 +1,156 @@
use ark_ff::PrimeField;
use ark_ec::CurveGroup;
use ark_relations::r1cs::SynthesisError;
use ark_std::rand::RngCore;
use crate::Error;
use crate::{commitment::CommitmentScheme, folding::traits::Dummy, Error};
pub mod ccs;
pub mod r1cs;
pub trait Arith<F: PrimeField> {
/// 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>;
/// `Arith` defines the operations that a constraint system (e.g., R1CS, CCS,
/// etc.) should support.
///
/// Here, `W` is the type of witness, and `U` is the type of statement / public
/// input / public IO / instance.
/// Note that the same constraint system may support different types of `W` and
/// `U`, and the satisfiability check may vary.
///
/// For example, both plain R1CS and relaxed R1CS are represented by 3 matrices,
/// but the types of `W` and `U` are different:
/// - The plain R1CS has `W` and `U` as vectors of field elements.
///
/// `W = w` and `U = x` satisfy R1CS if `Az ∘ Bz = Cz`, where `z = [1, x, w]`.
///
/// - In Nova, Relaxed R1CS has `W` as [`crate::folding::nova::Witness`],
/// and `U` as [`crate::folding::nova::CommittedInstance`].
///
/// `W = (w, e, ...)` and `U = (u, x, ...)` satisfy Relaxed R1CS if
/// `Az ∘ Bz = uCz + e`, where `z = [u, x, w]`.
/// (commitments in `U` are not checked here)
///
/// Also, `W` and `U` have non-native field elements as their components when
/// used as CycleFold witness and instance.
///
/// - In ProtoGalaxy, Relaxed R1CS has `W` as [`crate::folding::protogalaxy::Witness`],
/// and `U` as [`crate::folding::protogalaxy::CommittedInstance`].
///
/// `W = (w, ...)` and `U = (x, e, β, ...)` satisfy Relaxed R1CS if
/// `e = Σ pow_i(β) v_i`, where `v = Az ∘ Bz - Cz`, `z = [1, x, w]`.
/// (commitments in `U` are not checked here)
///
/// This is also the case of CCS, where `W` and `U` may be vectors of field
/// elements, [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::lcccs::LCCCS`],
/// or [`crate::folding::hypernova::Witness`] and [`crate::folding::hypernova::cccs::CCCS`].
pub trait Arith<W, U>: Clone {
type Evaluation;
/// Checks that the given Arith structure is satisfied by a z vector, i.e.,
/// if the evaluation is a zero vector
/// Returns a dummy witness and instance
fn dummy_witness_instance<'a>(&'a self) -> (W, U)
where
W: Dummy<&'a Self>,
U: Dummy<&'a Self>,
{
(W::dummy(self), U::dummy(self))
}
/// Evaluates the constraint system `self` at witness `w` and instance `u`.
/// Returns the evaluation result.
///
/// The evaluation result is usually a vector of field elements.
/// For instance:
/// - Evaluating the plain R1CS at `W = w` and `U = x` returns
/// `Az ∘ Bz - Cz`, where `z = [1, x, w]`.
///
/// - Evaluating the relaxed R1CS in Nova at `W = (w, e, ...)` and
/// `U = (u, x, ...)` returns `Az ∘ Bz - uCz`, where `z = [u, x, w]`.
///
/// - Evaluating the relaxed R1CS in ProtoGalaxy at `W = (w, ...)` and
/// `U = (x, e, β, ...)` returns `Az ∘ Bz - Cz`, where `z = [1, x, w]`.
///
/// However, we use `Self::Evaluation` to represent the evaluation result
/// for future extensibility.
fn eval_relation(&self, w: &W, u: &U) -> Result<Self::Evaluation, Error>;
/// Checks if the evaluation result is valid. The witness `w` and instance
/// `u` are also parameters, because the validity check may need information
/// contained in `w` and/or `u`.
///
/// For instance:
/// - The evaluation `v` of plain R1CS at satisfying `W` and `U` should be
/// an all-zero vector.
///
/// - The evaluation `v` of relaxed R1CS in Nova at satisfying `W` and `U`
/// should be equal to the error term `e` in the witness.
///
/// - The evaluation `v` of relaxed R1CS in ProtoGalaxy at satisfying `W`
/// and `U` should satisfy `e = Σ pow_i(β) v_i`, where `e` is the error
/// term in the committed instance.
fn check_evaluation(w: &W, u: &U, v: Self::Evaluation) -> Result<(), Error>;
/// Checks if witness `w` and instance `u` satisfy the constraint system
/// `self` by first computing the evaluation result and then checking the
/// validity of the evaluation result.
///
/// 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)
}
fn check_relation(&self, w: &W, u: &U) -> Result<(), Error> {
let e = self.eval_relation(w, u)?;
Self::check_evaluation(w, u, e)
}
}
/// `ArithSerializer` is for serializing constraint systems.
///
/// Currently we only support converting parameters to bytes, but in the future
/// we may consider implementing methods for serializing the actual data (e.g.,
/// R1CS matrices).
pub trait ArithSerializer {
/// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of
/// public inputs, etc, without the matrices/polynomials values.
fn params_to_le_bytes(&self) -> Vec<u8>;
}
/// `ArithSampler` allows sampling random pairs of witness and instance that
/// satisfy the constraint system `self`.
///
/// This is useful for constructing a zero-knowledge layer for a folding-based
/// IVC.
/// An example of such a layer can be found in Appendix D of the [HyperNova]
/// paper.
///
/// Note that we use a separate trait for sampling, because this operation may
/// not be supported by all witness-instance pairs.
/// For instance, it is difficult (if not impossible) to do this for `w` and `x`
/// in a plain R1CS.
///
/// [HyperNova]: https://eprint.iacr.org/2023/573.pdf
pub trait ArithSampler<C: CurveGroup, W, U>: Arith<W, U> {
/// Samples a random witness and instance that satisfy the constraint system.
fn sample_witness_instance<CS: CommitmentScheme<C, true>>(
&self,
params: &CS::ProverParams,
rng: impl RngCore,
) -> Result<(W, U), Error>;
}
/// `ArithGadget` defines the in-circuit counterparts of operations specified in
/// `Arith` on constraint systems.
pub trait ArithGadget<WVar, UVar> {
type Evaluation;
/// Evaluates the constraint system `self` at witness `w` and instance `u`.
/// Returns the evaluation result.
fn eval_relation(&self, w: &WVar, u: &UVar) -> Result<Self::Evaluation, SynthesisError>;
/// Generates constraints for enforcing that witness `w` and instance `u`
/// satisfy the constraint system `self` by first computing the evaluation
/// result and then checking the validity of the evaluation result.
fn enforce_relation(&self, w: &WVar, u: &UVar) -> Result<(), SynthesisError> {
let e = self.eval_relation(w, u)?;
Self::enforce_evaluation(w, u, e)
}
/// Generates constraints for enforcing that the evaluation result is valid.
/// The witness `w` and instance `u` are also parameters, because the
/// validity check may need information contained in `w` and/or `u`.
fn enforce_evaluation(w: &WVar, u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError>;
}

+ 59
- 84
folding-schemes/src/arith/r1cs.rs

@ -1,13 +1,13 @@
use crate::commitment::CommitmentScheme;
use crate::RngCore;
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_scalar_mul, vec_sub, SparseMatrix};
use super::ccs::CCS;
use super::{Arith, ArithSerializer};
use crate::utils::vec::{
hadamard, is_zero_vec, mat_vec_mul, vec_scalar_mul, vec_sub, SparseMatrix,
};
use crate::Error;
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
@ -18,8 +18,9 @@ pub struct R1CS {
pub C: SparseMatrix<F>,
}
impl<F: PrimeField> Arith<F> for R1CS<F> {
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error> {
impl<F: PrimeField> R1CS<F> {
/// Evaluates the CCS relation at a given vector of variables `z`
pub fn eval_at_z(&self, z: &[F]) -> Result<Vec<F>, Error> {
if z.len() != self.A.n_cols {
return Err(Error::NotSameLength(
"z.len()".to_string(),
@ -33,12 +34,26 @@ impl Arith for R1CS {
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.
// both relaxed and plain R1CS.
let uCz = vec_scalar_mul(&Cz, &z[0]);
let AzBz = hadamard(&Az, &Bz)?;
vec_sub(&AzBz, &uCz)
}
}
impl<F: PrimeField, W: AsRef<[F]>, U: AsRef<[F]>> Arith<W, U> for R1CS<F> {
type Evaluation = Vec<F>;
fn eval_relation(&self, w: &W, u: &U) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[F::one()], u.as_ref(), w.as_ref()].concat())
}
fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> {
is_zero_vec(&e).then_some(()).ok_or(Error::NotSatisfied)
}
}
impl<F: PrimeField> ArithSerializer for R1CS<F> {
fn params_to_le_bytes(&self) -> Vec<u8> {
[
self.l.to_le_bytes(),
@ -67,66 +82,41 @@ impl R1CS {
}
}
/// returns a tuple containing (w, x) (witness and public inputs respectively)
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
#[inline]
pub fn num_constraints(&self) -> usize {
self.A.n_rows
}
}
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);
/// 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);
}
let z = Self::extract_z(w, u);
self.check_relation(&z)
#[inline]
pub fn num_public_inputs(&self) -> usize {
self.l
}
/// 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)
#[inline]
pub fn num_variables(&self) -> usize {
self.A.n_cols
}
// Computes the E term, given A, B, C, z, u
fn compute_E(
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)?;
#[inline]
pub fn num_witnesses(&self) -> usize {
self.num_variables() - self.num_public_inputs() - 1
}
let Cz = mat_vec_mul(C, z)?;
let uCz = vec_scalar_mul(&Cz, u);
vec_sub(&AzBz, &uCz)
/// returns a tuple containing (w, x) (witness and public inputs respectively)
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
}
}
fn sample<CS>(&self, params: &CS::ProverParams, rng: impl RngCore) -> Result<(W, U), Error>
where
CS: CommitmentScheme<C, true>;
impl<F: PrimeField> From<CCS<F>> for R1CS<F> {
fn from(ccs: CCS<F>) -> Self {
R1CS::<F> {
l: ccs.l,
A: ccs.M[0].clone(),
B: ccs.M[1].clone(),
C: ccs.M[2].clone(),
}
}
}
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS
@ -173,27 +163,12 @@ 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::{
is_zero_vec,
tests::{to_F_matrix, to_F_vec},
},
use crate::utils::vec::{
is_zero_vec,
tests::{to_F_matrix, to_F_vec},
};
use ark_pallas::{Fr, Projective};
#[test]
pub fn sample_relaxed_r1cs() {
let rng = rand::rngs::OsRng;
let r1cs = get_test_r1cs::<Fr>();
let (prover_params, _) = Pedersen::<Projective>::setup(rng, r1cs.A.n_rows).unwrap();
let sampled: Result<(Witness<Projective>, CommittedInstance<Projective>), _> =
r1cs.sample::<Pedersen<Projective, true>>(&prover_params, rng);
assert!(sampled.is_ok());
}
use ark_pallas::Fr;
pub fn get_test_r1cs<F: PrimeField>() -> R1CS<F> {
// R1CS for: x^3 + x + 5 = y (example from article
@ -252,20 +227,20 @@ pub mod tests {
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 (_, x, mut w) = get_test_z_split::<Fr>(rng.gen::<u16>() as usize);
let f_w = r1cs.eval_relation(&z).unwrap();
let f_w = r1cs.eval_relation(&w, &x).unwrap();
assert!(is_zero_vec(&f_w));
z[1] = Fr::from(111);
let f_w = r1cs.eval_relation(&z).unwrap();
w[1] = Fr::from(111);
let f_w = r1cs.eval_relation(&w, &x).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();
let (_, x, w) = get_test_z_split(5);
r1cs.check_relation(&w, &x).unwrap();
}
}

+ 26
- 27
folding-schemes/src/folding/hypernova/cccs.rs

@ -3,20 +3,17 @@ 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;
use ark_std::rand::Rng;
use ark_std::{rand::Rng, sync::Arc, One, Zero};
use super::circuits::CCCSVar;
use super::Witness;
use crate::arith::{ccs::CCS, Arith};
use crate::commitment::CommitmentScheme;
use crate::folding::traits::CommittedInstanceOps;
use crate::folding::circuits::CF1;
use crate::folding::traits::{CommittedInstanceOps, Dummy};
use crate::transcript::AbsorbNonNative;
use crate::utils::mle::dense_vec_to_dense_mle;
use crate::utils::vec::mat_vec_mul;
use crate::utils::vec::{is_zero_vec, mat_vec_mul};
use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial};
use crate::Error;
@ -93,34 +90,34 @@ impl CCS {
}
}
impl<C: CurveGroup> CCCS<C> {
pub fn dummy(l: usize) -> CCCS<C>
where
C::ScalarField: PrimeField,
{
CCCS::<C> {
impl<C: CurveGroup> Dummy<&CCS<CF1<C>>> for CCCS<C> {
fn dummy(ccs: &CCS<CF1<C>>) -> Self {
Self {
C: C::zero(),
x: vec![C::ScalarField::zero(); l],
x: vec![CF1::<C>::zero(); ccs.l],
}
}
}
impl<C: CurveGroup> Arith<Witness<CF1<C>>, CCCS<C>> for CCS<CF1<C>> {
type Evaluation = Vec<CF1<C>>;
fn eval_relation(&self, w: &Witness<CF1<C>>, u: &CCCS<C>) -> Result<Self::Evaluation, Error> {
// evaluate CCCS relation
self.eval_at_z(&[&[CF1::<C>::one()][..], &u.x, &w.w].concat())
}
/// Perform the check of the CCCS instance described at section 4.1,
/// notice that this method does not check the commitment correctness
pub fn check_relation(
&self,
ccs: &CCS<C::ScalarField>,
w: &Witness<C::ScalarField>,
fn check_evaluation(
_w: &Witness<CF1<C>>,
_u: &CCCS<C>,
e: Self::Evaluation,
) -> Result<(), Error> {
// check CCCS relation
let z: Vec<C::ScalarField> =
[vec![C::ScalarField::one()], self.x.clone(), w.w.to_vec()].concat();
// A CCCS relation is satisfied if the q(x) multivariate polynomial evaluates to zero in
// the hypercube, evaluating over the whole boolean hypercube for a normal-sized instance
// would take too much, this checks the CCS relation of the CCCS.
ccs.check_relation(&z)?;
Ok(())
is_zero_vec(&e).then_some(()).ok_or(Error::NotSatisfied)
}
}
@ -193,7 +190,8 @@ pub mod tests {
let ccs: CCS<Fr> = get_test_ccs();
let z = get_test_z(3);
ccs.check_relation(&z).unwrap();
let (w, x) = ccs.split_z(&z);
ccs.check_relation(&w, &x).unwrap();
let beta: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();
@ -227,7 +225,8 @@ pub mod tests {
let ccs: CCS<Fr> = get_test_ccs();
let z = get_test_z(3);
ccs.check_relation(&z).unwrap();
let (w, x) = ccs.split_z(&z);
ccs.check_relation(&w, &x).unwrap();
// Now test that if we create Q(x) with eq(d,y) where d is inside the hypercube, \sum Q(x) should be G(d) which
// should be equal to q(d), since G(x) interpolates q(x) inside the hypercube

+ 21
- 20
folding-schemes/src/folding/hypernova/circuits.rs

@ -42,7 +42,7 @@ use crate::folding::{
CF1, CF2,
},
nova::get_r1cs_from_cs,
traits::CommittedInstanceVarOps,
traits::{CommittedInstanceVarOps, Dummy},
};
use crate::frontend::FCircuit;
use crate::utils::virtual_polynomial::VPAuxInfo;
@ -602,13 +602,13 @@ where
/// feed in as parameter for the AugmentedFCircuit::empty method to avoid computing them there.
pub fn upper_bound_ccs(&self) -> Result<CCS<C1::ScalarField>, Error> {
let r1cs = get_r1cs_from_cs::<CF1<C1>>(self.clone()).unwrap();
let mut ccs = CCS::from_r1cs(r1cs.clone());
let mut ccs = CCS::from(r1cs);
let z_0 = vec![C1::ScalarField::zero(); self.F.state_len()];
let mut W_i = Witness::<C1::ScalarField>::dummy(&ccs);
let mut U_i = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s);
let mut U_i = LCCCS::<C1>::dummy(&ccs);
let mut w_i = W_i.clone();
let mut u_i = CCCS::<C1>::dummy(ccs.l);
let mut u_i = CCCS::<C1>::dummy(&ccs);
let n_iters = 2;
for _ in 0..n_iters {
@ -674,7 +674,7 @@ where
r_w: C1::ScalarField::one(),
};
W_i = Witness::<C1::ScalarField>::dummy(&ccs);
U_i = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s);
U_i = LCCCS::<C1>::dummy(&ccs);
}
Ok(ccs)
@ -691,7 +691,7 @@ where
cs.finalize();
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
let ccs = CCS::from_r1cs(r1cs.clone());
let ccs = CCS::from(r1cs);
Ok((cs, ccs))
}
@ -734,8 +734,8 @@ where
.unwrap_or(vec![CF1::<C1>::zero(); self.F.external_inputs_len()]))
})?;
let U_dummy = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
let u_dummy = CCCS::<C1>::dummy(self.ccs.l);
let U_dummy = LCCCS::<C1>::dummy(&self.ccs);
let u_dummy = CCCS::<C1>::dummy(&self.ccs);
let U_i =
LCCCSVar::<C1>::new_witness(cs.clone(), || Ok(self.U_i.unwrap_or(U_dummy.clone())))?;
@ -748,7 +748,7 @@ where
let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || {
Ok(self.U_i1_C.unwrap_or_else(C1::zero))
})?;
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, MU, NU);
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy((&self.ccs, MU, NU));
let nimfs_proof = ProofVar::<C1>::new_witness(cs.clone(), || {
Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy))
})?;
@ -906,7 +906,8 @@ mod tests {
use crate::{
arith::{
ccs::tests::{get_test_ccs, get_test_z},
r1cs::{extract_w_x, RelaxedR1CS},
r1cs::extract_w_x,
Arith,
},
commitment::{pedersen::Pedersen, CommitmentScheme},
folding::{
@ -1100,7 +1101,7 @@ mod tests {
assert_eq!(folded_lcccs, folded_lcccs_v);
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
ccs.check_relation(&folded_witness, &folded_lcccs).unwrap();
// allocate circuit inputs
let cs = ConstraintSystem::<Fr>::new_ref();
@ -1248,13 +1249,13 @@ mod tests {
// prepare the dummy instances
let W_dummy = Witness::<Fr>::dummy(&ccs);
let U_dummy = LCCCS::<Projective>::dummy(ccs.l, ccs.t, ccs.s);
let U_dummy = LCCCS::<Projective>::dummy(&ccs);
let w_dummy = W_dummy.clone();
let u_dummy = CCCS::<Projective>::dummy(ccs.l);
let u_dummy = CCCS::<Projective>::dummy(&ccs);
let (cf_W_dummy, cf_U_dummy): (
CycleFoldWitness<Projective2>,
CycleFoldCommittedInstance<Projective2>,
) = cf_r1cs.dummy_running_instance();
) = cf_r1cs.dummy_witness_instance();
// set the initial dummy instances
let mut W_i = W_dummy.clone();
@ -1289,7 +1290,7 @@ mod tests {
if i == 0 {
W_i1 = Witness::<Fr>::dummy(&ccs);
U_i1 = LCCCS::dummy(ccs.l, ccs.t, ccs.s);
U_i1 = LCCCS::dummy(&ccs);
let u_i1_x = U_i1.hash(&sponge, pp_hash, Fr::one(), &z_0, &z_i1);
@ -1346,7 +1347,7 @@ mod tests {
.unwrap();
// sanity check: check the folded instance relation
U_i1.check_relation(&ccs, &W_i1).unwrap();
ccs.check_relation(&W_i1, &U_i1).unwrap();
let u_i1_x = U_i1.hash(&sponge, pp_hash, iFr + Fr::one(), &z_0, &z_i1);
@ -1465,7 +1466,7 @@ mod tests {
(u_i, w_i) = ccs
.to_cccs::<_, _, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &r1cs_z)
.unwrap();
u_i.check_relation(&ccs, &w_i).unwrap();
ccs.check_relation(&w_i, &u_i).unwrap();
// sanity checks
assert_eq!(w_i.w, r1cs_w_i1);
@ -1486,12 +1487,12 @@ mod tests {
W_i = W_i1.clone();
// check the new LCCCS instance relation
U_i.check_relation(&ccs, &W_i).unwrap();
ccs.check_relation(&W_i, &U_i).unwrap();
// check the new CCCS instance relation
u_i.check_relation(&ccs, &w_i).unwrap();
ccs.check_relation(&w_i, &u_i).unwrap();
// check the CycleFold instance relation
cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i).unwrap();
cf_r1cs.check_relation(&cf_W_i, &cf_U_i).unwrap();
println!("augmented_f_circuit step {}: {:?}", i, start.elapsed());
}

+ 9
- 10
folding-schemes/src/folding/hypernova/decider_eth_circuit.rs

@ -38,8 +38,10 @@ use crate::utils::{
vec::poly_from_vec,
};
use crate::Error;
use crate::{arith::ccs::CCS, folding::traits::CommittedInstanceVarOps};
use crate::{arith::r1cs::R1CS, folding::traits::WitnessVarOps};
use crate::{
arith::{ccs::CCS, r1cs::R1CS},
folding::traits::{CommittedInstanceVarOps, Dummy, WitnessVarOps},
};
/// In-circuit representation of the Witness associated to the CommittedInstance.
#[derive(Debug, Clone)]
@ -292,8 +294,8 @@ where
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let U_dummy_native = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
let u_dummy_native = CCCS::<C1>::dummy(self.ccs.l);
let U_dummy_native = LCCCS::<C1>::dummy(&self.ccs);
let u_dummy_native = CCCS::<C1>::dummy(&self.ccs);
let w_dummy_native = Witness::<C1::ScalarField>::new(
vec![C1::ScalarField::zero(); self.ccs.n - 3 /* (3=2+1, since u_i.x.len=2) */],
);
@ -311,7 +313,7 @@ where
let W_i1 = WitnessVar::<C1::ScalarField>::new_witness(cs.clone(), || {
Ok(self.W_i1.unwrap_or(w_dummy_native.clone()))
})?;
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, 1, 1); // mu=1 & nu=1 because the last fold is 2-to-1
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy((&self.ccs, 1, 1)); // mu=1 & nu=1 because the last fold is 2-to-1
let nimfs_proof = NIMFSProofVar::<C1>::new_witness(cs.clone(), || {
Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy))
})?;
@ -373,10 +375,7 @@ where
let cf_u_dummy_native =
CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
let cf_w_dummy_native = CycleFoldWitness::<C2>::dummy(
self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l,
self.cf_E_len,
);
let cf_w_dummy_native = CycleFoldWitness::<C2>::dummy(&self.cf_r1cs);
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;
@ -527,7 +526,7 @@ pub mod tests {
let n_rows = 2_u32.pow(5) as usize;
let n_cols = 2_u32.pow(5) as usize;
let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
let ccs = CCS::from_r1cs(r1cs);
let ccs = CCS::from(r1cs);
let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
let (pedersen_params, _) =

+ 33
- 30
folding-schemes/src/folding/hypernova/lcccs.rs

@ -11,8 +11,10 @@ use ark_std::Zero;
use super::circuits::LCCCSVar;
use super::Witness;
use crate::arith::ccs::CCS;
use crate::arith::Arith;
use crate::commitment::CommitmentScheme;
use crate::folding::traits::CommittedInstanceOps;
use crate::folding::circuits::CF1;
use crate::folding::traits::{CommittedInstanceOps, Dummy};
use crate::transcript::AbsorbNonNative;
use crate::utils::mle::dense_vec_to_dense_mle;
use crate::utils::vec::mat_vec_mul;
@ -80,42 +82,41 @@ impl CCS {
}
}
impl<C: CurveGroup> LCCCS<C> {
pub fn dummy(l: usize, t: usize, s: usize) -> LCCCS<C>
where
C::ScalarField: PrimeField,
{
LCCCS::<C> {
impl<C: CurveGroup> Dummy<&CCS<CF1<C>>> for LCCCS<C> {
fn dummy(ccs: &CCS<CF1<C>>) -> Self {
Self {
C: C::zero(),
u: C::ScalarField::zero(),
x: vec![C::ScalarField::zero(); l],
r_x: vec![C::ScalarField::zero(); s],
v: vec![C::ScalarField::zero(); t],
u: CF1::<C>::zero(),
x: vec![CF1::<C>::zero(); ccs.l],
r_x: vec![CF1::<C>::zero(); ccs.s],
v: vec![CF1::<C>::zero(); ccs.t],
}
}
}
impl<C: CurveGroup> Arith<Witness<CF1<C>>, LCCCS<C>> for CCS<CF1<C>> {
type Evaluation = Vec<CF1<C>>;
/// Perform the check of the LCCCS instance described at section 4.2,
/// notice that this method does not check the commitment correctness
pub fn check_relation(
&self,
ccs: &CCS<C::ScalarField>,
w: &Witness<C::ScalarField>,
) -> Result<(), Error> {
// check CCS relation
let z: Vec<C::ScalarField> = [vec![self.u], self.x.clone(), w.w.to_vec()].concat();
fn eval_relation(&self, w: &Witness<CF1<C>>, u: &LCCCS<C>) -> Result<Self::Evaluation, Error> {
let z = [&[u.u][..], &u.x, &w.w].concat();
let computed_v: Vec<C::ScalarField> = ccs
.M
self.M
.iter()
.map(|M_j| {
let Mz_mle = dense_vec_to_dense_mle(ccs.s, &mat_vec_mul(M_j, &z)?);
Mz_mle.evaluate(&self.r_x).ok_or(Error::EvaluationFail)
let Mz_mle = dense_vec_to_dense_mle(self.s, &mat_vec_mul(M_j, &z)?);
Mz_mle.evaluate(&u.r_x).ok_or(Error::EvaluationFail)
})
.collect::<Result<_, Error>>()?;
if computed_v != self.v {
return Err(Error::NotSatisfied);
}
Ok(())
.collect()
}
fn check_evaluation(
_w: &Witness<CF1<C>>,
u: &LCCCS<C>,
e: Self::Evaluation,
) -> Result<(), Error> {
(u.v == e).then_some(()).ok_or(Error::NotSatisfied)
}
}
@ -203,7 +204,7 @@ pub mod tests {
let n_rows = 2_u32.pow(5) as usize;
let n_cols = 2_u32.pow(5) as usize;
let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
let ccs = CCS::from_r1cs(r1cs);
let ccs = CCS::from(r1cs);
let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
let (pedersen_params, _) =
@ -237,12 +238,14 @@ pub mod tests {
let ccs = get_test_ccs();
let z = get_test_z(3);
ccs.check_relation(&z.clone()).unwrap();
let (w, x) = ccs.split_z(&z);
ccs.check_relation(&w, &x).unwrap();
// Mutate z so that the relation does not hold
let mut bad_z = z.clone();
bad_z[3] = Fr::zero();
assert!(ccs.check_relation(&bad_z.clone()).is_err());
let (bad_w, bad_x) = ccs.split_z(&bad_z);
assert!(ccs.check_relation(&bad_w, &bad_x).is_err());
let (pedersen_params, _) =
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();

+ 32
- 26
folding-schemes/src/folding/hypernova/mod.rs

@ -24,24 +24,27 @@ use decider_eth_circuit::WitnessVar;
use lcccs::LCCCS;
use nimfs::NIMFS;
use crate::commitment::CommitmentScheme;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::circuits::{
cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
use crate::folding::{
circuits::{
cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
},
CF2,
},
CF2,
nova::{get_r1cs_from_cs, PreprocessorParam},
traits::{CommittedInstanceOps, Dummy, WitnessOps},
};
use crate::folding::nova::{get_r1cs_from_cs, PreprocessorParam};
use crate::folding::traits::{CommittedInstanceOps, WitnessOps};
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,
r1cs::{extract_w_x, R1CS},
Arith,
},
FoldingScheme, MultiFolding,
};
@ -78,8 +81,11 @@ impl Witness {
// always.
Self { w, r_w: F::zero() }
}
pub fn dummy(ccs: &CCS<F>) -> Self {
Witness::<F>::new(vec![F::zero(); ccs.n - ccs.l - 1])
}
impl<F: PrimeField> Dummy<&CCS<F>> for Witness<F> {
fn dummy(ccs: &CCS<F>) -> Self {
Self::new(vec![F::zero(); ccs.n - ccs.l - 1])
}
}
@ -251,7 +257,7 @@ where
.to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
#[cfg(test)]
U_i.check_relation(&self.ccs, &W_i)?;
self.ccs.check_relation(&W_i, &U_i)?;
Ok((U_i, W_i))
}
@ -273,7 +279,7 @@ where
.to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
#[cfg(test)]
u_i.check_relation(&self.ccs, &w_i)?;
self.ccs.check_relation(&w_i, &u_i)?;
Ok((u_i, w_i))
}
@ -305,10 +311,10 @@ where
external_inputs: Vec<C1::ScalarField>,
) -> Result<Vec<C1::ScalarField>, Error> {
// prepare the initial dummy instances
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 U_i = LCCCS::<C1>::dummy(&self.ccs);
let mut u_i = CCCS::<C1>::dummy(&self.ccs);
let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
self.cf_r1cs.dummy_running_instance();
self.cf_r1cs.dummy_witness_instance();
let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
@ -329,7 +335,7 @@ where
.step_native(0, state.clone(), external_inputs.clone())?;
// compute u_{i+1}.x
let U_i1 = LCCCS::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
let U_i1 = LCCCS::dummy(&self.ccs);
let u_i1_x = U_i1.hash(
&sponge,
self.pp_hash,
@ -498,11 +504,11 @@ where
// setup the dummy instances
let W_dummy = Witness::<C1::ScalarField>::dummy(&ccs);
let U_dummy = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s);
let U_dummy = LCCCS::<C1>::dummy(&ccs);
let w_dummy = W_dummy.clone();
let mut u_dummy = CCCS::<C1>::dummy(ccs.l);
let mut u_dummy = CCCS::<C1>::dummy(&ccs);
let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
cf_r1cs.dummy_running_instance();
cf_r1cs.dummy_witness_instance();
u_dummy.x = vec![
U_dummy.hash(&sponge, pp_hash, C1::ScalarField::zero(), &z_0, &z_0),
cf_U_dummy.hash_cyclefold(&sponge, pp_hash),
@ -641,7 +647,7 @@ where
if self.i == C1::ScalarField::zero() {
W_i1 = Witness::<C1::ScalarField>::dummy(&self.ccs);
W_i1.r_w = self.W_i.r_w;
U_i1 = LCCCS::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
U_i1 = LCCCS::dummy(&self.ccs);
let u_i1_x = U_i1.hash(
&sponge,
@ -697,7 +703,7 @@ where
// sanity check: check the folded instance relation
#[cfg(test)]
U_i1.check_relation(&self.ccs, &W_i1)?;
self.ccs.check_relation(&W_i1, &U_i1)?;
let u_i1_x = U_i1.hash(
&sponge,
@ -831,9 +837,9 @@ where
#[cfg(test)]
{
// check the new LCCCS instance relation
self.U_i.check_relation(&self.ccs, &self.W_i)?;
self.ccs.check_relation(&self.W_i, &self.U_i)?;
// check the new CCCS instance relation
self.u_i.check_relation(&self.ccs, &self.w_i)?;
self.ccs.check_relation(&self.w_i, &self.u_i)?;
}
Ok(())
@ -899,12 +905,12 @@ where
}
// check LCCCS satisfiability
U_i.check_relation(&vp.ccs, &W_i)?;
vp.ccs.check_relation(&W_i, &U_i)?;
// check CCCS satisfiability
u_i.check_relation(&vp.ccs, &w_i)?;
vp.ccs.check_relation(&w_i, &u_i)?;
// check CycleFold's RelaxedR1CS satisfiability
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?;
Ok(())
}

+ 15
- 11
folding-schemes/src/folding/hypernova/nimfs.rs

@ -13,6 +13,8 @@ use super::{
};
use crate::arith::ccs::CCS;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::circuits::CF1;
use crate::folding::traits::Dummy;
use crate::transcript::Transcript;
use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage};
use crate::utils::sum_check::{IOPSumCheck, SumCheck};
@ -29,8 +31,8 @@ pub struct NIMFSProof {
pub sigmas_thetas: SigmasThetas<C::ScalarField>,
}
impl<C: CurveGroup> NIMFSProof<C> {
pub fn dummy(ccs: &CCS<C::ScalarField>, mu: usize, nu: usize) -> Self {
impl<C: CurveGroup> Dummy<(&CCS<CF1<C>>, usize, usize)> for NIMFSProof<C> {
fn dummy((ccs, mu, nu): (&CCS<CF1<C>>, usize, usize)) -> Self {
// use 'C::ScalarField::one()' instead of 'zero()' to enforce the NIMFSProof to have the
// same in-circuit representation to match the number of constraints of an actual proof.
NIMFSProof::<C> {
@ -410,8 +412,10 @@ pub mod tests {
let ccs = get_test_ccs();
let z1 = get_test_z::<Fr>(3);
let z2 = get_test_z::<Fr>(4);
ccs.check_relation(&z1).unwrap();
ccs.check_relation(&z2).unwrap();
let (w1, x1) = ccs.split_z(&z1);
let (w2, x2) = ccs.split_z(&z2);
ccs.check_relation(&w1, &x1).unwrap();
ccs.check_relation(&w2, &x2).unwrap();
let mut rng = test_rng();
let r_x_prime: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();
@ -429,8 +433,8 @@ pub mod tests {
.to_cccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z2)
.unwrap();
lcccs.check_relation(&ccs, &w1).unwrap();
cccs.check_relation(&ccs, &w2).unwrap();
ccs.check_relation(&w1, &lcccs).unwrap();
ccs.check_relation(&w2, &cccs).unwrap();
let mut rng = test_rng();
let rho = Fr::rand(&mut rng);
@ -446,7 +450,7 @@ pub mod tests {
let w_folded = NIMFS::<Projective, PoseidonSponge<Fr>>::fold_witness(&[w1], &[w2], rho);
// check lcccs relation
folded.check_relation(&ccs, &w_folded).unwrap();
ccs.check_relation(&w_folded, &folded).unwrap();
}
/// Perform multifolding of an LCCCS instance with a CCCS instance (as described in the paper)
@ -506,7 +510,7 @@ pub mod tests {
assert_eq!(folded_lcccs, folded_lcccs_v);
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
ccs.check_relation(&folded_witness, &folded_lcccs).unwrap();
}
/// Perform multiple steps of multifolding of an LCCCS instance with a CCCS instance
@ -566,7 +570,7 @@ pub mod tests {
assert_eq!(folded_lcccs, folded_lcccs_v);
// check that the folded instance with the folded witness holds the LCCCS relation
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
ccs.check_relation(&folded_witness, &folded_lcccs).unwrap();
running_instance = folded_lcccs;
w1 = folded_witness;
@ -652,7 +656,7 @@ pub mod tests {
assert_eq!(folded_lcccs, folded_lcccs_v);
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
ccs.check_relation(&folded_witness, &folded_lcccs).unwrap();
}
/// Test that generates mu>1 and nu>1 instances, and folds them in a single multifolding step
@ -740,7 +744,7 @@ pub mod tests {
assert_eq!(folded_lcccs, folded_lcccs_v);
// Check that the folded LCCCS instance is a valid instance with respect to the folded witness
folded_lcccs.check_relation(&ccs, &folded_witness).unwrap();
ccs.check_relation(&folded_witness, &folded_lcccs).unwrap();
}
}
}

+ 8
- 4
folding-schemes/src/folding/hypernova/utils.rs

@ -230,8 +230,10 @@ pub mod tests {
let ccs = get_test_ccs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
ccs.check_relation(&z1).unwrap();
ccs.check_relation(&z2).unwrap();
let (w1, x1) = ccs.split_z(&z1);
let (w2, x2) = ccs.split_z(&z2);
ccs.check_relation(&w1, &x1).unwrap();
ccs.check_relation(&w2, &x2).unwrap();
let mut rng = test_rng();
let gamma: Fr = Fr::rand(&mut rng);
@ -282,8 +284,10 @@ pub mod tests {
let ccs: CCS<Fr> = get_test_ccs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
ccs.check_relation(&z1).unwrap();
ccs.check_relation(&z2).unwrap();
let (w1, x1) = ccs.split_z(&z1);
let (w2, x2) = ccs.split_z(&z2);
ccs.check_relation(&w1, &x1).unwrap();
ccs.check_relation(&w2, &x2).unwrap();
let gamma: Fr = Fr::rand(&mut rng);
let beta: Vec<Fr> = (0..ccs.s).map(|_| Fr::rand(&mut rng)).collect();

+ 4
- 1
folding-schemes/src/folding/nova/circuits.rs

@ -31,7 +31,10 @@ use crate::folding::circuits::{
};
use crate::frontend::FCircuit;
use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar};
use crate::{constants::NOVA_N_BITS_RO, folding::traits::CommittedInstanceVarOps};
use crate::{
constants::NOVA_N_BITS_RO,
folding::traits::{CommittedInstanceVarOps, Dummy},
};
/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova
/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are

+ 5
- 9
folding-schemes/src/folding/nova/decider_circuits.rs

@ -40,7 +40,7 @@ use crate::folding::circuits::{
CF1, CF2,
};
use crate::folding::nova::NovaCycleFoldConfig;
use crate::folding::traits::CommittedInstanceVarOps;
use crate::folding::traits::{CommittedInstanceVarOps, Dummy};
use crate::frontend::FCircuit;
use crate::utils::vec::poly_from_vec;
use crate::Error;
@ -214,11 +214,8 @@ where
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let u_dummy_native = CommittedInstance::<C1>::dummy(2);
let w_dummy_native = Witness::<C1>::dummy(
self.r1cs.A.n_cols - 3, /* (3=2+1, since u_i.x.len=2) */
self.E_len,
);
let u_dummy_native = CommittedInstance::<C1>::dummy(&self.r1cs);
let w_dummy_native = Witness::<C1>::dummy(&self.r1cs);
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
@ -436,9 +433,8 @@ where
Ok(self.pp_hash.unwrap_or_else(CF1::<C2>::zero))
})?;
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
let w_dummy_native =
Witness::<C2>::dummy(self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l, self.cf_E_len);
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(&self.cf_r1cs);
let w_dummy_native = Witness::<C2>::dummy(&self.cf_r1cs);
let cf_U_i = CommittedInstanceVar::<C2>::new_input(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;

+ 20
- 28
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -29,6 +29,7 @@ use super::{
nifs::NIFS,
CommittedInstance, Nova, Witness,
};
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
use crate::folding::circuits::{
cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness},
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
@ -41,10 +42,9 @@ use crate::utils::{
vec::poly_from_vec,
};
use crate::Error;
use crate::{arith::r1cs::R1CS, folding::traits::WitnessVarOps};
use crate::{
commitment::{pedersen::Params as PedersenParams, CommitmentScheme},
folding::traits::CommittedInstanceVarOps,
arith::r1cs::R1CS,
folding::traits::{CommittedInstanceVarOps, Dummy, WitnessVarOps},
};
#[derive(Debug, Clone)]
@ -356,11 +356,8 @@ where
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let u_dummy_native = CommittedInstance::<C1>::dummy(2);
let w_dummy_native = Witness::<C1>::dummy(
self.r1cs.A.n_cols - 3, /* (3=2+1, since u_i.x.len=2) */
self.E_len,
);
let u_dummy_native = CommittedInstance::<C1>::dummy(&self.r1cs);
let w_dummy_native = Witness::<C1>::dummy(&self.r1cs);
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
@ -437,10 +434,7 @@ where
let cf_u_dummy_native =
CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
let w_dummy_native = CycleFoldWitness::<C2>::dummy(
self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l,
self.cf_E_len,
);
let w_dummy_native = CycleFoldWitness::<C2>::dummy(&self.cf_r1cs);
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;
@ -608,7 +602,6 @@ pub mod tests {
r1cs::{
extract_r1cs, extract_w_x,
tests::{get_test_r1cs, get_test_z},
RelaxedR1CS,
},
Arith,
};
@ -618,20 +611,18 @@ pub mod tests {
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::FoldingScheme;
fn prepare_instances<C: CurveGroup, CS: CommitmentScheme<C>, R: Rng>(
// Convert `z` to a witness-instance pair for the relaxed R1CS
fn prepare_relaxed_witness_instance<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,
{
) -> (Witness<C>, CommittedInstance<C>) {
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();
w.E = r1cs.eval_at_z(z).unwrap();
let mut u = w.commit::<CS, false>(&cs_pp, x).unwrap();
u.u = z[0];
@ -643,9 +634,10 @@ pub mod tests {
let rng = &mut thread_rng();
let r1cs: R1CS<Fr> = get_test_r1cs();
let mut z = get_test_z(3);
z[0] = Fr::rand(rng);
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
z[0] = Fr::rand(rng); // Randomize `z[0]` (i.e. `u.u`) to test the relaxed R1CS
let (w, u) = prepare_relaxed_witness_instance::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
let cs = ConstraintSystem::<Fr>::new_ref();
@ -673,12 +665,11 @@ pub mod tests {
let r1cs = extract_r1cs::<Fr>(&cs);
let (w, x) = extract_w_x::<Fr>(&cs);
let mut z = [vec![Fr::one()], x, w].concat();
r1cs.check_relation(&z).unwrap();
r1cs.check_relation(&w, &x).unwrap();
z[0] = Fr::rand(rng);
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
r1cs.check_relaxed_relation(&w, &u).unwrap();
let z = [vec![Fr::rand(rng)], x, w].concat();
let (w, u) = prepare_relaxed_witness_instance::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
r1cs.check_relation(&w, &u).unwrap();
// set new CS for the circuit that checks the RelaxedR1CS of our original circuit
let cs = ConstraintSystem::<Fr>::new_ref();
@ -767,9 +758,10 @@ 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::rand(rng)], x, w].concat();
let (w, u) = prepare_instances::<_, Pedersen<Projective2>, _>(rng, &r1cs, &z);
let z = [vec![Fq::rand(rng)], x, w].concat();
let (w, u) =
prepare_relaxed_witness_instance::<_, Pedersen<Projective2>, _>(rng, &r1cs, &z);
// natively
let cs = ConstraintSystem::<Fq>::new_ref();

+ 43
- 31
folding-schemes/src/folding/nova/mod.rs

@ -19,18 +19,21 @@ use crate::folding::circuits::cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
};
use crate::folding::circuits::CF2;
use crate::folding::{
circuits::{CF1, CF2},
traits::Dummy,
};
use crate::frontend::FCircuit;
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,
utils::{get_cm_coordinates, pp_hash},
};
use crate::{arith::Arith, commitment::CommitmentScheme};
use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar};
use nifs::NIFS;
@ -76,17 +79,23 @@ pub struct CommittedInstance {
pub x: Vec<C::ScalarField>,
}
impl<C: CurveGroup> CommittedInstance<C> {
pub fn dummy(io_len: usize) -> Self {
impl<C: CurveGroup> Dummy<usize> for CommittedInstance<C> {
fn dummy(io_len: usize) -> Self {
Self {
cmE: C::zero(),
u: C::ScalarField::zero(),
u: CF1::<C>::zero(),
cmW: C::zero(),
x: vec![C::ScalarField::zero(); io_len],
x: vec![CF1::<C>::zero(); io_len],
}
}
}
impl<C: CurveGroup> Dummy<&R1CS<CF1<C>>> for CommittedInstance<C> {
fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
Self::dummy(r1cs.l)
}
}
impl<C: CurveGroup> Absorb for CommittedInstance<C>
where
C::ScalarField: Absorb,
@ -149,18 +158,6 @@ impl Witness {
}
}
pub fn dummy(w_len: usize, e_len: usize) -> Self {
let (rW, rE) = (C::ScalarField::zero(), C::ScalarField::zero());
let w = vec![C::ScalarField::zero(); w_len];
Self {
E: vec![C::ScalarField::zero(); e_len],
rE,
W: w,
rW,
}
}
pub fn commit<CS: CommitmentScheme<C, HC>, const HC: bool>(
&self,
params: &CS::ProverParams,
@ -180,6 +177,17 @@ impl Witness {
}
}
impl<C: CurveGroup> Dummy<&R1CS<CF1<C>>> for Witness<C> {
fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
Self {
E: vec![C::ScalarField::zero(); r1cs.A.n_rows],
rE: C::ScalarField::zero(),
W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l],
rW: C::ScalarField::zero(),
}
}
}
impl<C: CurveGroup> WitnessOps<C::ScalarField> for Witness<C> {
type Var = WitnessVar<C>;
@ -562,9 +570,9 @@ where
let pp_hash = vp.pp_hash()?;
// setup the dummy instances
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();
let (W_dummy, U_dummy) = r1cs.dummy_witness_instance();
let (w_dummy, u_dummy) = r1cs.dummy_witness_instance();
let (cf_W_dummy, cf_U_dummy) = cf_r1cs.dummy_witness_instance();
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
// R1CS that we're working with.
@ -815,10 +823,11 @@ where
#[cfg(test)]
{
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_relation(&self.cf_W_i, &self.cf_U_i)?;
cfW_u_i.check_incoming()?;
cfE_u_i.check_incoming()?;
self.cf_r1cs.check_relation(&_cfW_w_i, &cfW_u_i)?;
self.cf_r1cs.check_relation(&_cfE_w_i, &cfE_u_i)?;
self.cf_r1cs.check_relation(&self.cf_W_i, &self.cf_U_i)?;
}
}
@ -850,8 +859,9 @@ where
#[cfg(test)]
{
self.r1cs.check_tight_relation(&self.w_i, &self.u_i)?;
self.r1cs.check_relaxed_relation(&self.W_i, &self.U_i)?;
self.u_i.check_incoming()?;
self.r1cs.check_relation(&self.w_i, &self.u_i)?;
self.r1cs.check_relation(&self.W_i, &self.U_i)?;
}
Ok(())
@ -917,13 +927,15 @@ where
return Err(Error::IVCVerificationFail);
}
// check R1CS satisfiability, which also enforces u_i.cmE==0, u_i.u==1
vp.r1cs.check_tight_relation(&w_i, &u_i)?;
// check R1CS satisfiability, which is equivalent to checking if `u_i`
// is an incoming instance and if `w_i` and `u_i` satisfy RelaxedR1CS
u_i.check_incoming()?;
vp.r1cs.check_relation(&w_i, &u_i)?;
// check RelaxedR1CS satisfiability
vp.r1cs.check_relaxed_relation(&W_i, &U_i)?;
vp.r1cs.check_relation(&W_i, &U_i)?;
// check CycleFold RelaxedR1CS satisfiability
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?;
Ok(())
}

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

@ -210,13 +210,16 @@ 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},
RelaxedR1CS,
};
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
use crate::folding::nova::circuits::ChallengeGadget;
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::{
arith::{
r1cs::tests::{get_test_r1cs, get_test_z},
Arith,
},
folding::traits::Dummy,
};
#[allow(clippy::type_complexity)]
pub(crate) fn prepare_simple_fold_inputs<C>() -> (
@ -302,13 +305,13 @@ pub mod tests {
fn test_nifs_fold_dummy() {
let r1cs = get_test_r1cs::<Fr>();
let z1 = get_test_z(3);
let (w1, x1) = r1cs.split_z(&z1);
let (_, x1) = r1cs.split_z(&z1);
let mut rng = ark_std::test_rng();
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// dummy instance, witness and public inputs zeroes
let w_dummy = Witness::<Projective>::dummy(w1.len(), r1cs.A.n_rows);
let w_dummy = Witness::<Projective>::dummy(&r1cs);
let mut u_dummy = w_dummy
.commit::<Pedersen<Projective>, false>(&pedersen_params, vec![Fr::zero(); x1.len()])
.unwrap();
@ -318,8 +321,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_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_relation(&W_i, &U_i).unwrap();
r1cs.check_relation(&w_i, &u_i).unwrap();
r1cs.check_relation(&W_i, &U_i).unwrap();
let r_Fr = Fr::from(3_u32);
@ -336,7 +339,7 @@ pub mod tests {
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
)
.unwrap();
r1cs.check_relaxed_relation(&W_i1, &U_i1).unwrap();
r1cs.check_relation(&W_i1, &U_i1).unwrap();
}
// fold 2 instances into one
@ -350,9 +353,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_relation(&w1, &ci1).unwrap();
r1cs.check_relaxed_relation(&w2, &ci2).unwrap();
r1cs.check_relaxed_relation(&w3, &ci3).unwrap();
r1cs.check_relation(&w1, &ci1).unwrap();
r1cs.check_relation(&w2, &ci2).unwrap();
r1cs.check_relation(&w3, &ci3).unwrap();
// check that folded commitments from folded instance (ci) are equal to folding the
// use folded rE, rW to commit w3
@ -427,7 +430,7 @@ pub mod tests {
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap();
r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance)
r1cs.check_relation(&running_instance_w, &running_committed_instance)
.unwrap();
let num_iters = 10;
@ -440,7 +443,7 @@ pub mod tests {
let incoming_committed_instance = incoming_instance_w
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap();
r1cs.check_relaxed_relation(&incoming_instance_w, &incoming_committed_instance)
r1cs.check_relation(&incoming_instance_w, &incoming_committed_instance)
.unwrap();
let r = Fr::rand(&mut rng); // folding challenge would come from the RO
@ -474,7 +477,7 @@ pub mod tests {
&cmT,
);
r1cs.check_relaxed_relation(&folded_w, &folded_committed_instance)
r1cs.check_relation(&folded_w, &folded_committed_instance)
.unwrap();
// set running_instance for next loop iteration

+ 49
- 42
folding-schemes/src/folding/nova/traits.rs

@ -1,50 +1,66 @@
use ark_ec::CurveGroup;
use ark_std::{rand::RngCore, One, UniformRand};
use ark_std::{rand::RngCore, UniformRand};
use super::{CommittedInstance, Witness};
use crate::arith::r1cs::{RelaxedR1CS, R1CS};
use crate::arith::ArithSampler;
use crate::arith::{r1cs::R1CS, Arith};
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::CF1;
use crate::Error;
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)
}
fn dummy_incoming_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
self.dummy_running_instance()
}
/// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and
/// the committed instance is of type [`CommittedInstance`].
///
/// Due to the error terms `Witness.E` and `CommittedInstance.u`, R1CS here is
/// considered as a relaxed R1CS.
///
/// One may wonder why we do not provide distinct structs for R1CS and relaxed
/// R1CS.
/// This is because both plain R1CS and relaxed R1CS have the same structure:
/// they are both represented by three matrices.
/// What makes them different is the error terms, which are not part of the R1CS
/// struct, but are part of the witness and committed instance.
///
/// As a follow-up, one may further ask why not providing a trait for relaxed
/// R1CS and implement it for the `R1CS` struct, where the relaxed R1CS trait
/// has methods for relaxed satisfiability check, while the `Arith` trait that
/// `R1CS` implements has methods for plain satisfiability check.
/// However, it would be more ideal if we have a single method that can smartly
/// choose the type of satisfiability check, which would make the code more
/// generic and easier to maintain.
///
/// This is achieved thanks to the new design of the [`Arith`] trait, where we
/// can implement the trait for the same constraint system with different types
/// of witnesses and committed instances.
/// For R1CS, whether it is relaxed or not is now determined by the types of `W`
/// and `U`: the satisfiability check is relaxed if `W` and `U` are defined by
/// folding schemes, and plain if they are vectors of field elements.
impl<C: CurveGroup> Arith<Witness<C>, CommittedInstance<C>> for R1CS<CF1<C>> {
type Evaluation = Vec<CF1<C>>;
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 eval_relation(
&self,
w: &Witness<C>,
u: &CommittedInstance<C>,
) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat())
}
fn check_error_terms(
fn check_evaluation(
w: &Witness<C>,
_u: &CommittedInstance<C>,
e: Vec<C::ScalarField>,
e: Self::Evaluation,
) -> Result<(), Error> {
if w.E == e {
Ok(())
} else {
Err(Error::NotSatisfied)
}
(w.E == e).then_some(()).ok_or(Error::NotSatisfied)
}
}
fn sample<CS>(
impl<C: CurveGroup> ArithSampler<C, Witness<C>, CommittedInstance<C>> for R1CS<CF1<C>> {
fn sample_witness_instance<CS: CommitmentScheme<C, true>>(
&self,
params: &CS::ProverParams,
mut rng: impl RngCore,
) -> Result<(Witness<C>, CommittedInstance<C>), Error>
where
CS: crate::commitment::CommitmentScheme<C, true>,
{
) -> Result<(Witness<C>, CommittedInstance<C>), Error> {
// Implements sampling a (committed) RelaxedR1CS
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
let u = C::ScalarField::rand(&mut rng);
@ -61,16 +77,7 @@ impl RelaxedR1CS, CommittedInstance> for R1CS
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 E = self.eval_at_z(&z)?;
let witness = Witness { E, rE, W, rW };
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
@ -79,7 +86,7 @@ impl RelaxedR1CS, CommittedInstance> for R1CS
cm_witness.u = u;
debug_assert!(
self.check_relaxed_relation(&witness, &cm_witness).is_ok(),
self.check_relation(&witness, &cm_witness).is_ok(),
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
u,
witness.E

+ 8
- 6
folding-schemes/src/folding/nova/zk.rs

@ -35,7 +35,7 @@ use ark_ff::{BigInteger, PrimeField};
use ark_std::{One, Zero};
use crate::{
arith::r1cs::{RelaxedR1CS, R1CS},
arith::{r1cs::R1CS, Arith, ArithSampler},
folding::traits::CommittedInstanceOps,
RngCore,
};
@ -142,7 +142,9 @@ where
let pi = FoldingProof { cmT };
// 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)?;
let (W_r, U_r) = nova
.r1cs
.sample_witness_instance::<CS1>(&nova.cs_pp, &mut rng)?;
// 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
// a. Compute T
@ -279,10 +281,10 @@ where
);
// 5. Check that W^{\prime}_i is a satisfying witness
r1cs.check_relaxed_relation(&proof.W_i_prime, &U_i_prime)?;
r1cs.check_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_relation(&proof.cf_W_i, &proof.cf_U_i)?;
cf_r1cs.check_relation(&proof.cf_W_i, &proof.cf_U_i)?;
Ok(())
}
@ -370,7 +372,7 @@ pub mod tests {
);
let (_, sampled_committed_instance) = nova
.r1cs
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample_witness_instance::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap();
// proof verification fails with incorrect running instance
@ -407,7 +409,7 @@ pub mod tests {
);
let (sampled_committed_witness, _) = nova
.r1cs
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample_witness_instance::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap();
// proof generation fails with incorrect running witness

+ 10
- 10
folding-schemes/src/folding/protogalaxy/circuits.rs

@ -33,7 +33,7 @@ use crate::{
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
CF1, CF2,
},
traits::CommittedInstanceVarOps,
traits::{CommittedInstanceVarOps, Dummy},
},
frontend::FCircuit,
transcript::{AbsorbNonNativeGadget, TranscriptVar},
@ -47,13 +47,13 @@ impl FoldingGadget {
pub fn fold_committed_instance<C: CurveGroup, S: CryptographicSponge>(
transcript: &mut impl TranscriptVar<C::ScalarField, S>,
// running instance
instance: &CommittedInstanceVar<C>,
instance: &CommittedInstanceVar<C, true>,
// incoming instances
vec_instances: &[CommittedInstanceVar<C>],
vec_instances: &[CommittedInstanceVar<C, false>],
// polys from P
F_coeffs: Vec<FpVar<C::ScalarField>>,
K_coeffs: Vec<FpVar<C::ScalarField>>,
) -> Result<(CommittedInstanceVar<C>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
) -> Result<(CommittedInstanceVar<C, true>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
let t = instance.betas.len();
// absorb the committed instances
@ -135,13 +135,13 @@ 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: CommittedInstanceVar<C, true>,
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> {
) -> Result<(CommittedInstanceVar<C, true>, Vec<FpVar<CF1<C>>>), SynthesisError> {
assert_eq!(u_phis.len(), u_xs.len());
// Prepare the incoming instances.
@ -250,7 +250,7 @@ pub struct AugmentedFCircuit<
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_i: CommittedInstance<C1, true>,
pub(super) U_i1_phi: C1,
pub(super) F_coeffs: Vec<CF1<C1>>,
pub(super) K_coeffs: Vec<CF1<C1>>,
@ -278,7 +278,7 @@ where
d: usize,
k: usize,
) -> Self {
let u_dummy = CommittedInstance::dummy_running(2, t);
let u_dummy = CommittedInstance::dummy((2, t));
let cf_u_dummy =
CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
@ -327,8 +327,8 @@ where
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_dummy = CommittedInstance::<C1, true>::dummy((2, self.U_i.betas.len()));
let U_i = CommittedInstanceVar::<C1, true>::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 =

+ 4
- 0
folding-schemes/src/folding/protogalaxy/constants.rs

@ -0,0 +1,4 @@
/// `RUNNING` indicates that the committed instance is a running instance.
pub const RUNNING: bool = true;
/// `INCOMING` indicates that the committed instance is an incoming instance.
pub const INCOMING: bool = false;

+ 29
- 27
folding-schemes/src/folding/protogalaxy/folding.rs

@ -14,9 +14,7 @@ use super::utils::{all_powers, betas_star, exponential_powers, pow_i};
use super::ProtoGalaxyError;
use super::{CommittedInstance, Witness};
#[cfg(test)]
use crate::arith::r1cs::RelaxedR1CS;
use crate::arith::{r1cs::R1CS, Arith};
use crate::arith::r1cs::R1CS;
use crate::transcript::Transcript;
use crate::utils::vec::*;
use crate::Error;
@ -38,14 +36,14 @@ where
transcript: &mut impl Transcript<C::ScalarField>,
r1cs: &R1CS<C::ScalarField>,
// running instance
instance: &CommittedInstance<C>,
instance: &CommittedInstance<C, true>,
w: &Witness<C::ScalarField>,
// incoming instances
vec_instances: &[CommittedInstance<C>],
vec_instances: &[CommittedInstance<C, false>],
vec_w: &[Witness<C::ScalarField>],
) -> Result<
(
CommittedInstance<C>,
CommittedInstance<C, true>,
Witness<C::ScalarField>,
Vec<C::ScalarField>, // F_X coeffs
Vec<C::ScalarField>, // K_X coeffs
@ -97,7 +95,7 @@ where
let delta = transcript.get_challenge();
let deltas = exponential_powers(delta, t);
let mut f_z = r1cs.eval_relation(&z)?;
let mut f_z = r1cs.eval_at_z(&z)?;
if f_z.len() != m {
return Err(Error::NotSameLength(
"number of constraints in R1CS".to_string(),
@ -127,15 +125,18 @@ where
// sanity check: check that the new randomized instance (the original instance but with
// 'refreshed' randomness) satisfies the relation.
#[cfg(test)]
r1cs.check_relaxed_relation(
w,
&CommittedInstance {
phi: instance.phi,
betas: betas_star.clone(),
e: F_alpha,
x: instance.x.clone(),
},
)?;
{
use crate::arith::Arith;
r1cs.check_relation(
w,
&CommittedInstance::<_, true> {
phi: instance.phi,
betas: betas_star.clone(),
e: F_alpha,
x: instance.x.clone(),
},
)?;
}
let zs: Vec<Vec<C::ScalarField>> = std::iter::once(z.clone())
.chain(
@ -178,7 +179,7 @@ where
inner[j] += Lh * zj;
}
}
let f_ev = r1cs.eval_relation(&inner)?;
let f_ev = r1cs.eval_at_z(&inner)?;
G_evals[hi] = cfg_into_iter!(f_ev)
.enumerate()
@ -253,13 +254,13 @@ where
pub fn verify(
transcript: &mut impl Transcript<C::ScalarField>,
// running instance
instance: &CommittedInstance<C>,
instance: &CommittedInstance<C, true>,
// incoming instances
vec_instances: &[CommittedInstance<C>],
vec_instances: &[CommittedInstance<C, false>],
// polys from P
F_coeffs: Vec<C::ScalarField>,
K_coeffs: Vec<C::ScalarField>,
) -> Result<CommittedInstance<C>, Error> {
) -> Result<CommittedInstance<C, true>, Error> {
let t = instance.betas.len();
// absorb the committed instances
@ -395,6 +396,7 @@ pub mod tests {
use ark_std::{rand::Rng, UniformRand};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z_split};
use crate::arith::Arith;
use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
use crate::transcript::poseidon::poseidon_canonical_config;
@ -419,9 +421,9 @@ pub mod tests {
k: usize,
) -> (
Witness<C::ScalarField>,
CommittedInstance<C>,
CommittedInstance<C, true>,
Vec<Witness<C::ScalarField>>,
Vec<CommittedInstance<C>>,
Vec<CommittedInstance<C, false>>,
) {
let mut rng = ark_std::test_rng();
@ -439,7 +441,7 @@ pub mod tests {
r_w: C::ScalarField::zero(),
};
let phi = Pedersen::<C>::commit(&pedersen_params, &witness.w, &witness.r_w).unwrap();
let instance = CommittedInstance::<C> {
let instance = CommittedInstance::<C, true> {
phi,
betas: betas.clone(),
e: C::ScalarField::zero(),
@ -447,7 +449,7 @@ pub mod tests {
};
// same for the other instances
let mut witnesses: Vec<Witness<C::ScalarField>> = Vec::new();
let mut instances: Vec<CommittedInstance<C>> = Vec::new();
let mut instances: Vec<CommittedInstance<C, false>> = Vec::new();
#[allow(clippy::needless_range_loop)]
for _ in 0..k {
let (_, x_i, w_i) = get_test_z_split::<C::ScalarField>(rng.gen::<u16>() as usize);
@ -457,7 +459,7 @@ pub mod tests {
};
let phi_i =
Pedersen::<C>::commit(&pedersen_params, &witness_i.w, &witness_i.r_w).unwrap();
let instance_i = CommittedInstance::<C> {
let instance_i = CommittedInstance::<C, false> {
phi: phi_i,
betas: vec![],
e: C::ScalarField::zero(),
@ -509,7 +511,7 @@ pub mod tests {
assert!(!folded_instance.e.is_zero());
// check that the folded instance satisfies the relation
r1cs.check_relaxed_relation(&folded_witness, &folded_instance)
r1cs.check_relation(&folded_witness, &folded_instance)
.unwrap();
}
@ -558,7 +560,7 @@ pub mod tests {
assert!(!folded_instance.e.is_zero());
// check that the folded instance satisfies the relation
r1cs.check_relaxed_relation(&folded_witness, &folded_instance)
r1cs.check_relation(&folded_witness, &folded_instance)
.unwrap();
running_witness = folded_witness;

+ 89
- 45
folding-schemes/src/folding/protogalaxy/mod.rs

@ -18,10 +18,14 @@ use ark_relations::r1cs::{
use ark_std::{
borrow::Borrow, cmp::max, fmt::Debug, log2, marker::PhantomData, rand::RngCore, One, Zero,
};
use constants::{INCOMING, RUNNING};
use num_bigint::BigUint;
use crate::{
arith::r1cs::{extract_r1cs, extract_w_x, RelaxedR1CS, R1CS},
arith::{
r1cs::{extract_r1cs, extract_w_x, R1CS},
Arith,
},
commitment::CommitmentScheme,
folding::circuits::{
cyclefold::{
@ -37,6 +41,7 @@ use crate::{
};
pub mod circuits;
pub mod constants;
pub mod folding;
pub mod traits;
pub(crate) mod utils;
@ -44,7 +49,9 @@ pub(crate) mod utils;
use circuits::AugmentedFCircuit;
use folding::Folding;
use super::traits::{CommittedInstanceOps, CommittedInstanceVarOps, WitnessOps, WitnessVarOps};
use super::traits::{
CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps,
};
/// Configuration for ProtoGalaxy's CycleFold circuit
pub struct ProtoGalaxyCycleFoldConfig<C: CurveGroup> {
@ -62,51 +69,68 @@ impl CycleFoldConfig for ProtoGalaxyCycleFoldConfig {
/// in ProtoGalaxy instances.
pub type ProtoGalaxyCycleFoldCircuit<C, GC> = CycleFoldCircuit<ProtoGalaxyCycleFoldConfig<C>, GC>;
/// The committed instance of ProtoGalaxy.
///
/// We use `TYPE` to distinguish between incoming and running instances, as
/// they have slightly different structures (e.g., length of `betas`) and
/// behaviors (e.g., in satisfiability checks).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CommittedInstance<C: CurveGroup> {
pub struct CommittedInstance<C: CurveGroup, const TYPE: bool> {
phi: C,
betas: Vec<C::ScalarField>,
e: C::ScalarField,
x: Vec<C::ScalarField>,
}
impl<C: CurveGroup> CommittedInstance<C> {
pub fn dummy_running(io_len: usize, t: usize) -> Self {
impl<C: CurveGroup, const TYPE: bool> Dummy<(usize, usize)> for CommittedInstance<C, TYPE> {
fn dummy((io_len, t): (usize, usize)) -> Self {
if TYPE == INCOMING {
assert_eq!(t, 0);
}
Self {
phi: C::zero(),
betas: vec![C::ScalarField::zero(); t],
e: C::ScalarField::zero(),
x: vec![C::ScalarField::zero(); io_len],
betas: vec![Zero::zero(); t],
e: Zero::zero(),
x: vec![Zero::zero(); io_len],
}
}
}
pub fn dummy_incoming(io_len: usize) -> Self {
Self::dummy_running(io_len, 0)
impl<C: CurveGroup, const TYPE: bool> Dummy<&R1CS<CF1<C>>> for CommittedInstance<C, TYPE> {
fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
let t = if TYPE == RUNNING {
log2(r1cs.num_constraints()) as usize
} else {
0
};
Self::dummy((r1cs.num_public_inputs(), t))
}
}
impl<C: CurveGroup> CommittedInstanceOps<C> for CommittedInstance<C> {
type Var = CommittedInstanceVar<C>;
impl<C: CurveGroup, const TYPE: bool> CommittedInstanceOps<C> for CommittedInstance<C, TYPE> {
type Var = CommittedInstanceVar<C, TYPE>;
fn get_commitments(&self) -> Vec<C> {
vec![self.phi]
}
fn is_incoming(&self) -> bool {
self.e == Zero::zero() && self.betas.is_empty()
TYPE == INCOMING
}
}
#[derive(Clone, Debug)]
pub struct CommittedInstanceVar<C: CurveGroup> {
pub struct CommittedInstanceVar<C: CurveGroup, const TYPE: bool> {
phi: NonNativeAffineVar<C>,
betas: Vec<FpVar<C::ScalarField>>,
e: FpVar<C::ScalarField>,
x: Vec<FpVar<C::ScalarField>>,
}
impl<C: CurveGroup> AllocVar<CommittedInstance<C>, C::ScalarField> for CommittedInstanceVar<C> {
fn new_variable<T: Borrow<CommittedInstance<C>>>(
impl<C: CurveGroup, const TYPE: bool> AllocVar<CommittedInstance<C, TYPE>, C::ScalarField>
for CommittedInstanceVar<C, TYPE>
{
fn new_variable<T: Borrow<CommittedInstance<C, TYPE>>>(
cs: impl Into<Namespace<C::ScalarField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
@ -119,15 +143,19 @@ impl AllocVar, C::ScalarField> for Committed
Ok(Self {
phi: NonNativeAffineVar::new_variable(cs.clone(), || Ok(u.phi), mode)?,
betas: Vec::new_variable(cs.clone(), || Ok(u.betas.clone()), mode)?,
e: FpVar::new_variable(cs.clone(), || Ok(u.e), mode)?,
e: if TYPE == RUNNING {
FpVar::new_variable(cs.clone(), || Ok(u.e), mode)?
} else {
FpVar::zero()
},
x: Vec::new_variable(cs.clone(), || Ok(u.x.clone()), mode)?,
})
})
}
}
impl<C: CurveGroup> R1CSVar<C::ScalarField> for CommittedInstanceVar<C> {
type Value = CommittedInstance<C>;
impl<C: CurveGroup, const TYPE: bool> R1CSVar<C::ScalarField> for CommittedInstanceVar<C, TYPE> {
type Value = CommittedInstance<C, TYPE>;
fn cs(&self) -> ConstraintSystemRef<C::ScalarField> {
self.phi
@ -151,7 +179,7 @@ impl R1CSVar for CommittedInstanceVar {
}
}
impl<C: CurveGroup> CommittedInstanceVarOps<C> for CommittedInstanceVar<C> {
impl<C: CurveGroup, const TYPE: bool> CommittedInstanceVarOps<C> for CommittedInstanceVar<C, TYPE> {
type PointVar = NonNativeAffineVar<C>;
fn get_commitments(&self) -> Vec<Self::PointVar> {
@ -163,11 +191,13 @@ impl CommittedInstanceVarOps for CommittedInstanceVar {
}
fn enforce_incoming(&self) -> Result<(), SynthesisError> {
if self.betas.is_empty() {
self.e.enforce_equal(&FpVar::zero())
} else {
Err(SynthesisError::Unsatisfiable)
}
// We don't need to check if `self` is an incoming instance in-circuit,
// because incoming instances and running instances already have
// different types of `e` (constant vs witness) when we allocate them
// in-circuit.
(TYPE == INCOMING)
.then_some(())
.ok_or(SynthesisError::Unsatisfiable)
}
fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> {
@ -195,9 +225,9 @@ impl Witness {
&self,
params: &CS::ProverParams,
x: Vec<F>,
) -> Result<CommittedInstance<C>, crate::Error> {
) -> Result<CommittedInstance<C, false>, crate::Error> {
let phi = CS::commit(params, &self.w, &self.r_w)?;
Ok(CommittedInstance {
Ok(CommittedInstance::<C, false> {
phi,
x,
e: F::zero(),
@ -206,6 +236,15 @@ impl Witness {
}
}
impl<F: PrimeField> Dummy<&R1CS<F>> for Witness<F> {
fn dummy(r1cs: &R1CS<F>) -> Self {
Self {
w: vec![F::zero(); r1cs.num_witnesses()],
r_w: F::zero(),
}
}
}
impl<F: PrimeField> WitnessOps<F> for Witness<F> {
type Var = WitnessVar<F>;
@ -357,9 +396,9 @@ where
pub z_i: Vec<C1::ScalarField>,
/// ProtoGalaxy instances
pub w_i: Witness<C1::ScalarField>,
pub u_i: CommittedInstance<C1>,
pub u_i: CommittedInstance<C1, false>,
pub W_i: Witness<C1::ScalarField>,
pub U_i: CommittedInstance<C1>,
pub U_i: CommittedInstance<C1, true>,
/// CycleFold running instance
pub cf_W_i: CycleFoldWitness<C2>,
@ -492,9 +531,10 @@ where
type PreprocessorParam = (PoseidonConfig<CF1<C1>>, FC);
type ProverParam = ProverParams<C1, C2, CS1, CS2>;
type VerifierParam = VerifierParams<C1, C2, CS1, CS2>;
type RunningInstance = (CommittedInstance<C1>, Witness<C1::ScalarField>);
type IncomingInstance = (CommittedInstance<C1>, Witness<C1::ScalarField>);
type MultiCommittedInstanceWithWitness = (CommittedInstance<C1>, Witness<C1::ScalarField>);
type RunningInstance = (CommittedInstance<C1, true>, Witness<C1::ScalarField>);
type IncomingInstance = (CommittedInstance<C1, false>, Witness<C1::ScalarField>);
type MultiCommittedInstanceWithWitness =
(CommittedInstance<C1, false>, Witness<C1::ScalarField>);
type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
fn preprocess(
@ -560,9 +600,9 @@ where
let pp_hash = vp.pp_hash()?;
// setup the dummy instances
let (W_dummy, U_dummy) = vp.r1cs.dummy_running_instance();
let (w_dummy, u_dummy) = vp.r1cs.dummy_incoming_instance();
let (cf_W_dummy, cf_U_dummy) = vp.cf_r1cs.dummy_running_instance();
let (w_dummy, u_dummy) = vp.r1cs.dummy_witness_instance();
let (W_dummy, U_dummy) = vp.r1cs.dummy_witness_instance();
let (cf_W_dummy, cf_U_dummy) = vp.cf_r1cs.dummy_witness_instance();
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
// R1CS that we're working with.
@ -808,10 +848,11 @@ where
)?,
U_i1
);
self.cf_r1cs.check_tight_relation(&_cf1_w_i, &cf1_u_i)?;
self.cf_r1cs.check_tight_relation(&_cf2_w_i, &cf2_u_i)?;
self.cf_r1cs
.check_relaxed_relation(&self.cf_W_i, &self.cf_U_i)?;
cf1_u_i.check_incoming()?;
cf2_u_i.check_incoming()?;
self.cf_r1cs.check_relation(&_cf1_w_i, &cf1_u_i)?;
self.cf_r1cs.check_relation(&_cf2_w_i, &cf2_u_i)?;
self.cf_r1cs.check_relation(&self.cf_W_i, &self.cf_U_i)?;
}
self.W_i = W_i1;
@ -846,8 +887,9 @@ where
#[cfg(test)]
{
self.r1cs.check_tight_relation(&self.w_i, &self.u_i)?;
self.r1cs.check_relaxed_relation(&self.W_i, &self.U_i)?;
self.u_i.check_incoming()?;
self.r1cs.check_relation(&self.w_i, &self.u_i)?;
self.r1cs.check_relation(&self.W_i, &self.U_i)?;
}
Ok(())
@ -904,13 +946,15 @@ where
return Err(Error::IVCVerificationFail);
}
// check R1CS satisfiability
vp.r1cs.check_tight_relation(&w_i, &u_i)?;
// check R1CS satisfiability, which is equivalent to checking if `u_i`
// is an incoming instance and if `w_i` and `u_i` satisfy RelaxedR1CS
u_i.check_incoming()?;
vp.r1cs.check_relation(&w_i, &u_i)?;
// check RelaxedR1CS satisfiability
vp.r1cs.check_relaxed_relation(&W_i, &U_i)?;
vp.r1cs.check_relation(&W_i, &U_i)?;
// check CycleFold RelaxedR1CS satisfiability
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?;
Ok(())
}

+ 47
- 62
folding-schemes/src/folding/protogalaxy/traits.rs

@ -3,18 +3,20 @@ 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 ark_std::{cfg_into_iter, log2, One};
use rayon::prelude::*;
use super::{utils::pow_i, CommittedInstance, CommittedInstanceVar, Witness};
use super::{constants::RUNNING, utils::pow_i, CommittedInstance, CommittedInstanceVar, Witness};
use crate::{
arith::r1cs::{RelaxedR1CS, R1CS},
arith::{r1cs::R1CS, Arith},
folding::circuits::CF1,
transcript::AbsorbNonNative,
utils::vec::is_zero_vec,
Error,
};
// Implements the trait for absorbing ProtoGalaxy's CommittedInstance.
impl<C: CurveGroup> Absorb for CommittedInstance<C>
impl<C: CurveGroup, const TYPE: bool> Absorb for CommittedInstance<C, TYPE>
where
C::ScalarField: Absorb,
{
@ -33,7 +35,9 @@ where
}
// Implements the trait for absorbing ProtoGalaxy's CommittedInstanceVar in-circuit.
impl<C: CurveGroup> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C> {
impl<C: CurveGroup, const TYPE: bool> AbsorbGadget<C::ScalarField>
for CommittedInstanceVar<C, TYPE>
{
fn to_sponge_bytes(&self) -> Result<Vec<UInt8<C::ScalarField>>, SynthesisError> {
FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?)
}
@ -49,69 +53,49 @@ impl AbsorbGadget for CommittedInstanceVar {
}
}
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C::ScalarField>, CommittedInstance<C>>
for R1CS<C::ScalarField>
/// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and
/// the committed instance is of type [`CommittedInstance`].
///
/// Due to the error term `CommittedInstance.e`, R1CS here is considered as a
/// relaxed R1CS.
///
/// See `nova/traits.rs` for the rationale behind the design.
impl<C: CurveGroup, const TYPE: bool> Arith<Witness<CF1<C>>, CommittedInstance<C, TYPE>>
for R1CS<CF1<C>>
{
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()
}
type Evaluation = Vec<CF1<C>>;
fn extract_z(w: &Witness<C::ScalarField>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
[&[C::ScalarField::one()][..], &u.x, &w.w].concat()
fn eval_relation(
&self,
w: &Witness<CF1<C>>,
u: &CommittedInstance<C, TYPE>,
) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[C::ScalarField::one()][..], &u.x, &w.w].concat())
}
fn check_error_terms(
fn check_evaluation(
_w: &Witness<C::ScalarField>,
u: &CommittedInstance<C>,
u: &CommittedInstance<C, TYPE>,
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(())
let ok = if TYPE == RUNNING {
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,
));
}
u.e == cfg_into_iter!(e)
.enumerate()
.map(|(i, e_i)| pow_i(i, &u.betas) * e_i)
.sum::<CF1<C>>()
} 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!()
is_zero_vec(&e)
};
ok.then_some(()).ok_or(Error::NotSatisfied)
}
}
@ -133,7 +117,7 @@ pub mod tests {
let t = rng.gen::<u8>() as usize;
let io_len = rng.gen::<u8>() as usize;
let ci = CommittedInstance::<Projective> {
let ci = CommittedInstance::<Projective, true> {
phi: Projective::rand(&mut rng),
betas: (0..t).map(|_| Fr::rand(&mut rng)).collect(),
e: Fr::rand(&mut rng),
@ -146,7 +130,8 @@ pub mod tests {
let cs = ConstraintSystem::<Fr>::new_ref();
let ciVar =
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
CommittedInstanceVar::<Projective, true>::new_witness(cs.clone(), || Ok(ci.clone()))
.unwrap();
let bytes_var = ciVar.to_sponge_bytes().unwrap();
let field_elements_var = ciVar.to_sponge_field_elements().unwrap();

+ 10
- 0
folding-schemes/src/folding/traits.rs

@ -119,3 +119,13 @@ pub trait WitnessVarOps {
/// randomness) contained in the witness.
fn get_openings(&self) -> Vec<(&[FpVar<F>], FpVar<F>)>;
}
pub trait Dummy<Cfg> {
fn dummy(cfg: Cfg) -> Self;
}
impl<T: Default + Clone> Dummy<usize> for Vec<T> {
fn dummy(cfg: usize) -> Self {
vec![Default::default(); cfg]
}
}

+ 3
- 3
folding-schemes/src/utils/mod.rs

@ -8,7 +8,7 @@ use ark_serialize::CanonicalSerialize;
use ark_std::Zero;
use sha3::{Digest, Sha3_256};
use crate::arith::Arith;
use crate::arith::ArithSerializer;
use crate::commitment::CommitmentScheme;
use crate::Error;
@ -45,8 +45,8 @@ pub fn get_cm_coordinates(cm: &C) -> Vec {
/// returns the hash of the given public parameters of the Folding Scheme
pub fn pp_hash<C1, C2, CS1, CS2, const H: bool>(
arith: &impl Arith<C1::ScalarField>,
cf_arith: &impl Arith<C2::ScalarField>,
arith: &impl ArithSerializer,
cf_arith: &impl ArithSerializer,
cs_vp: &CS1::VerifierParams,
cf_cs_vp: &CS2::VerifierParams,
poseidon_config: &PoseidonConfig<C1::ScalarField>,

Loading…
Cancel
Save