mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-09 07:21:28 +01:00
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.
This commit is contained in:
@@ -2,6 +2,7 @@ use ark_ff::PrimeField;
|
||||
|
||||
use crate::utils::vec::*;
|
||||
use crate::Error;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct R1CS<F: PrimeField> {
|
||||
@@ -71,10 +72,53 @@ impl<F: PrimeField> RelaxedR1CS<F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::utils::vec::tests::{to_F_matrix, to_F_vec};
|
||||
|
||||
use ark_ff::PrimeField;
|
||||
use ark_pallas::Fr;
|
||||
|
||||
pub fn get_test_r1cs<F: PrimeField>() -> R1CS<F> {
|
||||
|
||||
@@ -32,6 +32,7 @@ use super::{
|
||||
};
|
||||
use crate::constants::N_BITS_RO;
|
||||
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
|
||||
/// 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
|
||||
/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in
|
||||
/// [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 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::{
|
||||
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::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]
|
||||
fn test_committed_instance_var() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
@@ -675,7 +625,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[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.
|
||||
fn test_augmented_f_circuit() {
|
||||
let mut layer = ConstraintLayer::default();
|
||||
@@ -690,9 +640,9 @@ pub mod tests {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// prepare the circuit to obtain its R1CS
|
||||
let F_circuit = TestFCircuit::<Fr>::new();
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let mut augmented_F_circuit =
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>>::empty(
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>>::empty(
|
||||
&poseidon_config,
|
||||
F_circuit,
|
||||
);
|
||||
@@ -703,9 +653,7 @@ pub mod tests {
|
||||
println!("num_constraints={:?}", cs.num_constraints());
|
||||
let cs = cs.into_inner().unwrap();
|
||||
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!(r1cs.l, x.len());
|
||||
|
||||
@@ -748,7 +696,7 @@ pub mod tests {
|
||||
|
||||
// base case
|
||||
augmented_F_circuit =
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>> {
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
i: Some(i), // = 0
|
||||
@@ -840,7 +788,7 @@ pub mod tests {
|
||||
.unwrap();
|
||||
|
||||
augmented_F_circuit =
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, TestFCircuit<Fr>> {
|
||||
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
i: Some(i),
|
||||
@@ -873,10 +821,7 @@ pub mod tests {
|
||||
|
||||
cs.finalize();
|
||||
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[0], u_i1_x);
|
||||
|
||||
@@ -892,7 +837,7 @@ pub mod tests {
|
||||
i += Fr::one();
|
||||
// advance the F circuit state
|
||||
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();
|
||||
W_i = W_i1.clone();
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use crate::ccs::r1cs::R1CS;
|
||||
use crate::folding::nova::{
|
||||
circuits::{CommittedInstanceVar, FCircuit, CF1, CF2},
|
||||
circuits::{CommittedInstanceVar, CF1, CF2},
|
||||
ivc::IVC,
|
||||
CommittedInstance, Witness,
|
||||
};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::pedersen::Params as PedersenParams;
|
||||
use crate::utils::gadgets::{
|
||||
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_vesta::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
|
||||
use crate::folding::nova::circuits::{tests::TestFCircuit, FCircuit};
|
||||
use crate::folding::nova::ivc::IVC;
|
||||
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
|
||||
use crate::transcript::poseidon::tests::poseidon_test_config;
|
||||
|
||||
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
|
||||
use crate::ccs::r1cs::{
|
||||
tests::{get_test_r1cs, get_test_z},
|
||||
R1CS,
|
||||
};
|
||||
use crate::frontend::arkworks::{extract_r1cs_and_z, tests::TestCircuit};
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_small_gadget_handcrafted() {
|
||||
@@ -474,7 +475,9 @@ pub mod tests {
|
||||
|
||||
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();
|
||||
|
||||
let relaxed_r1cs = r1cs.clone().relax();
|
||||
@@ -494,9 +497,14 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -529,59 +537,15 @@ pub mod tests {
|
||||
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]
|
||||
fn test_relaxed_r1cs_custom_circuit() {
|
||||
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);
|
||||
}
|
||||
@@ -592,11 +556,19 @@ pub mod tests {
|
||||
// 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
|
||||
// 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();
|
||||
cs.finalize();
|
||||
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();
|
||||
|
||||
@@ -629,11 +601,11 @@ pub mod tests {
|
||||
let mut rng = ark_std::test_rng();
|
||||
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)];
|
||||
|
||||
// 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,
|
||||
poseidon_config,
|
||||
F_circuit,
|
||||
|
||||
@@ -8,12 +8,13 @@ use ark_std::{One, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::{
|
||||
circuits::{AugmentedFCircuit, ChallengeGadget, FCircuit, CF2},
|
||||
circuits::{AugmentedFCircuit, ChallengeGadget, CF2},
|
||||
cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit},
|
||||
};
|
||||
use super::{nifs::NIFS, traits::NovaR1CS, CommittedInstance, Witness};
|
||||
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::Error;
|
||||
|
||||
@@ -138,7 +139,7 @@ where
|
||||
let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
|
||||
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
|
||||
let (T, cmT) = self.compute_cmT()?;
|
||||
@@ -397,7 +398,7 @@ mod tests {
|
||||
use ark_pallas::{constraints::GVar, Fr, Projective};
|
||||
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;
|
||||
|
||||
#[test]
|
||||
@@ -405,10 +406,10 @@ mod tests {
|
||||
let mut rng = ark_std::test_rng();
|
||||
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 mut ivc = IVC::<Projective, GVar, Projective2, GVar2, TestFCircuit<Fr>>::new(
|
||||
let mut ivc = IVC::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>::new(
|
||||
&mut rng,
|
||||
poseidon_config,
|
||||
F_circuit,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,169 @@
|
||||
pub mod arkworks;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user