mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-08 15:01:30 +01:00
Move the experimental frontends into a separate crate, so that when not using them they don't take several minutes to compile (and indirect dependencies). (#168)
This saves several minutes (and MBs of data) on compilation time both when running tests in this repo, but also when using the sonobe lib as a dependency in external repos.
This commit is contained in:
33
frontends/Cargo.toml
Normal file
33
frontends/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "frontends"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ark-ff = { version = "^0.4.0", default-features = false, features = ["parallel", "asm"] }
|
||||
ark-std = { version = "^0.4.0", default-features = false, features = ["parallel"] }
|
||||
ark-relations = { version = "^0.4.0", default-features = false }
|
||||
# ark-r1cs-std is patched at the workspace level
|
||||
ark-r1cs-std = { version = "0.4.0", default-features = false, features = ["parallel"] }
|
||||
ark-serialize = { version = "^0.4.0", default-features = false }
|
||||
ark-circom = { git = "https://github.com/arnaucube/circom-compat", default-features = false }
|
||||
num-bigint = "0.4"
|
||||
ark-noname = { git = "https://github.com/dmpierre/ark-noname", branch = "feat/sonobe-integration" }
|
||||
noname = { git = "https://github.com/dmpierre/noname" }
|
||||
serde_json = "1.0.85" # to (de)serialize JSON
|
||||
acvm = { git = "https://github.com/noir-lang/noir", rev="2b4853e", default-features = false }
|
||||
noir_arkworks_backend = { package="arkworks_backend", git = "https://github.com/dmpierre/arkworks_backend", branch = "feat/sonobe-integration" }
|
||||
folding-schemes = { path = "../folding-schemes/"}
|
||||
|
||||
[dev-dependencies]
|
||||
ark-bn254 = {version="0.4.0", features=["r1cs"]}
|
||||
|
||||
# This allows the crate to be built when targeting WASM.
|
||||
# See more at: https://docs.rs/getrandom/#webassembly-support
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
[features]
|
||||
default = ["ark-circom/default", "parallel"]
|
||||
parallel = []
|
||||
wasm = ["ark-circom/wasm"]
|
||||
18
frontends/README.md
Normal file
18
frontends/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# frontends
|
||||
|
||||
This crate contains *experimental frontends* for Sonobe.
|
||||
The recommended frontend is to directly use [arkworks](https://github.com/arkworks-rs) to define the FCircuit, just following the [`FCircuit` trait](https://github.com/privacy-scaling-explorations/sonobe/blob/main/folding-schemes/src/frontend/mod.rs).
|
||||
|
||||
## Experimental frontends
|
||||
> Warning: the following frontends are experimental and some computational and time overhead is expected when using them compared to directly using the [arkworks frontend](https://github.com/privacy-scaling-explorations/sonobe/blob/main/folding-schemes/src/frontend/mod.rs).
|
||||
|
||||
Available experimental frontends:
|
||||
- [Circom](https://github.com/iden3/circom), iden3, 0Kims Association. Supported version`<=v2.1.9`.
|
||||
- [Noir](https://github.com/noir-lang/noir), Aztec.
|
||||
- [Noname](https://github.com/zksecurity/noname), zkSecurity. Partially supported.
|
||||
|
||||
|
||||
Documentation about frontend interface and experimental frontends: https://privacy-scaling-explorations.github.io/sonobe-docs/usage/frontend.html
|
||||
|
||||
## Implementing new frontends
|
||||
Support for new frontends can be added (even from outside this repo) by implementing the [`FCircuit` trait](https://github.com/privacy-scaling-explorations/sonobe/blob/main/folding-schemes/src/frontend/mod.rs).
|
||||
380
frontends/src/circom/mod.rs
Normal file
380
frontends/src/circom/mod.rs
Normal file
@@ -0,0 +1,380 @@
|
||||
use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_r1cs_std::fields::fp::FpVar::Var;
|
||||
use ark_r1cs_std::R1CSVar;
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::fmt::Debug;
|
||||
use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error};
|
||||
use num_bigint::BigInt;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, usize};
|
||||
|
||||
pub mod utils;
|
||||
use utils::CircomWrapper;
|
||||
|
||||
type ClosurePointer<F> = Rc<dyn Fn(usize, Vec<F>, Vec<F>) -> Result<Vec<F>, Error>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CustomStepNative<F: PrimeField> {
|
||||
func: ClosurePointer<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> fmt::Debug for CustomStepNative<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Function pointer: {:?}",
|
||||
std::any::type_name::<fn(usize, Vec<F>, Vec<F>) -> Result<Vec<F>, Error>>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define CircomFCircuit
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CircomFCircuit<F: PrimeField> {
|
||||
circom_wrapper: CircomWrapper<F>,
|
||||
pub state_len: usize,
|
||||
pub external_inputs_len: usize,
|
||||
r1cs: CircomR1CS<F>,
|
||||
custom_step_native_code: Option<CustomStepNative<F>>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> CircomFCircuit<F> {
|
||||
pub fn set_custom_step_native(&mut self, func: ClosurePointer<F>) {
|
||||
self.custom_step_native_code = Some(CustomStepNative::<F> { func });
|
||||
}
|
||||
|
||||
pub fn execute_custom_step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
if let Some(code) = &self.custom_step_native_code {
|
||||
(code.func)(_i, z_i, external_inputs)
|
||||
} else {
|
||||
#[cfg(test)]
|
||||
assert_eq!(z_i.len(), self.state_len());
|
||||
#[cfg(test)]
|
||||
assert_eq!(external_inputs.len(), self.external_inputs_len());
|
||||
|
||||
let inputs_bi = z_i
|
||||
.iter()
|
||||
.map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val))
|
||||
.collect::<Vec<BigInt>>();
|
||||
let mut inputs_map = vec![("ivc_input".to_string(), inputs_bi)];
|
||||
|
||||
if self.external_inputs_len() > 0 {
|
||||
let external_inputs_bi = external_inputs
|
||||
.iter()
|
||||
.map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val))
|
||||
.collect::<Vec<BigInt>>();
|
||||
inputs_map.push(("external_inputs".to_string(), external_inputs_bi));
|
||||
}
|
||||
|
||||
// Computes witness
|
||||
let witness = self
|
||||
.circom_wrapper
|
||||
.extract_witness(&inputs_map)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> {
|
||||
/// (r1cs_path, wasm_path, state_len, external_inputs_len)
|
||||
type Params = (PathOrBin, PathOrBin, usize, usize);
|
||||
|
||||
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||
let (r1cs_path, wasm_path, state_len, external_inputs_len) = params;
|
||||
let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path)?;
|
||||
|
||||
let r1cs = circom_wrapper.extract_r1cs()?;
|
||||
Ok(Self {
|
||||
circom_wrapper,
|
||||
state_len,
|
||||
external_inputs_len,
|
||||
r1cs,
|
||||
custom_step_native_code: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn state_len(&self) -> usize {
|
||||
self.state_len
|
||||
}
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
self.external_inputs_len
|
||||
}
|
||||
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
self.execute_custom_step_native(_i, z_i, external_inputs)
|
||||
}
|
||||
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
#[cfg(test)]
|
||||
assert_eq!(z_i.len(), self.state_len());
|
||||
#[cfg(test)]
|
||||
assert_eq!(external_inputs.len(), self.external_inputs_len());
|
||||
|
||||
let input_values = self.fpvars_to_bigints(&z_i)?;
|
||||
let mut inputs_map = vec![("ivc_input".to_string(), input_values)];
|
||||
|
||||
if self.external_inputs_len() > 0 {
|
||||
let external_inputs_bi = self.fpvars_to_bigints(&external_inputs)?;
|
||||
inputs_map.push(("external_inputs".to_string(), external_inputs_bi));
|
||||
}
|
||||
|
||||
let witness = self
|
||||
.circom_wrapper
|
||||
.extract_witness(&inputs_map)
|
||||
.map_err(|_| SynthesisError::AssignmentMissing)?;
|
||||
|
||||
// Since public inputs are already allocated variables, we will tell `circom-compat` to not re-allocate those
|
||||
let mut already_allocated_public_inputs = vec![];
|
||||
for var in z_i.iter() {
|
||||
match var {
|
||||
Var(var) => already_allocated_public_inputs.push(var.variable),
|
||||
_ => return Err(SynthesisError::Unsatisfiable), // allocated z_i should be Var
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the CircomCircuit.
|
||||
let circom_circuit = CircomCircuit {
|
||||
r1cs: self.r1cs.clone(),
|
||||
witness: Some(witness.clone()),
|
||||
public_inputs_indexes: already_allocated_public_inputs,
|
||||
allocate_inputs_as_witnesses: true,
|
||||
};
|
||||
|
||||
// Generates the constraints for the circom_circuit.
|
||||
circom_circuit.generate_constraints(cs.clone())?;
|
||||
|
||||
// TODO: https://github.com/privacy-scaling-explorations/sonobe/issues/104
|
||||
// We disable checking constraints for now
|
||||
// Checks for constraint satisfaction.
|
||||
// if !cs.is_satisfied().unwrap() {
|
||||
// return Err(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(witness[1..1 + self.state_len()].to_vec())
|
||||
})?;
|
||||
|
||||
Ok(z_i1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> CircomFCircuit<F> {
|
||||
fn fpvars_to_bigints(&self, fpvars: &[FpVar<F>]) -> Result<Vec<BigInt>, SynthesisError> {
|
||||
let mut input_values = Vec::new();
|
||||
// converts each FpVar to PrimeField value, then to num_bigint::BigInt.
|
||||
for fp_var in fpvars.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);
|
||||
}
|
||||
Ok(input_values)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_bn254::Fr;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Tests the step_native function of CircomFCircuit.
|
||||
#[test]
|
||||
fn test_circom_step_native() {
|
||||
let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs");
|
||||
let wasm_path =
|
||||
PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||
|
||||
let circom_fcircuit =
|
||||
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
|
||||
|
||||
let z_i = vec![Fr::from(3u32)];
|
||||
let z_i1 = circom_fcircuit.step_native(1, z_i, vec![]).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/circom/test_folder/cubic_circuit.r1cs");
|
||||
let wasm_path =
|
||||
PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||
|
||||
let circom_fcircuit =
|
||||
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
|
||||
|
||||
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 z_i1_var = circom_fcircuit
|
||||
.generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
|
||||
.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/circom/test_folder/cubic_circuit.r1cs");
|
||||
let wasm_path =
|
||||
PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||
|
||||
let circom_fcircuit =
|
||||
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
|
||||
|
||||
// Allocates z_i1 by using step_native function.
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let wrapper_circuit = folding_schemes::frontend::utils::WrapperCircuit {
|
||||
FC: circom_fcircuit.clone(),
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
|
||||
};
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
wrapper_circuit.generate_constraints(cs.clone()).unwrap();
|
||||
assert!(
|
||||
cs.is_satisfied().unwrap(),
|
||||
"Constraint system is not satisfied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circom_external_inputs() {
|
||||
let r1cs_path = PathBuf::from("./src/circom/test_folder/with_external_inputs.r1cs");
|
||||
let wasm_path = PathBuf::from(
|
||||
"./src/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
|
||||
);
|
||||
let circom_fcircuit =
|
||||
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let z_i = vec![Fr::from(3u32)];
|
||||
let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)];
|
||||
|
||||
// run native step
|
||||
let z_i1_native = circom_fcircuit
|
||||
.step_native(1, z_i.clone(), external_inputs.clone())
|
||||
.unwrap();
|
||||
|
||||
// run gadget step
|
||||
let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
|
||||
let external_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs.clone())).unwrap();
|
||||
let z_i1_var = circom_fcircuit
|
||||
.generate_step_constraints(cs.clone(), 1, z_i_var, external_inputs_var)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(z_i1_var.value().unwrap(), z_i1_native);
|
||||
|
||||
// re-init cs and run gadget step with wrong ivc inputs (first ivc should not be zero)
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let wrong_z_i = vec![Fr::from(0)];
|
||||
let wrong_z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(wrong_z_i)).unwrap();
|
||||
let external_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap();
|
||||
let _z_i1_var = circom_fcircuit.generate_step_constraints(
|
||||
cs.clone(),
|
||||
1,
|
||||
wrong_z_i_var,
|
||||
external_inputs_var,
|
||||
);
|
||||
// TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104
|
||||
// Disable check for now
|
||||
// assert!(z_i1_var.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_circom_no_external_inputs() {
|
||||
let r1cs_path = PathBuf::from("./src/circom/test_folder/no_external_inputs.r1cs");
|
||||
let wasm_path =
|
||||
PathBuf::from("./src/circom/test_folder/no_external_inputs_js/no_external_inputs.wasm");
|
||||
let circom_fcircuit =
|
||||
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 3, 0)).unwrap();
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let z_i = vec![Fr::from(3u32), Fr::from(4u32), Fr::from(5u32)];
|
||||
let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
|
||||
|
||||
// run native step
|
||||
let z_i1_native = circom_fcircuit.step_native(1, z_i.clone(), vec![]).unwrap();
|
||||
|
||||
// run gadget step
|
||||
let z_i1_var = circom_fcircuit
|
||||
.generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(z_i1_var.value().unwrap(), z_i1_native);
|
||||
|
||||
// re-init cs and run gadget step with wrong ivc inputs (first ivc input should not be zero)
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let wrong_z_i = vec![Fr::from(0u32), Fr::from(4u32), Fr::from(5u32)];
|
||||
let wrong_z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(wrong_z_i)).unwrap();
|
||||
let _z_i1_var =
|
||||
circom_fcircuit.generate_step_constraints(cs.clone(), 1, wrong_z_i_var, vec![]);
|
||||
// TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104
|
||||
// Disable check for now
|
||||
// assert!(z_i1_var.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_code() {
|
||||
let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs");
|
||||
let wasm_path =
|
||||
PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||
|
||||
let mut circom_fcircuit =
|
||||
CircomFCircuit::<Fr>::new((r1cs_path.into(), wasm_path.into(), 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
|
||||
|
||||
circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
|
||||
let z = z_i[0];
|
||||
Ok(vec![z * z * z + z + Fr::from(5)])
|
||||
}));
|
||||
|
||||
// Allocates z_i1 by using step_native function.
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let wrapper_circuit = folding_schemes::frontend::utils::WrapperCircuit {
|
||||
FC: circom_fcircuit.clone(),
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
|
||||
};
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
wrapper_circuit.generate_constraints(cs.clone()).unwrap();
|
||||
assert!(
|
||||
cs.is_satisfied().unwrap(),
|
||||
"Constraint system is not satisfied"
|
||||
);
|
||||
}
|
||||
}
|
||||
14
frontends/src/circom/test_folder/circuits/is_zero.circom
Normal file
14
frontends/src/circom/test_folder/circuits/is_zero.circom
Normal file
@@ -0,0 +1,14 @@
|
||||
pragma circom 2.0.0;
|
||||
// From: https://github.com/iden3/circomlib/blob/master/circuits/comparators.circom
|
||||
|
||||
template IsZero() {
|
||||
signal input in;
|
||||
signal output out;
|
||||
|
||||
signal inv;
|
||||
|
||||
inv <-- in!=0 ? 1/in : 0;
|
||||
|
||||
out <== -in*inv +1;
|
||||
in*out === 0;
|
||||
}
|
||||
4
frontends/src/circom/test_folder/compile.sh
Executable file
4
frontends/src/circom/test_folder/compile.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
circom ./frontends/src/circom/test_folder/cubic_circuit.circom --r1cs --sym --wasm --prime bn128 --output ./frontends/src/circom/test_folder/
|
||||
circom ./frontends/src/circom/test_folder/with_external_inputs.circom --r1cs --sym --wasm --prime bn128 --output ./frontends/src/circom/test_folder/
|
||||
circom ./frontends/src/circom/test_folder/no_external_inputs.circom --r1cs --sym --wasm --prime bn128 --output ./frontends/src/circom/test_folder/
|
||||
12
frontends/src/circom/test_folder/cubic_circuit.circom
Normal file
12
frontends/src/circom/test_folder/cubic_circuit.circom
Normal file
@@ -0,0 +1,12 @@
|
||||
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();
|
||||
23
frontends/src/circom/test_folder/no_external_inputs.circom
Normal file
23
frontends/src/circom/test_folder/no_external_inputs.circom
Normal file
@@ -0,0 +1,23 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "./circuits/is_zero.circom";
|
||||
|
||||
template NoExternalInputs () {
|
||||
signal input ivc_input[3];
|
||||
signal output ivc_output[3];
|
||||
|
||||
component check_input = IsZero();
|
||||
check_input.in <== ivc_input[0];
|
||||
check_input.out === 0;
|
||||
|
||||
signal temp1;
|
||||
signal temp2;
|
||||
|
||||
temp1 <== ivc_input[0] * ivc_input[1];
|
||||
temp2 <== temp1 * ivc_input[2];
|
||||
ivc_output[0] <== temp1 * ivc_input[0];
|
||||
ivc_output[1] <== temp1 * ivc_input[1] + temp1;
|
||||
ivc_output[2] <== temp1 * ivc_input[2] + temp2;
|
||||
}
|
||||
|
||||
component main {public [ivc_input]} = NoExternalInputs();
|
||||
22
frontends/src/circom/test_folder/with_external_inputs.circom
Normal file
22
frontends/src/circom/test_folder/with_external_inputs.circom
Normal file
@@ -0,0 +1,22 @@
|
||||
pragma circom 2.0.3;
|
||||
|
||||
include "./circuits/is_zero.circom";
|
||||
|
||||
template WithExternalInputs () {
|
||||
signal input ivc_input[1];
|
||||
signal input external_inputs[2];
|
||||
signal output ivc_output[1];
|
||||
|
||||
component check_input = IsZero();
|
||||
check_input.in <== ivc_input[0];
|
||||
check_input.out === 0;
|
||||
|
||||
signal temp1;
|
||||
signal temp2;
|
||||
|
||||
temp1 <== ivc_input[0] * ivc_input[0];
|
||||
temp2 <== ivc_input[0] * external_inputs[0];
|
||||
ivc_output[0] <== temp1 * ivc_input[0] + temp2 + external_inputs[1];
|
||||
}
|
||||
|
||||
component main {public [ivc_input]} = WithExternalInputs();
|
||||
179
frontends/src/circom/utils.rs
Normal file
179
frontends/src/circom/utils.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use ark_circom::{
|
||||
circom::{r1cs_reader, R1CS},
|
||||
WitnessCalculator,
|
||||
};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_serialize::Read;
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use std::{fs::File, io::Cursor, marker::PhantomData, path::PathBuf};
|
||||
|
||||
use folding_schemes::{utils::PathOrBin, 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> {
|
||||
r1csfile_bytes: Vec<u8>,
|
||||
wasmfile_bytes: Vec<u8>,
|
||||
_marker: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> CircomWrapper<F> {
|
||||
// Creates a new instance of the CircomWrapper with the file paths.
|
||||
pub fn new(r1cs: PathOrBin, wasm: PathOrBin) -> Result<Self, Error> {
|
||||
match (r1cs, wasm) {
|
||||
(PathOrBin::Path(r1cs_path), PathOrBin::Path(wasm_path)) => {
|
||||
Self::new_from_path(r1cs_path, wasm_path)
|
||||
}
|
||||
(PathOrBin::Bin(r1cs_bin), PathOrBin::Bin(wasm_bin)) => Ok(Self {
|
||||
r1csfile_bytes: r1cs_bin,
|
||||
wasmfile_bytes: wasm_bin,
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
_ => unreachable!("You should pass the same enum branch for both inputs"),
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new instance of the CircomWrapper with the file paths.
|
||||
fn new_from_path(r1cs_file_path: PathBuf, wasm_file_path: PathBuf) -> Result<Self, Error> {
|
||||
let mut file = File::open(r1cs_file_path)?;
|
||||
let metadata = File::metadata(&file)?;
|
||||
let mut r1csfile_bytes = vec![0; metadata.len() as usize];
|
||||
file.read_exact(&mut r1csfile_bytes)?;
|
||||
|
||||
let mut file = File::open(wasm_file_path)?;
|
||||
let metadata = File::metadata(&file)?;
|
||||
let mut wasmfile_bytes = vec![0; metadata.len() as usize];
|
||||
file.read_exact(&mut wasmfile_bytes)?;
|
||||
|
||||
Ok(CircomWrapper {
|
||||
r1csfile_bytes,
|
||||
wasmfile_bytes,
|
||||
_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 r1cs_file = r1cs_reader::R1CSFile::<F>::new(Cursor::new(&self.r1csfile_bytes))?;
|
||||
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)))
|
||||
}
|
||||
|
||||
pub fn extract_r1cs(&self) -> Result<R1CS<F>, Error> {
|
||||
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(Cursor::new(&self.r1csfile_bytes))?;
|
||||
let mut r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
|
||||
r1cs.wire_mapping = None;
|
||||
Ok(r1cs)
|
||||
}
|
||||
|
||||
// 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::from_binary(&self.wasmfile_bytes).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};
|
||||
|
||||
//To generate .r1cs and .wasm files, run the below command in the terminal.
|
||||
//bash ./frontends/src/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/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm",
|
||||
"./src/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/circom/test_folder/cubic_circuit.r1cs");
|
||||
let wasm_path =
|
||||
PathBuf::from("./src/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.into(), wasm_path.into()).unwrap();
|
||||
|
||||
let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
let circom_circuit = CircomCircuit {
|
||||
r1cs,
|
||||
witness,
|
||||
public_inputs_indexes: vec![],
|
||||
allocate_inputs_as_witnesses: false,
|
||||
};
|
||||
|
||||
circom_circuit.generate_constraints(cs.clone()).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
}
|
||||
3
frontends/src/lib.rs
Normal file
3
frontends/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod circom;
|
||||
pub mod noir;
|
||||
pub mod noname;
|
||||
303
frontends/src/noir/mod.rs
Normal file
303
frontends/src/noir/mod.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use acvm::{
|
||||
acir::{
|
||||
acir_field::GenericFieldElement,
|
||||
circuit::{Circuit, Program},
|
||||
native_types::{Witness as AcvmWitness, WitnessMap},
|
||||
},
|
||||
blackbox_solver::StubbedBlackBoxSolver,
|
||||
pwg::ACVM,
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar};
|
||||
use ark_relations::r1cs::ConstraintSynthesizer;
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error};
|
||||
use noir_arkworks_backend::{
|
||||
read_program_from_binary, read_program_from_file, sonobe_bridge::AcirCircuitSonobe,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NoirFCircuit<F: PrimeField> {
|
||||
pub circuit: Circuit<GenericFieldElement<F>>,
|
||||
pub state_len: usize,
|
||||
pub external_inputs_len: usize,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> FCircuit<F> for NoirFCircuit<F> {
|
||||
type Params = (PathOrBin, usize, usize);
|
||||
|
||||
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||
let (source, state_len, external_inputs_len) = params;
|
||||
let program = match source {
|
||||
PathOrBin::Path(path) => read_program_from_file(path),
|
||||
PathOrBin::Bin(bytes) => read_program_from_binary(&bytes),
|
||||
}
|
||||
.map_err(|ee| Error::Other(format!("{:?}", ee)))?;
|
||||
let circuit: Circuit<GenericFieldElement<F>> = program.functions[0].clone();
|
||||
let ivc_input_length = circuit.public_parameters.0.len();
|
||||
let ivc_return_length = circuit.return_values.0.len();
|
||||
|
||||
if ivc_input_length != ivc_return_length {
|
||||
return Err(Error::NotSameLength(
|
||||
"IVC input: ".to_string(),
|
||||
ivc_input_length,
|
||||
"IVC output: ".to_string(),
|
||||
ivc_return_length,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(NoirFCircuit {
|
||||
circuit,
|
||||
state_len,
|
||||
external_inputs_len,
|
||||
})
|
||||
}
|
||||
|
||||
fn state_len(&self) -> usize {
|
||||
self.state_len
|
||||
}
|
||||
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
self.external_inputs_len
|
||||
}
|
||||
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
external_inputs: Vec<F>, // inputs that are not part of the state
|
||||
) -> Result<Vec<F>, Error> {
|
||||
let mut acvm = ACVM::new(
|
||||
&StubbedBlackBoxSolver,
|
||||
&self.circuit.opcodes,
|
||||
WitnessMap::new(),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
|
||||
self.circuit
|
||||
.public_parameters
|
||||
.0
|
||||
.iter()
|
||||
.map(|witness| {
|
||||
let idx: usize = witness.as_usize();
|
||||
let value = z_i[idx].to_string();
|
||||
let witness = AcvmWitness(witness.witness_index());
|
||||
let f = GenericFieldElement::<F>::try_from_str(&value)
|
||||
.ok_or(SynthesisError::Unsatisfiable)?;
|
||||
acvm.overwrite_witness(witness, f);
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<Vec<()>, SynthesisError>>()?;
|
||||
|
||||
// write witness values for external_inputs
|
||||
self.circuit
|
||||
.private_parameters
|
||||
.iter()
|
||||
.map(|witness| {
|
||||
let idx = witness.as_usize() - z_i.len();
|
||||
let value = external_inputs[idx].to_string();
|
||||
let f = GenericFieldElement::<F>::try_from_str(&value)
|
||||
.ok_or(SynthesisError::Unsatisfiable)?;
|
||||
acvm.overwrite_witness(AcvmWitness(witness.witness_index()), f);
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<Vec<()>, SynthesisError>>()?;
|
||||
let _ = acvm.solve();
|
||||
|
||||
let witness_map = acvm.finalize();
|
||||
|
||||
// get the z_{i+1} output state
|
||||
let assigned_z_i1 = self
|
||||
.circuit
|
||||
.return_values
|
||||
.0
|
||||
.iter()
|
||||
.map(|witness| {
|
||||
let noir_field_element = witness_map
|
||||
.get(witness)
|
||||
.ok_or(SynthesisError::AssignmentMissing)?;
|
||||
Ok(noir_field_element.into_repr())
|
||||
})
|
||||
.collect::<Result<Vec<F>, SynthesisError>>()?;
|
||||
|
||||
Ok(assigned_z_i1)
|
||||
}
|
||||
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
external_inputs: Vec<FpVar<F>>, // inputs that are not part of the state
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let mut acvm = ACVM::new(
|
||||
&StubbedBlackBoxSolver,
|
||||
&self.circuit.opcodes,
|
||||
WitnessMap::new(),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
|
||||
let mut already_assigned_witness_values = HashMap::new();
|
||||
|
||||
self.circuit
|
||||
.public_parameters
|
||||
.0
|
||||
.iter()
|
||||
.map(|witness| {
|
||||
let idx: usize = witness.as_usize();
|
||||
let witness = AcvmWitness(witness.witness_index());
|
||||
already_assigned_witness_values.insert(witness, &z_i[idx]);
|
||||
let val = z_i[idx].value()?;
|
||||
let value = if val == F::zero() {
|
||||
"0".to_string()
|
||||
} else {
|
||||
val.to_string()
|
||||
};
|
||||
|
||||
let f = GenericFieldElement::<F>::try_from_str(&value)
|
||||
.ok_or(SynthesisError::Unsatisfiable)?;
|
||||
acvm.overwrite_witness(witness, f);
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<Vec<()>, SynthesisError>>()?;
|
||||
|
||||
// write witness values for external_inputs
|
||||
self.circuit
|
||||
.private_parameters
|
||||
.iter()
|
||||
.map(|witness| {
|
||||
let idx = witness.as_usize() - z_i.len();
|
||||
let witness = AcvmWitness(witness.witness_index());
|
||||
already_assigned_witness_values.insert(witness, &external_inputs[idx]);
|
||||
|
||||
let val = external_inputs[idx].value()?;
|
||||
let value = if val == F::zero() {
|
||||
"0".to_string()
|
||||
} else {
|
||||
val.to_string()
|
||||
};
|
||||
|
||||
let f = GenericFieldElement::<F>::try_from_str(&value)
|
||||
.ok_or(SynthesisError::Unsatisfiable)?;
|
||||
acvm.overwrite_witness(witness, f);
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<Vec<()>, SynthesisError>>()?;
|
||||
|
||||
// computes the witness
|
||||
let _ = acvm.solve();
|
||||
let witness_map = acvm.finalize();
|
||||
|
||||
// get the z_{i+1} output state
|
||||
let assigned_z_i1 = self
|
||||
.circuit
|
||||
.return_values
|
||||
.0
|
||||
.iter()
|
||||
.map(|witness| {
|
||||
let noir_field_element = witness_map
|
||||
.get(witness)
|
||||
.ok_or(SynthesisError::AssignmentMissing)?;
|
||||
FpVar::<F>::new_witness(cs.clone(), || Ok(noir_field_element.into_repr()))
|
||||
})
|
||||
.collect::<Result<Vec<FpVar<F>>, SynthesisError>>()?;
|
||||
|
||||
// initialize circuit and set already assigned values
|
||||
let mut acir_circuit = AcirCircuitSonobe::from((&self.circuit, witness_map));
|
||||
acir_circuit.already_assigned_witnesses = already_assigned_witness_values;
|
||||
|
||||
acir_circuit.generate_constraints(cs.clone())?;
|
||||
|
||||
Ok(assigned_z_i1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_noir_circuit<F: PrimeField>(path: String) -> Circuit<GenericFieldElement<F>> {
|
||||
let program: Program<GenericFieldElement<F>> = read_program_from_file(path).unwrap();
|
||||
let circuit: Circuit<GenericFieldElement<F>> = program.functions[0].clone();
|
||||
circuit
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::noir::load_noir_circuit;
|
||||
use ark_bn254::Fr;
|
||||
use ark_r1cs_std::R1CSVar;
|
||||
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use folding_schemes::frontend::FCircuit;
|
||||
use std::env;
|
||||
|
||||
use crate::noir::NoirFCircuit;
|
||||
|
||||
#[test]
|
||||
fn test_step_native() {
|
||||
let cur_path = env::current_dir().unwrap();
|
||||
let circuit_path = format!(
|
||||
"{}/src/noir/test_folder/test_circuit/target/test_circuit.json",
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let noirfcircuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 2,
|
||||
external_inputs_len: 2,
|
||||
};
|
||||
let inputs = vec![Fr::from(2), Fr::from(5)];
|
||||
let res = noirfcircuit.step_native(0, inputs.clone(), inputs);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(res.unwrap(), vec![Fr::from(4), Fr::from(25)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_constraints() {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let cur_path = env::current_dir().unwrap();
|
||||
let circuit_path = format!(
|
||||
"{}/src/noir/test_folder/test_circuit/target/test_circuit.json",
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let noirfcircuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 2,
|
||||
external_inputs_len: 2,
|
||||
};
|
||||
let inputs = vec![Fr::from(2), Fr::from(5)];
|
||||
let z_i = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs.clone())).unwrap();
|
||||
let external_inputs = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs)).unwrap();
|
||||
let output = noirfcircuit
|
||||
.generate_step_constraints(cs.clone(), 0, z_i, external_inputs)
|
||||
.unwrap();
|
||||
assert_eq!(output[0].value().unwrap(), Fr::from(4));
|
||||
assert_eq!(output[1].value().unwrap(), Fr::from(25));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_constraints_no_external_inputs() {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let cur_path = env::current_dir().unwrap();
|
||||
let circuit_path = format!(
|
||||
"{}/src/noir/test_folder/test_no_external_inputs/target/test_no_external_inputs.json",
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let noirfcircuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 2,
|
||||
external_inputs_len: 0,
|
||||
};
|
||||
let inputs = vec![Fr::from(2), Fr::from(5)];
|
||||
let z_i = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs.clone())).unwrap();
|
||||
let external_inputs = vec![];
|
||||
let output = noirfcircuit
|
||||
.generate_step_constraints(cs.clone(), 0, z_i, external_inputs)
|
||||
.unwrap();
|
||||
assert_eq!(output[0].value().unwrap(), Fr::from(4));
|
||||
assert_eq!(output[1].value().unwrap(), Fr::from(25));
|
||||
}
|
||||
}
|
||||
7
frontends/src/noir/test_folder/compile.sh
Executable file
7
frontends/src/noir/test_folder/compile.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
CUR_DIR=$(pwd)
|
||||
TEST_PATH="${CUR_DIR}/frontends/src/noir/test_folder/"
|
||||
for test_path in test_circuit test_mimc test_no_external_inputs; do
|
||||
FOLDER="${TEST_PATH}${test_path}/"
|
||||
cd ${FOLDER} && nargo compile && cd ${TEST_PATH}
|
||||
done
|
||||
8
frontends/src/noir/test_folder/test_circuit/Nargo.toml
Normal file
8
frontends/src/noir/test_folder/test_circuit/Nargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "test_circuit"
|
||||
type = "bin"
|
||||
authors = [""]
|
||||
compiler_version = ">=0.30.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
11
frontends/src/noir/test_folder/test_circuit/src/main.nr
Normal file
11
frontends/src/noir/test_folder/test_circuit/src/main.nr
Normal file
@@ -0,0 +1,11 @@
|
||||
fn main(public_inputs: pub [Field; 2], private_inputs: [Field; 2]) -> pub [Field; 2]{
|
||||
let a_pub = public_inputs[0];
|
||||
let b_pub = public_inputs[1];
|
||||
let c_private = private_inputs[0];
|
||||
let d_private = private_inputs[1];
|
||||
|
||||
let out_1 = a_pub * c_private;
|
||||
let out_2 = b_pub * d_private;
|
||||
|
||||
[out_1, out_2]
|
||||
}
|
||||
8
frontends/src/noir/test_folder/test_mimc/Nargo.toml
Normal file
8
frontends/src/noir/test_folder/test_mimc/Nargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "test_mimc"
|
||||
type = "bin"
|
||||
authors = [""]
|
||||
compiler_version = ">=0.30.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
6
frontends/src/noir/test_folder/test_mimc/src/main.nr
Normal file
6
frontends/src/noir/test_folder/test_mimc/src/main.nr
Normal file
@@ -0,0 +1,6 @@
|
||||
use dep::std;
|
||||
|
||||
pub fn main(x: pub [Field; 1]) -> pub Field {
|
||||
let hash = std::hash::mimc::mimc_bn254(x);
|
||||
hash
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "test_no_external_inputs"
|
||||
type = "bin"
|
||||
authors = [""]
|
||||
compiler_version = ">=0.30.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fn main(public_inputs: pub [Field; 2]) -> pub [Field; 2]{
|
||||
let a_pub = public_inputs[0];
|
||||
let b_pub = public_inputs[1];
|
||||
let out_1 = a_pub * a_pub;
|
||||
let out_2 = b_pub * b_pub;
|
||||
|
||||
[out_1, out_2]
|
||||
}
|
||||
|
||||
200
frontends/src/noname/mod.rs
Normal file
200
frontends/src/noname/mod.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
use ark_noname::sonobe::NonameSonobeCircuit;
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use num_bigint::BigUint;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use self::utils::NonameInputs;
|
||||
|
||||
use ark_ff::PrimeField;
|
||||
use ark_noname::utils::compile_source_code;
|
||||
use folding_schemes::{frontend::FCircuit, Error};
|
||||
use noname::backends::{r1cs::R1CS as R1CSNoname, BackendField};
|
||||
use noname::witness::CompiledCircuit;
|
||||
pub mod utils;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NonameFCircuit<F: PrimeField, BF: BackendField> {
|
||||
pub state_len: usize,
|
||||
pub external_inputs_len: usize,
|
||||
pub circuit: CompiledCircuit<R1CSNoname<BF>>,
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField, BF: BackendField> FCircuit<F> for NonameFCircuit<F, BF> {
|
||||
type Params = (String, usize, usize);
|
||||
|
||||
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||
let (code, state_len, external_inputs_len) = params;
|
||||
let compiled_circuit = compile_source_code::<BF>(&code).map_err(|_| {
|
||||
Error::Other("Encountered an error while compiling a noname circuit".to_owned())
|
||||
})?;
|
||||
Ok(NonameFCircuit {
|
||||
state_len,
|
||||
external_inputs_len,
|
||||
circuit: compiled_circuit,
|
||||
_f: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn state_len(&self) -> usize {
|
||||
self.state_len
|
||||
}
|
||||
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
self.external_inputs_len
|
||||
}
|
||||
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
let wtns_external_inputs =
|
||||
NonameInputs::from((&external_inputs, "external_inputs".to_string()));
|
||||
let wtns_ivc_inputs = NonameInputs::from((&z_i, "ivc_inputs".to_string()));
|
||||
|
||||
let noname_witness = self
|
||||
.circuit
|
||||
.generate_witness(wtns_ivc_inputs.0, wtns_external_inputs.0)
|
||||
.map_err(|e| Error::WitnessCalculationError(e.to_string()))?;
|
||||
|
||||
let z_i1_end_index = z_i.len() + 1;
|
||||
let assigned_z_i1 = (1..z_i1_end_index)
|
||||
.map(|idx| {
|
||||
let value: BigUint = Into::into(noname_witness.witness[idx]);
|
||||
F::from(value)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(assigned_z_i1)
|
||||
}
|
||||
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let wtns_external_inputs =
|
||||
NonameInputs::from_fpvars((&external_inputs, "external_inputs".to_string()))?;
|
||||
let wtns_ivc_inputs = NonameInputs::from_fpvars((&z_i, "ivc_inputs".to_string()))?;
|
||||
let noname_witness = self
|
||||
.circuit
|
||||
.generate_witness(wtns_ivc_inputs.0, wtns_external_inputs.0)
|
||||
.map_err(|_| SynthesisError::Unsatisfiable)?;
|
||||
let z_i1_end_index = z_i.len() + 1;
|
||||
let assigned_z_i1: Vec<FpVar<F>> = (1..z_i1_end_index)
|
||||
.map(|idx| -> Result<FpVar<F>, SynthesisError> {
|
||||
// the assigned zi1 is of the same size than the initial zi and is located in the
|
||||
// output of the witness vector
|
||||
// we prefer to assign z_i1 here since (1) we have to return it, (2) we cant return
|
||||
// anything with the `generate_constraints` method used below
|
||||
let value: BigUint = Into::into(noname_witness.witness[idx]);
|
||||
let field_element = F::from(value);
|
||||
FpVar::<F>::new_witness(cs.clone(), || Ok(field_element))
|
||||
})
|
||||
.collect::<Result<Vec<FpVar<F>>, SynthesisError>>()?;
|
||||
|
||||
let noname_circuit = NonameSonobeCircuit {
|
||||
compiled_circuit: self.circuit.clone(),
|
||||
witness: noname_witness,
|
||||
assigned_z_i: &z_i,
|
||||
assigned_external_inputs: &external_inputs,
|
||||
assigned_z_i1: &assigned_z_i1,
|
||||
};
|
||||
noname_circuit.generate_constraints(cs.clone())?;
|
||||
|
||||
Ok(assigned_z_i1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use ark_bn254::Fr;
|
||||
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar};
|
||||
use noname::backends::r1cs::R1csBn254Field;
|
||||
|
||||
use folding_schemes::frontend::FCircuit;
|
||||
|
||||
use super::NonameFCircuit;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
|
||||
const NONAME_CIRCUIT_EXTERNAL_INPUTS: &str =
|
||||
"fn main(pub ivc_inputs: [Field; 2], external_inputs: [Field; 2]) -> [Field; 2] {
|
||||
let xx = external_inputs[0] + ivc_inputs[0];
|
||||
let yy = external_inputs[1] * ivc_inputs[1];
|
||||
assert_eq(yy, xx);
|
||||
return [xx, yy];
|
||||
}";
|
||||
|
||||
const NONAME_CIRCUIT_NO_EXTERNAL_INPUTS: &str =
|
||||
"fn main(pub ivc_inputs: [Field; 2]) -> [Field; 2] {
|
||||
let out = ivc_inputs[0] * ivc_inputs[1];
|
||||
return [out, ivc_inputs[1]];
|
||||
}";
|
||||
|
||||
#[test]
|
||||
fn test_step_native() {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let params = (NONAME_CIRCUIT_EXTERNAL_INPUTS.to_owned(), 2, 2);
|
||||
let circuit = NonameFCircuit::<Fr, R1csBn254Field>::new(params).unwrap();
|
||||
let inputs_public = vec![Fr::from(2), Fr::from(5)];
|
||||
let inputs_private = vec![Fr::from(8), Fr::from(2)];
|
||||
|
||||
let ivc_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs_public.clone())).unwrap();
|
||||
let external_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs_private.clone())).unwrap();
|
||||
|
||||
let z_i1 = circuit
|
||||
.generate_step_constraints(cs.clone(), 0, ivc_inputs_var, external_inputs_var)
|
||||
.unwrap();
|
||||
let z_i1_native = circuit
|
||||
.step_native(0, inputs_public, inputs_private)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(z_i1[0].value().unwrap(), z_i1_native[0]);
|
||||
assert_eq!(z_i1[1].value().unwrap(), z_i1_native[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_constraints() {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let params = (NONAME_CIRCUIT_EXTERNAL_INPUTS.to_owned(), 2, 2);
|
||||
let circuit = NonameFCircuit::<Fr, R1csBn254Field>::new(params).unwrap();
|
||||
let inputs_public = vec![Fr::from(2), Fr::from(5)];
|
||||
let inputs_private = vec![Fr::from(8), Fr::from(2)];
|
||||
|
||||
let ivc_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs_public)).unwrap();
|
||||
let external_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs_private)).unwrap();
|
||||
|
||||
let z_i1 = circuit
|
||||
.generate_step_constraints(cs.clone(), 0, ivc_inputs_var, external_inputs_var)
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
assert_eq!(z_i1[0].value().unwrap(), Fr::from(10_u8));
|
||||
assert_eq!(z_i1[1].value().unwrap(), Fr::from(10_u8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_constraints_no_external_inputs() {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let params = (NONAME_CIRCUIT_NO_EXTERNAL_INPUTS.to_owned(), 2, 0);
|
||||
let inputs_public = vec![Fr::from(2), Fr::from(5)];
|
||||
|
||||
let ivc_inputs_var =
|
||||
Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs_public)).unwrap();
|
||||
|
||||
let f_circuit = NonameFCircuit::<Fr, R1csBn254Field>::new(params).unwrap();
|
||||
f_circuit
|
||||
.generate_step_constraints(cs.clone(), 0, ivc_inputs_var, vec![])
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
}
|
||||
58
frontends/src/noname/utils.rs
Normal file
58
frontends/src/noname/utils.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{fields::fp::FpVar, R1CSVar};
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use noname::inputs::JsonInputs;
|
||||
use serde_json::json;
|
||||
|
||||
pub struct NonameInputs(pub JsonInputs);
|
||||
|
||||
impl<F: PrimeField> From<(&Vec<F>, String)> for NonameInputs {
|
||||
fn from(value: (&Vec<F>, String)) -> Self {
|
||||
let (values, key) = value;
|
||||
let mut inputs = HashMap::new();
|
||||
if values.is_empty() {
|
||||
NonameInputs(JsonInputs(inputs))
|
||||
} else {
|
||||
let field_elements: Vec<String> = values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
if value.is_zero() {
|
||||
"0".to_string()
|
||||
} else {
|
||||
value.to_string()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
inputs.insert(key, json!(field_elements));
|
||||
NonameInputs(JsonInputs(inputs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NonameInputs {
|
||||
pub fn from_fpvars<F: PrimeField>(
|
||||
value: (&Vec<FpVar<F>>, String),
|
||||
) -> Result<Self, SynthesisError> {
|
||||
let (values, key) = value;
|
||||
let mut inputs = HashMap::new();
|
||||
if values.is_empty() {
|
||||
Ok(NonameInputs(JsonInputs(inputs)))
|
||||
} else {
|
||||
let field_elements: Vec<String> = values
|
||||
.iter()
|
||||
.map(|var| {
|
||||
let value = var.value()?;
|
||||
if value.is_zero() {
|
||||
Ok("0".to_string())
|
||||
} else {
|
||||
Ok(value.to_string())
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<String>, SynthesisError>>()?;
|
||||
inputs.insert(key, json!(field_elements));
|
||||
Ok(NonameInputs(JsonInputs(inputs)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user