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 11 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