From 7e3d2dfa4360068a3d594fdf91d6d6e3378b288c Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 16 Jan 2024 09:23:47 +0100 Subject: [PATCH] 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. --- src/ccs/r1cs.rs | 44 +++++++++ src/folding/nova/circuits.rs | 77 +++------------- src/folding/nova/decider.rs | 94 +++++++------------ src/folding/nova/ivc.rs | 13 +-- src/frontend/arkworks/mod.rs | 122 ------------------------- src/frontend/mod.rs | 169 ++++++++++++++++++++++++++++++++++- 6 files changed, 263 insertions(+), 256 deletions(-) delete mode 100644 src/frontend/arkworks/mod.rs diff --git a/src/ccs/r1cs.rs b/src/ccs/r1cs.rs index 3f90933..6e85ebd 100644 --- a/src/ccs/r1cs.rs +++ b/src/ccs/r1cs.rs @@ -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 { @@ -71,10 +72,53 @@ impl RelaxedR1CS { } } +/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS +/// struct. +pub fn extract_r1cs(cs: &ConstraintSystem) -> R1CS { + 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:: { + n_rows, + n_cols, + coeffs: m.a, + }; + let B = SparseMatrix:: { + n_rows, + n_cols, + coeffs: m.b, + }; + let C = SparseMatrix:: { + n_rows, + n_cols, + coeffs: m.c, + }; + + R1CS:: { + 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(cs: &ConstraintSystem) -> (Vec, Vec) { + ( + 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() -> R1CS { diff --git a/src/folding/nova/circuits.rs b/src/folding/nova/circuits.rs index afa7cc5..109c605 100644 --- a/src/folding/nova/circuits.rs +++ b/src/folding/nova/circuits.rs @@ -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: 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, - ) -> Vec; - - /// 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, - z_i: Vec>, - ) -> Result>, 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: PhantomData, - } - impl FCircuit for TestFCircuit { - fn new() -> Self { - Self { _f: PhantomData } - } - fn step_native(self, z_i: Vec) -> Vec { - vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)] - } - fn generate_step_constraints( - self, - cs: ConstraintSystemRef, - z_i: Vec>, - ) -> Result>, SynthesisError> { - let five = FpVar::::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::::new_ref(); // prepare the circuit to obtain its R1CS - let F_circuit = TestFCircuit::::new(); + let F_circuit = CubicFCircuit::::new(()); let mut augmented_F_circuit = - AugmentedFCircuit::>::empty( + AugmentedFCircuit::>::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::(&cs); - let z = extract_z::(&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::(&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::> { + AugmentedFCircuit::> { _gc2: PhantomData, poseidon_config: poseidon_config.clone(), i: Some(i), // = 0 @@ -840,7 +788,7 @@ pub mod tests { .unwrap(); augmented_F_circuit = - AugmentedFCircuit::> { + AugmentedFCircuit::> { _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::(&cs); - let (w_i1, x_i1) = r1cs.split_z(&Z_i1); + let (w_i1, x_i1) = extract_w_x::(&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(); } diff --git a/src/folding/nova/decider.rs b/src/folding/nova/decider.rs index adafc01..75022ef 100644 --- a/src/folding/nova/decider.rs +++ b/src/folding/nova/decider.rs @@ -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::(&cs); + let r1cs = extract_r1cs::(&cs); + let (w, x) = extract_w_x::(&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:: { x, y }; + let z_i = vec![Fr::from(3_u32)]; + let cubic_circuit = CubicFCircuit::::new(()); + let circuit = WrapperCircuit::> { + 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: PhantomData, - pub n_constraints: usize, - pub x: F, - pub y: F, - } - impl CustomTestCircuit { - 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 ConstraintSynthesizer for CustomTestCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let x = FpVar::::new_witness(cs.clone(), || Ok(self.x))?; - let y = FpVar::::new_input(cs.clone(), || Ok(self.y))?; - - let mut comp_y = FpVar::::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:: { - _f: PhantomData, - n_constraints, - x, - y, + let custom_circuit = CustomFCircuit::::new(n_constraints); + let z_i = vec![Fr::from(5_u32)]; + let circuit = WrapperCircuit::> { + 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::::new(10); + let custom_circuit = CustomFCircuit::::new(10); + let z_i = vec![Fq::from(5_u32)]; + let circuit = WrapperCircuit::> { + 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::(&cs); + let r1cs = extract_r1cs::(&cs); + let (w, x) = extract_w_x::(&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::(); - let F_circuit = TestFCircuit::::new(); + let F_circuit = CubicFCircuit::::new(()); let z_0 = vec![Fr::from(3_u32)]; // generate an IVC and do a step of it - let mut ivc = IVC::>::new( + let mut ivc = IVC::>::new( &mut rng, poseidon_config, F_circuit, diff --git a/src/folding/nova/ivc.rs b/src/folding/nova/ivc.rs index 2c4f156..287bf74 100644 --- a/src/folding/nova/ivc.rs +++ b/src/folding/nova/ivc.rs @@ -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; let cf_circuit: CycleFoldCircuit; - 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::(); - let F_circuit = TestFCircuit::::new(); + let F_circuit = CubicFCircuit::::new(()); let z_0 = vec![Fr::from(3_u32)]; - let mut ivc = IVC::>::new( + let mut ivc = IVC::>::new( &mut rng, poseidon_config, F_circuit, diff --git a/src/frontend/arkworks/mod.rs b/src/frontend/arkworks/mod.rs deleted file mode 100644 index 5ef3924..0000000 --- a/src/frontend/arkworks/mod.rs +++ /dev/null @@ -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(cs: &ConstraintSystem) -> (R1CS, Vec) { - 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(cs: &ConstraintSystem) -> R1CS { - 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:: { - n_rows, - n_cols, - coeffs: m.a, - }; - let B = SparseMatrix:: { - n_rows, - n_cols, - coeffs: m.b, - }; - let C = SparseMatrix:: { - n_rows, - n_cols, - coeffs: m.c, - }; - - R1CS:: { - 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(cs: &ConstraintSystem) -> Vec { - // 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(cs: &ConstraintSystem) -> (Vec, Vec) { - ( - 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 { - pub x: F, - pub y: F, - } - impl ConstraintSynthesizer for TestCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let x = FpVar::::new_input(cs.clone(), || Ok(self.x))?; - let y = FpVar::::new_witness(cs.clone(), || Ok(self.y))?; - let five = FpVar::::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::::new_ref(); - - let x = Fr::from(5_u32); - let y = x * x * x + x + Fr::from(5_u32); - - let test_circuit = TestCircuit:: { 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::(&cs); - r1cs.check_relation(&z).unwrap(); - // ensure that number of public inputs (l) is 1 - assert_eq!(r1cs.l, 1); - } -} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 80a1046..dda6ab5 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -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: 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, + ) -> Result, 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, + z_i: Vec>, + ) -> Result>, 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: PhantomData, + } + impl FCircuit for CubicFCircuit { + type Params = (); + fn new(_params: Self::Params) -> Self { + Self { _f: PhantomData } + } + fn step_native(self, z_i: Vec) -> Result, 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, + z_i: Vec>, + ) -> Result>, SynthesisError> { + let five = FpVar::::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: PhantomData, + pub n_constraints: usize, + } + impl FCircuit for CustomFCircuit { + type Params = usize; + + fn new(params: Self::Params) -> Self { + Self { + _f: PhantomData, + n_constraints: params, + } + } + fn step_native(self, z_i: Vec) -> Result, 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, + z_i: Vec>, + ) -> Result>, SynthesisError> { + let mut z_i1 = FpVar::::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> { + pub FC: FC, // F circuit + pub z_i: Option>, + pub z_i1: Option>, + } + impl ConstraintSynthesizer for WrapperCircuit + where + F: PrimeField, + FC: FCircuit, + { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let z_i = Vec::>::new_witness(cs.clone(), || { + Ok(self.z_i.unwrap_or(vec![F::zero()])) + })?; + let z_i1 = Vec::>::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::::new_ref(); + let F_circuit = CubicFCircuit::::new(()); + + let wrapper_circuit = WrapperCircuit::> { + 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::::new_ref(); + let n_constraints = 1000; + let custom_circuit = CustomFCircuit::::new(n_constraints); + let z_i = vec![Fr::from(5_u32)]; + let wrapper_circuit = WrapperCircuit::> { + 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); + } +}