mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-09 23:41:30 +01:00
Adapt Frontend/Circom for FCircuit Trait (#71)
* initial
* improved z_i
* improved
* Redefined the .circom and the test code for CircomWrapper
* added test function for wrapper_circuit which has circom_fcircuit, but incompatibility error of Pairing and Pallas
* changed the path in the gitignore
* Remove circom generated path
* improved variable vector
* Pallas::Fr
* improved to the primefield
* mentioned the issues:1. extract z_i1(only public output) 2.constraintsSystem and its Ref
* modified
* public input in circom
* generalized; removed the hardcorded value
* Generalization using FpVar::<Fr>::new_input
* initial
* improved z_i
* improved
* Redefined the .circom and the test code for CircomWrapper
* added test function for wrapper_circuit which has circom_fcircuit, but incompatibility error of Pairing and Pallas
* Remove circom generated path
* improved variable vector
* Pallas::Fr
* improved to the primefield
* mentioned the issues:1. extract z_i1(only public output) 2.constraintsSystem and its Ref
* modified
* Small updates:
- update cubic_circuit.circom: remove extra constraint, remove public
inputs
- remove allocations of inputs in arkworks
- add return of z_{i+1} at the end of CircomtoFCircuit::generate_step_constraints
With this tmp fix the `test_circom_step_constraints` passes, but needs to be iterated and polished.
* Update circom-compat to re-allocate inputs
* update after rebase to latest main: add usage of self.state_len() to circom frontend
* move circom frontend related structs into frontend/circom dir
* clippy lints
* extract_witness
* add comments
* clean
* fmt, lint, and spell
* CI Check trigger
* fmt
* applied the feedback
---------
Co-authored-by: Y5 <76672645+yugonsan@users.noreply.github.com>
Co-authored-by: arnaucube <root@arnaucube.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,8 +1,8 @@
|
|||||||
/target
|
/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
# Circom generated files
|
# Circom generated files
|
||||||
/src/frontend/circom/test_folder/test_circuit.r1cs
|
folding-schemes/src/frontend/circom/test_folder/cubic_circuit.r1cs
|
||||||
/src/frontend/circom/test_folder/test_circuit_js/
|
folding-schemes/src/frontend/circom/test_folder/cubic_circuit_js/
|
||||||
|
|
||||||
# generated contracts at test time
|
# generated contracts at test time
|
||||||
solidity-verifiers/generated
|
solidity-verifiers/generated
|
||||||
@@ -14,7 +14,7 @@ ark-relations = { version = "^0.4.0", default-features = false }
|
|||||||
ark-r1cs-std = { version = "0.4.0", default-features = false } # this is patched at the workspace level
|
ark-r1cs-std = { version = "0.4.0", default-features = false } # this is patched at the workspace level
|
||||||
ark-snark = { version = "^0.4.0"}
|
ark-snark = { version = "^0.4.0"}
|
||||||
ark-serialize = "^0.4.0"
|
ark-serialize = "^0.4.0"
|
||||||
ark-circom = { git = "https://github.com/gakonst/ark-circom.git" }
|
ark-circom = { git = "https://github.com/arnaucube/circom-compat.git" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
rayon = "1.7.0"
|
rayon = "1.7.0"
|
||||||
num-bigint = "0.4"
|
num-bigint = "0.4"
|
||||||
|
|||||||
@@ -1,226 +1,177 @@
|
|||||||
use std::{error::Error, fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
|
use ark_circom::circom::CircomCircuit;
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use num_bigint::BigInt;
|
|
||||||
|
|
||||||
use ark_circom::{circom::r1cs_reader, WitnessCalculator};
|
|
||||||
use ark_ec::pairing::Pairing;
|
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
|
use ark_r1cs_std::alloc::AllocVar;
|
||||||
|
use ark_r1cs_std::fields::fp::FpVar;
|
||||||
|
use ark_r1cs_std::R1CSVar;
|
||||||
|
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||||
|
use ark_std::fmt::Debug;
|
||||||
|
use num_bigint::BigInt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::ccs::r1cs::R1CS;
|
use crate::frontend::FCircuit;
|
||||||
use crate::utils::vec::SparseMatrix;
|
use crate::Error;
|
||||||
|
|
||||||
// Define the sparse matrices on PrimeFiled.
|
pub mod utils;
|
||||||
pub type Constraints<F> = (ConstraintVec<F>, ConstraintVec<F>, ConstraintVec<F>);
|
use utils::CircomWrapper;
|
||||||
pub type ConstraintVec<F> = Vec<(usize, F)>;
|
|
||||||
type ExtractedConstraints<F> = (Vec<Constraints<F>>, usize, usize);
|
|
||||||
pub type ExtractedConstraintsResult<F> = Result<ExtractedConstraints<F>, Box<dyn Error>>;
|
|
||||||
pub type R1CSandZ<F> = (R1CS<F>, Vec<F>);
|
|
||||||
|
|
||||||
// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses
|
/// Define CircomFCircuit
|
||||||
// based on file paths to Circom's .r1cs and .wasm.
|
#[derive(Clone, Debug)]
|
||||||
pub struct CircomWrapper<E: Pairing> {
|
pub struct CircomFCircuit<F: PrimeField> {
|
||||||
r1cs_filepath: PathBuf,
|
circom_wrapper: CircomWrapper<F>,
|
||||||
wasm_filepath: PathBuf,
|
|
||||||
_marker: PhantomData<E>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> CircomWrapper<E> {
|
impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> {
|
||||||
// Creates a new instance of the CircomWrapper with the file paths.
|
type Params = (PathBuf, PathBuf);
|
||||||
pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
|
|
||||||
CircomWrapper {
|
fn new(params: Self::Params) -> Self {
|
||||||
r1cs_filepath,
|
let (r1cs_path, wasm_path) = params;
|
||||||
wasm_filepath,
|
let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path);
|
||||||
_marker: PhantomData,
|
Self { circom_wrapper }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggregates multiple functions to obtain R1CS and Z as defined in folding-schemes from Circom.
|
fn state_len(&self) -> usize {
|
||||||
pub fn extract_r1cs_and_z(
|
1
|
||||||
&self,
|
|
||||||
inputs: &[(String, Vec<BigInt>)],
|
|
||||||
) -> Result<R1CSandZ<E::ScalarField>, Box<dyn Error>> {
|
|
||||||
let (constraints, pub_io_len, num_variables) = self.extract_constraints_from_r1cs()?;
|
|
||||||
let witness = self.calculate_witness(inputs)?;
|
|
||||||
self.circom_to_folding_schemes_r1cs_and_z(constraints, &witness, pub_io_len, num_variables)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extracts constraints from the r1cs file.
|
fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
|
||||||
pub fn extract_constraints_from_r1cs(&self) -> ExtractedConstraintsResult<E::ScalarField>
|
// Converts PrimeField values to BigInt for computing witness.
|
||||||
where
|
let input_num_bigint = z_i
|
||||||
E: Pairing,
|
|
||||||
{
|
|
||||||
// Opens the .r1cs file and create a reader.
|
|
||||||
let file = File::open(&self.r1cs_filepath)?;
|
|
||||||
let reader = BufReader::new(file);
|
|
||||||
|
|
||||||
// Reads the R1CS file and extract the constraints directly.
|
|
||||||
let r1cs_file = r1cs_reader::R1CSFile::<E>::new(reader)?;
|
|
||||||
let pub_io_len = (r1cs_file.header.n_pub_in + r1cs_file.header.n_pub_out) as usize;
|
|
||||||
let r1cs = r1cs_reader::R1CS::<E>::from(r1cs_file);
|
|
||||||
let num_variables = r1cs.num_variables;
|
|
||||||
let constraints: Vec<Constraints<E::ScalarField>> = r1cs.constraints;
|
|
||||||
|
|
||||||
Ok((constraints, pub_io_len, num_variables))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a set of constraints from ark-circom into R1CS format of folding-schemes.
|
|
||||||
pub fn convert_to_folding_schemes_r1cs<F>(
|
|
||||||
&self,
|
|
||||||
constraints: Vec<Constraints<F>>,
|
|
||||||
pub_io_len: usize,
|
|
||||||
num_variables: usize,
|
|
||||||
) -> R1CS<F>
|
|
||||||
where
|
|
||||||
F: PrimeField,
|
|
||||||
{
|
|
||||||
let mut a_matrix: Vec<Vec<(F, usize)>> = Vec::new();
|
|
||||||
let mut b_matrix: Vec<Vec<(F, usize)>> = Vec::new();
|
|
||||||
let mut c_matrix: Vec<Vec<(F, usize)>> = Vec::new();
|
|
||||||
|
|
||||||
let n_rows = constraints.len();
|
|
||||||
|
|
||||||
for (ai, bi, ci) in constraints {
|
|
||||||
a_matrix.push(
|
|
||||||
ai.into_iter()
|
|
||||||
.map(|(index, scalar)| (scalar, index))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
b_matrix.push(
|
|
||||||
bi.into_iter()
|
|
||||||
.map(|(index, scalar)| (scalar, index))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
c_matrix.push(
|
|
||||||
ci.into_iter()
|
|
||||||
.map(|(index, scalar)| (scalar, index))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let l = pub_io_len;
|
|
||||||
let n_cols = num_variables;
|
|
||||||
|
|
||||||
let A = SparseMatrix {
|
|
||||||
n_rows,
|
|
||||||
n_cols,
|
|
||||||
coeffs: a_matrix,
|
|
||||||
};
|
|
||||||
let B = SparseMatrix {
|
|
||||||
n_rows,
|
|
||||||
n_cols,
|
|
||||||
coeffs: b_matrix,
|
|
||||||
};
|
|
||||||
let C = SparseMatrix {
|
|
||||||
n_rows,
|
|
||||||
n_cols,
|
|
||||||
coeffs: c_matrix,
|
|
||||||
};
|
|
||||||
|
|
||||||
R1CS::<F> { l, A, B, C }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates the witness given the Wasm filepath and inputs.
|
|
||||||
pub fn calculate_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<BigInt>> {
|
|
||||||
let mut calculator = WitnessCalculator::new(&self.wasm_filepath)?;
|
|
||||||
calculator.calculate_witness(inputs.iter().cloned(), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a num_bigint input to `PrimeField`'s BigInt.
|
|
||||||
pub fn num_bigint_to_ark_bigint<F: PrimeField>(
|
|
||||||
&self,
|
|
||||||
value: &BigInt,
|
|
||||||
) -> Result<F::BigInt, Box<dyn Error>> {
|
|
||||||
let big_uint = value
|
|
||||||
.to_biguint()
|
|
||||||
.ok_or_else(|| "BigInt is negative".to_string())?;
|
|
||||||
F::BigInt::try_from(big_uint).map_err(|_| "BigInt conversion failed".to_string().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts R1CS constraints and witness from ark-circom format
|
|
||||||
// into folding-schemes R1CS and z format.
|
|
||||||
pub fn circom_to_folding_schemes_r1cs_and_z<F>(
|
|
||||||
&self,
|
|
||||||
constraints: Vec<Constraints<F>>,
|
|
||||||
witness: &[BigInt],
|
|
||||||
pub_io_len: usize,
|
|
||||||
num_variables: usize,
|
|
||||||
) -> Result<(R1CS<F>, Vec<F>), Box<dyn Error>>
|
|
||||||
where
|
|
||||||
F: PrimeField,
|
|
||||||
{
|
|
||||||
let folding_schemes_r1cs =
|
|
||||||
self.convert_to_folding_schemes_r1cs(constraints, pub_io_len, num_variables);
|
|
||||||
|
|
||||||
let z: Result<Vec<F>, _> = witness
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|big_int| {
|
.map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val))
|
||||||
let ark_big_int = self.num_bigint_to_ark_bigint::<F>(big_int)?;
|
.collect::<Vec<BigInt>>();
|
||||||
F::from_bigint(ark_big_int).ok_or_else(|| {
|
|
||||||
"Failed to convert bigint to field element"
|
|
||||||
.to_string()
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
z.map(|z| (folding_schemes_r1cs, z))
|
// Computes witness
|
||||||
|
let witness = self
|
||||||
|
.circom_wrapper
|
||||||
|
.extract_witness(&[("ivc_input".to_string(), input_num_bigint)])
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Extracts the z_i1(next state) from the witness vector.
|
||||||
|
let z_i1 = witness[1..1 + self.state_len()].to_vec();
|
||||||
|
Ok(z_i1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_step_constraints(
|
||||||
|
&self,
|
||||||
|
cs: ConstraintSystemRef<F>,
|
||||||
|
_i: usize,
|
||||||
|
z_i: Vec<FpVar<F>>,
|
||||||
|
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||||
|
let mut input_values = Vec::new();
|
||||||
|
// Converts each FpVar to PrimeField value, then to num_bigint::BigInt.
|
||||||
|
for fp_var in z_i.iter() {
|
||||||
|
// Extracts the PrimeField value from FpVar.
|
||||||
|
let primefield_value = fp_var.value()?;
|
||||||
|
// Converts the PrimeField value to num_bigint::BigInt.
|
||||||
|
let num_bigint_value = self
|
||||||
|
.circom_wrapper
|
||||||
|
.ark_primefield_to_num_bigint(primefield_value);
|
||||||
|
input_values.push(num_bigint_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_bigint_inputs = vec![("ivc_input".to_string(), input_values)];
|
||||||
|
|
||||||
|
// Extracts R1CS and witness.
|
||||||
|
let (r1cs, witness) = self
|
||||||
|
.circom_wrapper
|
||||||
|
.extract_r1cs_and_witness(&num_bigint_inputs)
|
||||||
|
.map_err(|_| SynthesisError::AssignmentMissing)?;
|
||||||
|
|
||||||
|
// Initializes the CircomCircuit.
|
||||||
|
let circom_circuit = CircomCircuit {
|
||||||
|
r1cs,
|
||||||
|
witness: witness.clone(),
|
||||||
|
inputs_already_computed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generates the constraints for the circom_circuit.
|
||||||
|
circom_circuit
|
||||||
|
.generate_constraints(cs.clone())
|
||||||
|
.map_err(|_| SynthesisError::AssignmentMissing)?;
|
||||||
|
|
||||||
|
// Checks for constraint satisfaction.
|
||||||
|
if !cs.is_satisfied().unwrap() {
|
||||||
|
return Err(SynthesisError::Unsatisfiable);
|
||||||
|
}
|
||||||
|
|
||||||
|
let w = witness.ok_or(SynthesisError::Unsatisfiable)?;
|
||||||
|
|
||||||
|
// Extracts the z_i1(next state) from the witness vector.
|
||||||
|
let z_i1: Vec<FpVar<F>> =
|
||||||
|
Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(w[1..1 + self.state_len()].to_vec()))?;
|
||||||
|
|
||||||
|
Ok(z_i1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use crate::frontend::circom::CircomWrapper;
|
use super::*;
|
||||||
use ark_bn254::Bn254;
|
use ark_pallas::Fr;
|
||||||
use num_bigint::BigInt;
|
use ark_r1cs_std::alloc::AllocVar;
|
||||||
use std::env;
|
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||||
|
|
||||||
/*
|
|
||||||
test_circuit represents 35 = x^3 + x + 5 in test_folder/test_circuit.circom.
|
|
||||||
In the test_circom_conversion_success function, x is assigned the value 3, which satisfies the R1CS correctly.
|
|
||||||
In the test_circom_conversion_failure function, x is assigned the value 6, which doesn't satisfy the R1CS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
To generate .r1cs and .wasm files, run the below command in the terminal.
|
|
||||||
bash ./src/frontend/circom/test_folder/compile.sh
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn test_circom_conversion_logic(expect_success: bool, inputs: Vec<(String, Vec<BigInt>)>) {
|
|
||||||
let current_dir = env::current_dir().unwrap();
|
|
||||||
let base_path = current_dir.join("src/frontend/circom/test_folder");
|
|
||||||
|
|
||||||
let r1cs_filepath = base_path.join("test_circuit.r1cs");
|
|
||||||
let wasm_filepath = base_path.join("test_circuit_js/test_circuit.wasm");
|
|
||||||
|
|
||||||
assert!(r1cs_filepath.exists());
|
|
||||||
assert!(wasm_filepath.exists());
|
|
||||||
|
|
||||||
let circom_wrapper = CircomWrapper::<Bn254>::new(r1cs_filepath, wasm_filepath);
|
|
||||||
|
|
||||||
let (r1cs, z) = circom_wrapper
|
|
||||||
.extract_r1cs_and_z(&inputs)
|
|
||||||
.expect("Error processing input");
|
|
||||||
|
|
||||||
// Checks the relationship of R1CS.
|
|
||||||
let check_result = std::panic::catch_unwind(|| {
|
|
||||||
r1cs.check_relation(&z).unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
match (expect_success, check_result) {
|
|
||||||
(true, Ok(_)) => {}
|
|
||||||
(false, Err(_)) => {}
|
|
||||||
(true, Err(_)) => panic!("Expected success but got a failure."),
|
|
||||||
(false, Ok(_)) => panic!("Expected a failure but got success."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Tests the step_native function of CircomFCircuit.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_circom_conversion() {
|
fn test_circom_step_native() {
|
||||||
// expect it to pass for correct inputs.
|
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
||||||
test_circom_conversion_logic(true, vec![("step_in".to_string(), vec![BigInt::from(3)])]);
|
let wasm_path =
|
||||||
|
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||||
|
|
||||||
// expect it to fail for incorrect inputs.
|
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path));
|
||||||
test_circom_conversion_logic(false, vec![("step_in".to_string(), vec![BigInt::from(7)])]);
|
|
||||||
|
let z_i = vec![Fr::from(3u32)];
|
||||||
|
let z_i1 = circom_fcircuit.step_native(1, z_i).unwrap();
|
||||||
|
assert_eq!(z_i1, vec![Fr::from(35u32)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the generate_step_constraints function of CircomFCircuit.
|
||||||
|
#[test]
|
||||||
|
fn test_circom_step_constraints() {
|
||||||
|
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
||||||
|
let wasm_path =
|
||||||
|
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||||
|
|
||||||
|
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path));
|
||||||
|
|
||||||
|
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||||
|
|
||||||
|
let z_i = vec![Fr::from(3u32)];
|
||||||
|
|
||||||
|
let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
|
||||||
|
|
||||||
|
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||||
|
let z_i1_var = circom_fcircuit
|
||||||
|
.generate_step_constraints(cs.clone(), 1, z_i_var)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the WrapperCircuit with CircomFCircuit.
|
||||||
|
#[test]
|
||||||
|
fn test_wrapper_circomtofcircuit() {
|
||||||
|
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
||||||
|
let wasm_path =
|
||||||
|
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||||
|
|
||||||
|
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path));
|
||||||
|
|
||||||
|
// Allocates z_i1 by using step_native function.
|
||||||
|
let z_i = vec![Fr::from(3_u32)];
|
||||||
|
let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
|
||||||
|
FC: circom_fcircuit.clone(),
|
||||||
|
z_i: Some(z_i.clone()),
|
||||||
|
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone()).unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||||
|
|
||||||
|
wrapper_circuit.generate_constraints(cs.clone()).unwrap();
|
||||||
|
assert!(
|
||||||
|
cs.is_satisfied().unwrap(),
|
||||||
|
"Constraint system is not satisfied"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
folding-schemes/src/frontend/circom/test_folder/compile.sh
Normal file → Executable file
2
folding-schemes/src/frontend/circom/test_folder/compile.sh
Normal file → Executable file
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
circom ./folding-schemes/src/frontend/circom/test_folder/test_circuit.circom --r1cs --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/
|
circom ./folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom --r1cs --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
pragma circom 2.0.3;
|
||||||
|
|
||||||
|
template Example () {
|
||||||
|
signal input ivc_input[1];
|
||||||
|
signal output ivc_output[1];
|
||||||
|
signal temp;
|
||||||
|
|
||||||
|
temp <== ivc_input[0] * ivc_input[0];
|
||||||
|
ivc_output[0] <== temp * ivc_input[0] + ivc_input[0] + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
component main {public [ivc_input]} = Example();
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
pragma circom 2.0.3;
|
|
||||||
|
|
||||||
template Example () {
|
|
||||||
signal input step_in;
|
|
||||||
signal output step_out;
|
|
||||||
signal temp;
|
|
||||||
|
|
||||||
temp <== step_in * step_in;
|
|
||||||
step_out <== temp * step_in + step_in + 5;
|
|
||||||
step_out === 35;
|
|
||||||
}
|
|
||||||
|
|
||||||
component main = Example();
|
|
||||||
149
folding-schemes/src/frontend/circom/utils.rs
Normal file
149
folding-schemes/src/frontend/circom/utils.rs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
use ark_circom::{
|
||||||
|
circom::{r1cs_reader, R1CS},
|
||||||
|
WitnessCalculator,
|
||||||
|
};
|
||||||
|
use ark_ff::{BigInteger, PrimeField};
|
||||||
|
use color_eyre::Result;
|
||||||
|
use num_bigint::{BigInt, Sign};
|
||||||
|
use std::{fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses
|
||||||
|
// based on file paths to Circom's .r1cs and .wasm.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CircomWrapper<F: PrimeField> {
|
||||||
|
r1cs_filepath: PathBuf,
|
||||||
|
wasm_filepath: PathBuf,
|
||||||
|
_marker: PhantomData<F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: PrimeField> CircomWrapper<F> {
|
||||||
|
// Creates a new instance of the CircomWrapper with the file paths.
|
||||||
|
pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
|
||||||
|
CircomWrapper {
|
||||||
|
r1cs_filepath,
|
||||||
|
wasm_filepath,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregated function to obtain R1CS and witness from Circom.
|
||||||
|
pub fn extract_r1cs_and_witness(
|
||||||
|
&self,
|
||||||
|
inputs: &[(String, Vec<BigInt>)],
|
||||||
|
) -> Result<(R1CS<F>, Option<Vec<F>>), Error> {
|
||||||
|
// Extracts the R1CS
|
||||||
|
let file = File::open(&self.r1cs_filepath)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
|
||||||
|
let r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
|
||||||
|
|
||||||
|
// Extracts the witness vector
|
||||||
|
let witness_vec = self.extract_witness(inputs)?;
|
||||||
|
|
||||||
|
Ok((r1cs, Some(witness_vec)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts the witness vector as a vector of PrimeField elements.
|
||||||
|
pub fn extract_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<F>, Error> {
|
||||||
|
let witness_bigint = self.calculate_witness(inputs)?;
|
||||||
|
|
||||||
|
witness_bigint
|
||||||
|
.iter()
|
||||||
|
.map(|big_int| {
|
||||||
|
self.num_bigint_to_ark_bigint(big_int)
|
||||||
|
.and_then(|ark_big_int| {
|
||||||
|
F::from_bigint(ark_big_int)
|
||||||
|
.ok_or_else(|| Error::Other("could not get F from bigint".to_string()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the witness given the Wasm filepath and inputs.
|
||||||
|
pub fn calculate_witness(
|
||||||
|
&self,
|
||||||
|
inputs: &[(String, Vec<BigInt>)],
|
||||||
|
) -> Result<Vec<BigInt>, Error> {
|
||||||
|
let mut calculator = WitnessCalculator::new(&self.wasm_filepath).map_err(|e| {
|
||||||
|
Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e))
|
||||||
|
})?;
|
||||||
|
calculator
|
||||||
|
.calculate_witness(inputs.iter().cloned(), true)
|
||||||
|
.map_err(|e| {
|
||||||
|
Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a num_bigint::BigInt to a PrimeField::BigInt.
|
||||||
|
pub fn num_bigint_to_ark_bigint(&self, value: &BigInt) -> Result<F::BigInt, Error> {
|
||||||
|
let big_uint = value
|
||||||
|
.to_biguint()
|
||||||
|
.ok_or_else(|| Error::BigIntConversionError("BigInt is negative".to_string()))?;
|
||||||
|
F::BigInt::try_from(big_uint).map_err(|_| {
|
||||||
|
Error::BigIntConversionError("Failed to convert to PrimeField::BigInt".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a PrimeField element to a num_bigint::BigInt representation.
|
||||||
|
pub fn ark_primefield_to_num_bigint(&self, value: F) -> BigInt {
|
||||||
|
let primefield_bigint: F::BigInt = value.into_bigint();
|
||||||
|
let bytes = primefield_bigint.to_bytes_be();
|
||||||
|
BigInt::from_bytes_be(Sign::Plus, &bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ark_bn254::Fr;
|
||||||
|
use ark_circom::circom::{CircomBuilder, CircomConfig};
|
||||||
|
use ark_circom::CircomCircuit;
|
||||||
|
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
//To generate .r1cs and .wasm files, run the below command in the terminal.
|
||||||
|
//bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh
|
||||||
|
|
||||||
|
// Test the satisfication by using the CircomBuilder of circom-compat
|
||||||
|
#[test]
|
||||||
|
fn test_circombuilder_satisfied() {
|
||||||
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
|
"./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm",
|
||||||
|
"./src/frontend/circom/test_folder/cubic_circuit.r1cs",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut builder = CircomBuilder::new(cfg);
|
||||||
|
builder.push_input("ivc_input", 3);
|
||||||
|
|
||||||
|
let circom = builder.build().unwrap();
|
||||||
|
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||||
|
circom.generate_constraints(cs.clone()).unwrap();
|
||||||
|
assert!(cs.is_satisfied().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the satisfication by using the CircomWrapper
|
||||||
|
#[test]
|
||||||
|
fn test_extract_r1cs_and_witness() {
|
||||||
|
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
||||||
|
let wasm_path =
|
||||||
|
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||||
|
|
||||||
|
let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])];
|
||||||
|
let wrapper = CircomWrapper::<Fr>::new(r1cs_path, wasm_path);
|
||||||
|
|
||||||
|
let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();
|
||||||
|
|
||||||
|
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||||
|
|
||||||
|
let circom_circuit = CircomCircuit {
|
||||||
|
r1cs,
|
||||||
|
witness,
|
||||||
|
inputs_already_computed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
circom_circuit.generate_constraints(cs.clone()).unwrap();
|
||||||
|
assert!(cs.is_satisfied().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
pub mod circom;
|
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use ark_ff::PrimeField;
|
use ark_ff::PrimeField;
|
||||||
use ark_r1cs_std::fields::fp::FpVar;
|
use ark_r1cs_std::fields::fp::FpVar;
|
||||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||||
use ark_std::fmt::Debug;
|
use ark_std::fmt::Debug;
|
||||||
|
|
||||||
|
pub mod circom;
|
||||||
|
|
||||||
/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
|
/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
|
||||||
/// inside the agmented F' function).
|
/// inside the agmented F' function).
|
||||||
/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying
|
/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying
|
||||||
@@ -44,7 +44,7 @@ pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ark_pallas::Fr;
|
use ark_bn254::Fr;
|
||||||
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
|
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
|
||||||
use ark_relations::r1cs::{
|
use ark_relations::r1cs::{
|
||||||
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
|
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ pub enum Error {
|
|||||||
ArithError(#[from] utils::espresso::virtual_polynomial::ArithErrors),
|
ArithError(#[from] utils::espresso::virtual_polynomial::ArithErrors),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError),
|
ProtoGalaxy(folding::protogalaxy::ProtoGalaxyError),
|
||||||
|
#[error("std::io::Error")]
|
||||||
|
IOError(#[from] std::io::Error),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
|
|
||||||
@@ -86,6 +88,10 @@ pub enum Error {
|
|||||||
NotSupported(String),
|
NotSupported(String),
|
||||||
#[error("max i-th step reached (usize limit reached)")]
|
#[error("max i-th step reached (usize limit reached)")]
|
||||||
MaxStep,
|
MaxStep,
|
||||||
|
#[error("Circom Witness calculation error: {0}")]
|
||||||
|
WitnessCalculationError(String),
|
||||||
|
#[error("BigInt to PrimeField conversion error: {0}")]
|
||||||
|
BigIntConversionError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined
|
/// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined
|
||||||
|
|||||||
Reference in New Issue
Block a user