Browse Source

refactor frontend composition, update usage of arkworks CS helpers, refactor test circuits (#54)

Changes:
- get rid of `extract_r1cs_and_z` and `extract_z`
- move `extract_r1cs` and `extract_w_x` from `frontend/arkworks` into
  `r1cs.rs`
The reasoning: they are not methods needed for the Frontend interface,
but only needed internally for the folding scheme to extract values from
the AugmentedF circuit and similar.
- set the `FCircuit` as the trait for the `src/frontend`
- remove the `frontend/arkworks` since the `FCircuit` trait can be
  directly implemented without a middle layer
- reorganize test circuits into `src/frontend/mod.rs`, updating them into
    `CubicFCircuit`: the typical x^3+x+5=y circuit
    `CustomFCircuit`: a circuit in which you can specify the number of
constraints that it will take
  where both fulfill the `FCircuit` trait, and they are used for
  different tests being folded.
main
arnaucube 8 months ago
committed by GitHub
parent
commit
7e3d2dfa43
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 263 additions and 256 deletions
  1. +44
    -0
      src/ccs/r1cs.rs
  2. +11
    -66
      src/folding/nova/circuits.rs
  3. +33
    -61
      src/folding/nova/decider.rs
  4. +7
    -6
      src/folding/nova/ivc.rs
  5. +0
    -122
      src/frontend/arkworks/mod.rs
  6. +168
    -1
      src/frontend/mod.rs

+ 44
- 0
src/ccs/r1cs.rs

@ -2,6 +2,7 @@ use ark_ff::PrimeField;
use crate::utils::vec::*; use crate::utils::vec::*;
use crate::Error; use crate::Error;
use ark_relations::r1cs::ConstraintSystem;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct R1CS<F: PrimeField> { pub struct R1CS<F: PrimeField> {
@ -71,10 +72,53 @@ impl RelaxedR1CS {
} }
} }
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS
/// struct.
pub fn extract_r1cs<F: PrimeField>(cs: &ConstraintSystem<F>) -> R1CS<F> {
let m = cs.to_matrices().unwrap();
let n_rows = cs.num_constraints;
let n_cols = cs.num_instance_variables + cs.num_witness_variables; // cs.num_instance_variables already counts the 1
let A = SparseMatrix::<F> {
n_rows,
n_cols,
coeffs: m.a,
};
let B = SparseMatrix::<F> {
n_rows,
n_cols,
coeffs: m.b,
};
let C = SparseMatrix::<F> {
n_rows,
n_cols,
coeffs: m.c,
};
R1CS::<F> {
l: cs.num_instance_variables - 1, // -1 to substract the first '1'
A,
B,
C,
}
}
/// extracts the witness and the public inputs from arkworks ConstraintSystem.
pub fn extract_w_x<F: PrimeField>(cs: &ConstraintSystem<F>) -> (Vec<F>, Vec<F>) {
(
cs.witness_assignment.clone(),
// skip the first element which is '1'
cs.instance_assignment[1..].to_vec(),
)
}
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::utils::vec::tests::{to_F_matrix, to_F_vec}; use crate::utils::vec::tests::{to_F_matrix, to_F_vec};
use ark_ff::PrimeField;
use ark_pallas::Fr; use ark_pallas::Fr;
pub fn get_test_r1cs<F: PrimeField>() -> R1CS<F> { pub fn get_test_r1cs<F: PrimeField>() -> R1CS<F> {

+ 11
- 66
src/folding/nova/circuits.rs

@ -32,6 +32,7 @@ use super::{
}; };
use crate::constants::N_BITS_RO; use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar}; use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar};
use crate::frontend::FCircuit;
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where /// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where
/// E1 is the main curve where we do the folding. /// E1 is the main curve where we do the folding.
@ -231,31 +232,6 @@ where
} }
} }
/// FCircuit defines the trait of the circuit of the F function, which is the one being executed
/// inside the agmented F' function.
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
/// returns a new FCircuit instance
fn new() -> Self;
/// computes the next state values in place, assigning z_{i+1} into z_i, and
/// computing the new z_i
fn step_native(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to compute the next state.
self,
z_i: Vec<F>,
) -> Vec<F>;
/// generates the constraints for the step of F for the given z_i
fn generate_step_constraints(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to generate the constraints.
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError>;
}
/// AugmentedFCircuit implements the F' circuit (augmented F) defined in /// AugmentedFCircuit implements the F' circuit (augmented F) defined in
/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in /// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
@ -493,41 +469,15 @@ pub mod tests {
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
use crate::folding::nova::{ use crate::folding::nova::{
ivc::get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness, ivc::get_committed_instance_coordinates, nifs::NIFS, traits::NovaR1CS, Witness,
}; };
use crate::frontend::arkworks::{extract_r1cs, extract_z};
use crate::frontend::tests::CubicFCircuit;
use crate::pedersen::Pedersen; use crate::pedersen::Pedersen;
use crate::transcript::poseidon::tests::poseidon_test_config; use crate::transcript::poseidon::tests::poseidon_test_config;
#[derive(Clone, Copy, Debug)]
/// TestFCircuit is a variation of `x^3 + x + 5 = y` (as in
/// src/frontend/arkworks/mod.rs#tests::TestCircuit), adapted to have 2 public inputs which are
/// used as the state. `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next
/// step, `z_{i+1}` will be assigned to `z_i`, and a new `z+{i+1}` will be computted.
pub struct TestFCircuit<F: PrimeField> {
_f: PhantomData<F>,
}
impl<F: PrimeField> FCircuit<F> for TestFCircuit<F> {
fn new() -> Self {
Self { _f: PhantomData }
}
fn step_native(self, z_i: Vec<F>) -> Vec<F> {
vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]
}
fn generate_step_constraints(
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
let z_i = z_i[0].clone();
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
}
}
#[test] #[test]
fn test_committed_instance_var() { fn test_committed_instance_var() {
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
@ -675,7 +625,7 @@ pub mod tests {
} }
#[test] #[test]
/// test_augmented_f_circuit folds the TestFCircuit circuit in multiple iterations, feeding the
/// test_augmented_f_circuit folds the CubicFCircuit circuit in multiple iterations, feeding the
/// values into the AugmentedFCircuit. /// values into the AugmentedFCircuit.
fn test_augmented_f_circuit() { fn test_augmented_f_circuit() {
let mut layer = ConstraintLayer::default(); let mut layer = ConstraintLayer::default();
@ -690,9 +640,9 @@ pub mod tests {
let cs = ConstraintSystem::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();
// prepare the circuit to obtain its R1CS // prepare the circuit to obtain its R1CS
let F_circuit = TestFCircuit::<Fr>::new();
let F_circuit = CubicFCircuit::<Fr>::new(());
let mut augmented_F_circuit = let mut augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>>::empty(
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>>::empty(
&poseidon_config, &poseidon_config,
F_circuit, F_circuit,
); );
@ -703,9 +653,7 @@ pub mod tests {
println!("num_constraints={:?}", cs.num_constraints()); println!("num_constraints={:?}", cs.num_constraints());
let cs = cs.into_inner().unwrap(); let cs = cs.into_inner().unwrap();
let r1cs = extract_r1cs::<Fr>(&cs); let r1cs = extract_r1cs::<Fr>(&cs);
let z = extract_z::<Fr>(&cs); // includes 1 and public inputs
let (w, x) = r1cs.split_z(&z);
assert_eq!(z.len(), r1cs.A.n_cols);
let (w, x) = extract_w_x::<Fr>(&cs);
assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols); assert_eq!(1 + x.len() + w.len(), r1cs.A.n_cols);
assert_eq!(r1cs.l, x.len()); assert_eq!(r1cs.l, x.len());
@ -748,7 +696,7 @@ pub mod tests {
// base case // base case
augmented_F_circuit = augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>> {
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
_gc2: PhantomData, _gc2: PhantomData,
poseidon_config: poseidon_config.clone(), poseidon_config: poseidon_config.clone(),
i: Some(i), // = 0 i: Some(i), // = 0
@ -840,7 +788,7 @@ pub mod tests {
.unwrap(); .unwrap();
augmented_F_circuit = augmented_F_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>> {
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
_gc2: PhantomData, _gc2: PhantomData,
poseidon_config: poseidon_config.clone(), poseidon_config: poseidon_config.clone(),
i: Some(i), i: Some(i),
@ -873,10 +821,7 @@ pub mod tests {
cs.finalize(); cs.finalize();
let cs = cs.into_inner().unwrap(); let cs = cs.into_inner().unwrap();
// notice that here we use 'Z' (uppercase) to denote the 'z-vector' as in the paper,
// not the value 'z' (lowercase) which is the state
let Z_i1 = extract_z::<Fr>(&cs);
let (w_i1, x_i1) = r1cs.split_z(&Z_i1);
let (w_i1, x_i1) = extract_w_x::<Fr>(&cs);
assert_eq!(x_i1.len(), 1); assert_eq!(x_i1.len(), 1);
assert_eq!(x_i1[0], u_i1_x); assert_eq!(x_i1[0], u_i1_x);
@ -892,7 +837,7 @@ pub mod tests {
i += Fr::one(); i += Fr::one();
// advance the F circuit state // advance the F circuit state
z_i = z_i1.clone(); z_i = z_i1.clone();
z_i1 = F_circuit.step_native(z_i.clone());
z_i1 = F_circuit.step_native(z_i.clone()).unwrap();
U_i = U_i1.clone(); U_i = U_i1.clone();
W_i = W_i1.clone(); W_i = W_i1.clone();
} }

+ 33
- 61
src/folding/nova/decider.rs

@ -19,10 +19,11 @@ use core::{borrow::Borrow, marker::PhantomData};
use crate::ccs::r1cs::R1CS; use crate::ccs::r1cs::R1CS;
use crate::folding::nova::{ use crate::folding::nova::{
circuits::{CommittedInstanceVar, FCircuit, CF1, CF2},
circuits::{CommittedInstanceVar, CF1, CF2},
ivc::IVC, ivc::IVC,
CommittedInstance, Witness, CommittedInstance, Witness,
}; };
use crate::frontend::FCircuit;
use crate::pedersen::Params as PedersenParams; use crate::pedersen::Params as PedersenParams;
use crate::utils::gadgets::{ use crate::utils::gadgets::{
hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar, hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar,
@ -436,15 +437,15 @@ pub mod tests {
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use crate::folding::nova::circuits::{tests::TestFCircuit, FCircuit};
use crate::folding::nova::ivc::IVC; use crate::folding::nova::ivc::IVC;
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
use crate::transcript::poseidon::tests::poseidon_test_config; use crate::transcript::poseidon::tests::poseidon_test_config;
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
use crate::ccs::r1cs::{ use crate::ccs::r1cs::{
tests::{get_test_r1cs, get_test_z}, tests::{get_test_r1cs, get_test_z},
R1CS, R1CS,
}; };
use crate::frontend::arkworks::{extract_r1cs_and_z, tests::TestCircuit};
#[test] #[test]
fn test_relaxed_r1cs_small_gadget_handcrafted() { fn test_relaxed_r1cs_small_gadget_handcrafted() {
@ -474,7 +475,9 @@ pub mod tests {
let cs = cs.into_inner().unwrap(); let cs = cs.into_inner().unwrap();
let (r1cs, z) = extract_r1cs_and_z::<Fr>(&cs);
let r1cs = extract_r1cs::<Fr>(&cs);
let (w, x) = extract_w_x::<Fr>(&cs);
let z = [vec![Fr::one()], x, w].concat();
r1cs.check_relation(&z).unwrap(); r1cs.check_relation(&z).unwrap();
let relaxed_r1cs = r1cs.clone().relax(); let relaxed_r1cs = r1cs.clone().relax();
@ -494,9 +497,14 @@ pub mod tests {
#[test] #[test]
fn test_relaxed_r1cs_small_gadget_arkworks() { fn test_relaxed_r1cs_small_gadget_arkworks() {
let x = Fr::from(5_u32);
let y = x * x * x + x + Fr::from(5_u32);
let circuit = TestCircuit::<Fr> { x, y };
let z_i = vec![Fr::from(3_u32)];
let cubic_circuit = CubicFCircuit::<Fr>::new(());
let circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> {
FC: cubic_circuit,
z_i: Some(z_i.clone()),
z_i1: Some(cubic_circuit.step_native(z_i).unwrap()),
};
test_relaxed_r1cs_gadget(circuit); test_relaxed_r1cs_gadget(circuit);
} }
@ -529,59 +537,15 @@ pub mod tests {
test_relaxed_r1cs_gadget(circuit); test_relaxed_r1cs_gadget(circuit);
} }
// circuit that has the number of constraints specified in the `n_constraints` parameter. Note
// that the generated circuit will have very sparse matrices, so the resulting constraints
// number of the RelaxedR1CS gadget must take that into account.
struct CustomTestCircuit<F: PrimeField> {
_f: PhantomData<F>,
pub n_constraints: usize,
pub x: F,
pub y: F,
}
impl<F: PrimeField> CustomTestCircuit<F> {
fn new(n_constraints: usize) -> Self {
let x = F::from(5_u32);
let mut y = F::one();
for _ in 0..n_constraints - 1 {
y *= x;
}
Self {
_f: PhantomData,
n_constraints,
x,
y,
}
}
}
impl<F: PrimeField> ConstraintSynthesizer<F> for CustomTestCircuit<F> {
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let x = FpVar::<F>::new_witness(cs.clone(), || Ok(self.x))?;
let y = FpVar::<F>::new_input(cs.clone(), || Ok(self.y))?;
let mut comp_y = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
for _ in 0..self.n_constraints - 1 {
comp_y *= x.clone();
}
comp_y.enforce_equal(&y)?;
Ok(())
}
}
#[test] #[test]
fn test_relaxed_r1cs_custom_circuit() { fn test_relaxed_r1cs_custom_circuit() {
let n_constraints = 10_000; let n_constraints = 10_000;
let x = Fr::from(5_u32);
let mut y = Fr::one();
for _ in 0..n_constraints - 1 {
y *= x;
}
let circuit = CustomTestCircuit::<Fr> {
_f: PhantomData,
n_constraints,
x,
y,
let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints);
let z_i = vec![Fr::from(5_u32)];
let circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> {
FC: custom_circuit,
z_i: Some(z_i.clone()),
z_i1: Some(custom_circuit.step_native(z_i).unwrap()),
}; };
test_relaxed_r1cs_gadget(circuit); test_relaxed_r1cs_gadget(circuit);
} }
@ -592,11 +556,19 @@ pub mod tests {
// in practice we would use CycleFoldCircuit, but is a very big circuit (when computed // in practice we would use CycleFoldCircuit, but is a very big circuit (when computed
// non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a // non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a
// custom circuit. // custom circuit.
let circuit = CustomTestCircuit::<Fq>::new(10);
let custom_circuit = CustomFCircuit::<Fq>::new(10);
let z_i = vec![Fq::from(5_u32)];
let circuit = WrapperCircuit::<Fq, CustomFCircuit<Fq>> {
FC: custom_circuit,
z_i: Some(z_i.clone()),
z_i1: Some(custom_circuit.step_native(z_i).unwrap()),
};
circuit.generate_constraints(cs.clone()).unwrap(); circuit.generate_constraints(cs.clone()).unwrap();
cs.finalize(); cs.finalize();
let cs = cs.into_inner().unwrap(); let cs = cs.into_inner().unwrap();
let (r1cs, z) = extract_r1cs_and_z::<Fq>(&cs);
let r1cs = extract_r1cs::<Fq>(&cs);
let (w, x) = extract_w_x::<Fq>(&cs);
let z = [vec![Fq::one()], x, w].concat();
let relaxed_r1cs = r1cs.clone().relax(); let relaxed_r1cs = r1cs.clone().relax();
@ -629,11 +601,11 @@ pub mod tests {
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fr>(); let poseidon_config = poseidon_test_config::<Fr>();
let F_circuit = TestFCircuit::<Fr>::new();
let F_circuit = CubicFCircuit::<Fr>::new(());
let z_0 = vec![Fr::from(3_u32)]; let z_0 = vec![Fr::from(3_u32)];
// generate an IVC and do a step of it // generate an IVC and do a step of it
let mut ivc = IVC::<Projective, GVar, Projective2, GVar2, TestFCircuit<Fr>>::new(
let mut ivc = IVC::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>::new(
&mut rng, &mut rng,
poseidon_config, poseidon_config,
F_circuit, F_circuit,

+ 7
- 6
src/folding/nova/ivc.rs

@ -8,12 +8,13 @@ use ark_std::{One, Zero};
use core::marker::PhantomData; use core::marker::PhantomData;
use super::{ use super::{
circuits::{AugmentedFCircuit, ChallengeGadget, FCircuit, CF2},
circuits::{AugmentedFCircuit, ChallengeGadget, CF2},
cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit}, cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit},
}; };
use super::{nifs::NIFS, traits::NovaR1CS, CommittedInstance, Witness}; use super::{nifs::NIFS, traits::NovaR1CS, CommittedInstance, Witness};
use crate::ccs::r1cs::R1CS; use crate::ccs::r1cs::R1CS;
use crate::frontend::arkworks::{extract_r1cs, extract_w_x}; // TODO once Frontend trait is ready, use that
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
use crate::frontend::FCircuit;
use crate::pedersen::{Params as PedersenParams, Pedersen}; use crate::pedersen::{Params as PedersenParams, Pedersen};
use crate::Error; use crate::Error;
@ -138,7 +139,7 @@ where
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
let cf_circuit: CycleFoldCircuit<C1, GC1>; let cf_circuit: CycleFoldCircuit<C1, GC1>;
let z_i1 = self.F.step_native(self.z_i.clone());
let z_i1 = self.F.step_native(self.z_i.clone())?;
// compute T and cmT for AugmentedFCircuit // compute T and cmT for AugmentedFCircuit
let (T, cmT) = self.compute_cmT()?; let (T, cmT) = self.compute_cmT()?;
@ -397,7 +398,7 @@ mod tests {
use ark_pallas::{constraints::GVar, Fr, Projective}; use ark_pallas::{constraints::GVar, Fr, Projective};
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use crate::folding::nova::circuits::tests::TestFCircuit;
use crate::frontend::tests::CubicFCircuit;
use crate::transcript::poseidon::tests::poseidon_test_config; use crate::transcript::poseidon::tests::poseidon_test_config;
#[test] #[test]
@ -405,10 +406,10 @@ mod tests {
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fr>(); let poseidon_config = poseidon_test_config::<Fr>();
let F_circuit = TestFCircuit::<Fr>::new();
let F_circuit = CubicFCircuit::<Fr>::new(());
let z_0 = vec![Fr::from(3_u32)]; let z_0 = vec![Fr::from(3_u32)];
let mut ivc = IVC::<Projective, GVar, Projective2, GVar2, TestFCircuit<Fr>>::new(
let mut ivc = IVC::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>::new(
&mut rng, &mut rng,
poseidon_config, poseidon_config,
F_circuit, F_circuit,

+ 0
- 122
src/frontend/arkworks/mod.rs

@ -1,122 +0,0 @@
/// arkworks frontend
use ark_ff::PrimeField;
use ark_relations::r1cs::ConstraintSystem;
use crate::ccs::r1cs::R1CS;
use crate::utils::vec::SparseMatrix;
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format, and
/// extracts public inputs and witness into z vector. Returns a tuple containing (R1CS, z).
pub fn extract_r1cs_and_z<F: PrimeField>(cs: &ConstraintSystem<F>) -> (R1CS<F>, Vec<F>) {
let r1cs = extract_r1cs(cs);
// z = (1, x, w)
let z = extract_z(cs);
(r1cs, z)
}
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS
/// struct.
pub fn extract_r1cs<F: PrimeField>(cs: &ConstraintSystem<F>) -> R1CS<F> {
let m = cs.to_matrices().unwrap();
let n_rows = cs.num_constraints;
let n_cols = cs.num_instance_variables + cs.num_witness_variables; // cs.num_instance_variables already counts the 1
let A = SparseMatrix::<F> {
n_rows,
n_cols,
coeffs: m.a,
};
let B = SparseMatrix::<F> {
n_rows,
n_cols,
coeffs: m.b,
};
let C = SparseMatrix::<F> {
n_rows,
n_cols,
coeffs: m.c,
};
R1CS::<F> {
l: cs.num_instance_variables - 1, // -1 to substract the first '1'
A,
B,
C,
}
}
/// extracts public inputs and witness into z vector from arkworks ConstraintSystem.
pub fn extract_z<F: PrimeField>(cs: &ConstraintSystem<F>) -> Vec<F> {
// z = (1, x, w)
[
// 1 is already included in cs.instance_assignment
cs.instance_assignment.clone(),
cs.witness_assignment.clone(),
]
.concat()
}
/// extracts the witness and the public inputs from arkworks ConstraintSystem.
pub fn extract_w_x<F: PrimeField>(cs: &ConstraintSystem<F>) -> (Vec<F>, Vec<F>) {
(
cs.witness_assignment.clone(),
// skip the first element which is '1'
cs.instance_assignment[1..].to_vec(),
)
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_ff::PrimeField;
use ark_pallas::Fr;
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget, fields::fp::FpVar};
use ark_relations::r1cs::{
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
};
// TestCircuit implements the R1CS for: x^3 + x + 5 = y (example from article
// https://www.vitalik.ca/general/2016/12/10/qap.html )
#[derive(Clone, Copy, Debug)]
pub struct TestCircuit<F: PrimeField> {
pub x: F,
pub y: F,
}
impl<F: PrimeField> ConstraintSynthesizer<F> for TestCircuit<F> {
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let x = FpVar::<F>::new_input(cs.clone(), || Ok(self.x))?;
let y = FpVar::<F>::new_witness(cs.clone(), || Ok(self.y))?;
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
let comp_y = (&x * &x * &x) + &x + &five;
comp_y.enforce_equal(&y)?;
Ok(())
}
}
#[test]
fn test_cs_to_matrices() {
let cs = ConstraintSystem::<Fr>::new_ref();
let x = Fr::from(5_u32);
let y = x * x * x + x + Fr::from(5_u32);
let test_circuit = TestCircuit::<Fr> { x, y };
test_circuit.generate_constraints(cs.clone()).unwrap();
cs.finalize();
assert!(cs.is_satisfied().unwrap());
let cs = cs.into_inner().unwrap();
// ensure that num_instance_variables is 2: 1 + 1 public input
assert_eq!(cs.num_instance_variables, 2);
let (r1cs, z) = extract_r1cs_and_z::<Fr>(&cs);
r1cs.check_relation(&z).unwrap();
// ensure that number of public inputs (l) is 1
assert_eq!(r1cs.l, 1);
}
}

+ 168
- 1
src/frontend/mod.rs

@ -1,2 +1,169 @@
pub mod arkworks;
pub mod circom; pub mod circom;
use crate::Error;
use ark_ff::PrimeField;
use ark_r1cs_std::fields::fp::FpVar;
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
use ark_std::fmt::Debug;
/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
/// inside the agmented F' function).
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
type Params: Debug;
/// returns a new FCircuit instance
fn new(params: Self::Params) -> Self;
/// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
/// z_{i+1}
fn step_native(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to compute the next state.
self,
z_i: Vec<F>,
) -> Result<Vec<F>, Error>;
/// generates the constraints for the step of F for the given z_i
fn generate_step_constraints(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to generate the constraints.
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError>;
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_pallas::Fr;
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
use ark_relations::r1cs::{
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
};
use core::marker::PhantomData;
/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`. It
/// has 2 public inputs which are used as the state. `z_i` is used as `x`, and `z_{i+1}` is
/// used as `y`, and at the next step, `z_{i+1}` will be assigned to `z_i`, and a new `z+{i+1}`
/// will be computted.
#[derive(Clone, Copy, Debug)]
pub struct CubicFCircuit<F: PrimeField> {
_f: PhantomData<F>,
}
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
type Params = ();
fn new(_params: Self::Params) -> Self {
Self { _f: PhantomData }
}
fn step_native(self, z_i: Vec<F>) -> Result<Vec<F>, Error> {
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
}
fn generate_step_constraints(
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
let z_i = z_i[0].clone();
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
}
}
/// CustomFCircuit is a circuit that has the number of constraints specified in the
/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices.
#[derive(Clone, Copy, Debug)]
pub struct CustomFCircuit<F: PrimeField> {
_f: PhantomData<F>,
pub n_constraints: usize,
}
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> {
type Params = usize;
fn new(params: Self::Params) -> Self {
Self {
_f: PhantomData,
n_constraints: params,
}
}
fn step_native(self, z_i: Vec<F>) -> Result<Vec<F>, Error> {
let mut z_i1 = F::one();
for _ in 0..self.n_constraints - 1 {
z_i1 *= z_i[0];
}
Ok(vec![z_i1])
}
fn generate_step_constraints(
self,
cs: ConstraintSystemRef<F>,
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
for _ in 0..self.n_constraints - 1 {
z_i1 *= z_i[0].clone();
}
Ok(vec![z_i1])
}
}
/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This
/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping
/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints
/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in
/// the the AugmentedF logic but in the wrapping of the circuits.
pub struct WrapperCircuit<F: PrimeField, FC: FCircuit<F>> {
pub FC: FC, // F circuit
pub z_i: Option<Vec<F>>,
pub z_i1: Option<Vec<F>>,
}
impl<F, FC> ConstraintSynthesizer<F> for WrapperCircuit<F, FC>
where
F: PrimeField,
FC: FCircuit<F>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let z_i = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
Ok(self.z_i.unwrap_or(vec![F::zero()]))
})?;
let z_i1 = Vec::<FpVar<F>>::new_input(cs.clone(), || {
Ok(self.z_i1.unwrap_or(vec![F::zero()]))
})?;
let computed_z_i1 = self.FC.generate_step_constraints(cs.clone(), z_i.clone())?;
computed_z_i1.enforce_equal(&z_i1)?;
Ok(())
}
}
#[test]
fn test_testfcircuit() {
let cs = ConstraintSystem::<Fr>::new_ref();
let F_circuit = CubicFCircuit::<Fr>::new(());
let wrapper_circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> {
FC: F_circuit,
z_i: Some(vec![Fr::from(3_u32)]),
z_i1: Some(vec![Fr::from(35_u32)]),
};
wrapper_circuit.generate_constraints(cs.clone()).unwrap();
assert_eq!(cs.num_constraints(), 3);
}
#[test]
fn test_customtestfcircuit() {
let cs = ConstraintSystem::<Fr>::new_ref();
let n_constraints = 1000;
let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints);
let z_i = vec![Fr::from(5_u32)];
let wrapper_circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> {
FC: custom_circuit,
z_i: Some(z_i.clone()),
z_i1: Some(custom_circuit.step_native(z_i).unwrap()),
};
wrapper_circuit.generate_constraints(cs.clone()).unwrap();
assert_eq!(cs.num_constraints(), n_constraints);
}
}

Loading…
Cancel
Save