Skip to content

Commit

Permalink
Protogalaxy based IVC (#123)
Browse files Browse the repository at this point in the history
* Parallelize vector and matrix operations

* Implement convenient methods for `NonNativeAffineVar`

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

These values will be used as hints to the augmented circuit

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

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

* Improve prover's performance

* Make `prepare_inputs` generic

* Remove redundant parameters in verifier

* Move `eval_f` to arith

* `u` is unnecessary in ProtoGalaxy

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

* Implement several traits for ProtoGalaxy

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

* `AugmentedFCircuit` and ProtoGalaxy-based IVC

* Add explanations about IVC prover and in-circuit operations

* Avoid using unstable features

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

* Fix merge conflicts in `RelaxedR1CS::sample`

* Fix merge conflicts in `CycleFoldCircuit`

* Swap `m` and `n` for protogalaxy

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

* Prefer unit struct over empty struct

* Add documents to `AugmentedFCircuit` for ProtoGalaxy

* Fix the names for CycleFold cricuits in ProtoGalaxy

* Fix usize conversion when targeting wasm

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

* Make CycleFold circuits and configs public

* Add docs for `ProverParams` and `VerifierParams`

* Refactor `pow_i`

* Fix imports

* Remove lint reasons

* Fix type inference
  • Loading branch information
winderica authored Sep 12, 2024
1 parent 0ad5457 commit 1322767
Show file tree
Hide file tree
Showing 26 changed files with 2,218 additions and 691 deletions.
32 changes: 20 additions & 12 deletions folding-schemes/src/arith/ccs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ pub struct CCS<F: PrimeField> {
}

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 {
Expand All @@ -57,14 +56,7 @@ impl<F: PrimeField> Arith<F> for CCS<F> {
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> {
Expand Down Expand Up @@ -113,7 +105,10 @@ impl<F: PrimeField> CCS<F> {
#[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> {
Expand All @@ -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);

Expand Down
17 changes: 15 additions & 2 deletions folding-schemes/src/arith/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
190 changes: 75 additions & 115 deletions folding-schemes/src/arith/r1cs.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -21,16 +19,24 @@ pub struct R1CS<F: PrimeField> {
}

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> {
Expand Down Expand Up @@ -65,55 +71,50 @@ impl<F: PrimeField> R1CS<F> {
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
}

/// 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)?;
Expand All @@ -123,66 +124,9 @@ impl<F: PrimeField> RelaxedR1CS<F> {
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
Expand Down Expand Up @@ -229,9 +173,13 @@ pub fn extract_w_x<F: PrimeField>(cs: &ConstraintSystem<F>) -> (Vec<F>, Vec<F>)
#[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};
Expand All @@ -242,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());
}

Expand Down Expand Up @@ -302,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();
}
}
9 changes: 7 additions & 2 deletions folding-schemes/src/folding/circuits/cyclefold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()))
Expand All @@ -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(),
]
Expand Down
Loading

0 comments on commit 1322767

Please sign in to comment.