Browse Source

feat: Minimal in-browser compatibility for the crate (#149)

* change: CircomWrapper constructor to use raw bytes

* chore: tmp update to latest circom-compat

* feat: Introduce PathOrBin to support in-browser CircomWrapper usage

This changes the associated type `Params` of the `CircomFCircuit` to use
the newly created `PathOrBin` type.

This allows the user of the lib to directly send the binary of the files
already read or instead, provide a path to it and let `sonobe` do the
work.

With this, Circom should be already usable from the browser if we allow
JS to take care of reading the `.wasm` and `.r1cs` files.

* feat: Introduce PathOrBin to support in-browser NoirFCircuit usage

This commit temporarilly stands on top of
https://github.com/dmpierre/arkworks_backend/pull/1 referenced as `rev`.

This changes the associated type `Params` of the `CircomFCircuit` to use
the newly created `PathOrBin` type.

This allows the user of the lib to directly send the binary of the files
already read or instead, provide a path to it and let `sonobe` do the
work.

With this, Noir should be already usable from the browser if we allow
JS to take care of reading the `circuit.json` files

* chore: Update deps to branch instead of `rev`

* fix: use PathOrBin in examples

* fix: clippy

* fix: read file length for initializing vec size

---------

Co-authored-by: dmpierre <pdaixmoreux@gmail.com>
main
Carlos Pérez 2 months ago
committed by GitHub
parent
commit
52de2d185c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
6 changed files with 100 additions and 35 deletions
  1. +1
    -1
      examples/circom_full_flow.rs
  2. +2
    -2
      folding-schemes/Cargo.toml
  3. +16
    -9
      folding-schemes/src/frontend/circom/mod.rs
  4. +39
    -17
      folding-schemes/src/frontend/circom/utils.rs
  5. +11
    -6
      folding-schemes/src/frontend/noir/mod.rs
  6. +31
    -0
      folding-schemes/src/utils/mod.rs

+ 1
- 1
examples/circom_full_flow.rs

@ -61,7 +61,7 @@ fn main() {
"./folding-schemes/src/frontend/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
);
let f_circuit_params = (r1cs_path, wasm_path, 1, 2);
let f_circuit_params = (r1cs_path.into(), wasm_path.into(), 1, 2);
let f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap();
pub type N =

+ 2
- 2
folding-schemes/Cargo.toml

@ -25,12 +25,12 @@ num-bigint = "0.4"
num-integer = "0.1"
color-eyre = "=0.6.2"
sha3 = "0.10"
ark-noname = { git = "https://github.com/dmpierre/ark-noname", branch="feat/sonobe-integration" }
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
serde = "1.0.203"
acvm = { git = "https://github.com/noir-lang/noir", rev="2b4853e", default-features = false }
arkworks_backend = { git = "https://github.com/dmpierre/arkworks_backend", branch="feat/sonobe-integration" }
arkworks_backend = { git = "https://github.com/dmpierre/arkworks_backend", branch = "feat/sonobe-integration" }
log = "0.4"
# tmp import for espresso's sumcheck

+ 16
- 9
folding-schemes/src/frontend/circom/mod.rs

@ -1,5 +1,6 @@
use crate::frontend::FCircuit;
use crate::frontend::FpVar::Var;
use crate::utils::PathOrBin;
use crate::Error;
use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS};
use ark_ff::PrimeField;
@ -9,7 +10,6 @@ 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 std::rc::Rc;
use std::{fmt, usize};
@ -93,11 +93,11 @@ impl CircomFCircuit {
impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> {
/// (r1cs_path, wasm_path, state_len, external_inputs_len)
type Params = (PathBuf, PathBuf, usize, usize);
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 circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path)?;
let r1cs = circom_wrapper.extract_r1cs()?;
Ok(Self {
@ -208,6 +208,7 @@ 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]
@ -216,7 +217,8 @@ pub mod tests {
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, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
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();
@ -230,7 +232,8 @@ pub mod tests {
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, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
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();
@ -250,7 +253,8 @@ pub mod tests {
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, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
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)];
@ -276,7 +280,8 @@ pub mod tests {
let wasm_path = PathBuf::from(
"./src/frontend/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
);
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
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)];
@ -319,7 +324,8 @@ pub mod tests {
let wasm_path = PathBuf::from(
"./src/frontend/circom/test_folder/no_external_inputs_js/no_external_inputs.wasm",
);
let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 3, 0)).unwrap();
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();
@ -351,7 +357,8 @@ pub mod tests {
let wasm_path =
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
let mut circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
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];

+ 39
- 17
folding-schemes/src/frontend/circom/utils.rs

@ -3,40 +3,64 @@ use ark_circom::{
WitnessCalculator,
};
use ark_ff::{BigInteger, PrimeField};
use ark_serialize::Read;
use color_eyre::Result;
use num_bigint::{BigInt, Sign};
use std::{fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
use std::{fs::File, io::Cursor, marker::PhantomData, path::PathBuf};
use crate::Error;
use crate::{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> {
r1cs_filepath: PathBuf,
wasm_filepath: PathBuf,
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_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
CircomWrapper {
r1cs_filepath,
wasm_filepath,
_marker: PhantomData,
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 file = File::open(&self.r1cs_filepath)?;
let reader = BufReader::new(file);
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
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
@ -46,9 +70,7 @@ impl CircomWrapper {
}
pub fn extract_r1cs(&self) -> Result<R1CS<F>, Error> {
let file = File::open(&self.r1cs_filepath)?;
let reader = BufReader::new(file);
let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
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)
@ -75,7 +97,7 @@ impl CircomWrapper {
&self,
inputs: &[(String, Vec<BigInt>)],
) -> Result<Vec<BigInt>, Error> {
let mut calculator = WitnessCalculator::new(&self.wasm_filepath).map_err(|e| {
let mut calculator = WitnessCalculator::from_binary(&self.wasmfile_bytes).map_err(|e| {
Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e))
})?;
calculator
@ -139,7 +161,7 @@ mod tests {
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 wrapper = CircomWrapper::<Fr>::new(r1cs_path.into(), wasm_path.into()).unwrap();
let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();

+ 11
- 6
folding-schemes/src/frontend/noir/mod.rs

@ -1,6 +1,6 @@
use std::collections::HashMap;
use crate::Error;
use crate::{utils::PathOrBin, Error};
use super::FCircuit;
use acvm::{
@ -16,7 +16,9 @@ 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 arkworks_backend::{read_program_from_file, sonobe_bridge::AcirCircuitSonobe};
use arkworks_backend::{
read_program_from_binary, read_program_from_file, sonobe_bridge::AcirCircuitSonobe,
};
#[derive(Clone, Debug)]
pub struct NoirFCircuit<F: PrimeField> {
@ -26,12 +28,15 @@ pub struct NoirFCircuit {
}
impl<F: PrimeField> FCircuit<F> for NoirFCircuit<F> {
type Params = (String, usize, usize);
type Params = (PathOrBin, usize, usize);
fn new(params: Self::Params) -> Result<Self, crate::Error> {
let (path, state_len, external_inputs_len) = params;
let program =
read_program_from_file(path).map_err(|ee| Error::Other(format!("{:?}", ee)))?;
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();

+ 31
- 0
folding-schemes/src/utils/mod.rs

@ -1,3 +1,6 @@
use std::path::Path;
use std::path::PathBuf;
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
@ -100,3 +103,31 @@ where
&public_params_hash,
))
}
/// Tiny utility enum that allows to import circuits and wasm modules from files by passing their path
/// or passing their content already read.
///
/// This enum implements the [`From`] trait for both [`Path`], [`PathBuf`] and [`Vec<u8>`].
#[derive(Debug, Clone)]
pub enum PathOrBin {
Path(PathBuf),
Bin(Vec<u8>),
}
impl From<&Path> for PathOrBin {
fn from(value: &Path) -> Self {
PathOrBin::Path(value.into())
}
}
impl From<PathBuf> for PathOrBin {
fn from(value: PathBuf) -> Self {
PathOrBin::Path(value)
}
}
impl From<Vec<u8>> for PathOrBin {
fn from(value: Vec<u8>) -> Self {
PathOrBin::Bin(value)
}
}

Loading…
Cancel
Save