From 039a40035e32c8958cc9a09064ea24b7008f1fe7 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Tue, 15 Oct 2024 17:41:55 +0200 Subject: [PATCH] feat: Enable browser-folding with passed-in witness This commit introduces CircomFCircuitBrowser, a structure that facilitates folding of Circom-generated circuits in the browser via WASM. It loads R1CS data, handles witness inputs, and generates constraints for the folding process. Resolves: #155 --- folding-schemes/src/folding/nova/mod.rs | 21 +++ .../src/frontend/circom/browser.rs | 177 ++++++++++++++++++ folding-schemes/src/frontend/circom/mod.rs | 6 +- 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 folding-schemes/src/frontend/circom/browser.rs diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index d67c33b9..dea5bd85 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -642,10 +642,31 @@ where fn prove_step( &mut self, mut rng: impl RngCore, + // This contains the full witness when we're in the browser-frontend case external_inputs: Vec, // Nova does not support multi-instances folding _other_instances: Option, ) -> Result<(), Error> { + // Slice and separate between external inputs and frontend witness. + let (frontend_witness, external_inputs) = + if external_inputs.len() > self.F.external_inputs_len() { + ( + // Full circom witness trace + Some(external_inputs[..].to_vec()), + // Extracted external inputs from circom trace + external_inputs[1 + self.F.state_len() * 2 + ..1 + self.F.state_len() * 2 + self.F.external_inputs_len()] + .to_vec(), + ) + } else { + (None, external_inputs) + }; + + // If we are in the browser-case (frontend_witness = Some(witness)) then we load the witness into the FCircuit. + if let Some(witness) = frontend_witness { + self.F.load_witness(witness) + }; + // ensure that commitments are blinding if user has specified so. if H && self.i >= C1::ScalarField::one() { let blinding_commitments = if self.i == C1::ScalarField::one() { diff --git a/folding-schemes/src/frontend/circom/browser.rs b/folding-schemes/src/frontend/circom/browser.rs new file mode 100644 index 00000000..7ee29c77 --- /dev/null +++ b/folding-schemes/src/frontend/circom/browser.rs @@ -0,0 +1,177 @@ +use crate::frontend::FCircuit; +use crate::frontend::FpVar::Var; +use crate::Error; +use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS}; +use ark_circom::circom::{R1CSFile, R1CS}; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::alloc::AllocVar; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use ark_std::fmt::Debug; +use byteorder::{LittleEndian, ReadBytesExt}; +use std::io::{BufReader, Cursor, Read}; +use std::usize; + +/// This circuit is the one we will use in order to fold from the browser. +/// +/// A clear example on how to do all of this can be seen at: +/// +#[derive(Clone, Debug)] +pub struct CircomFCircuitBrowser { + pub state_len: usize, + pub external_inputs_len: usize, + pub witness: Vec, + pub r1cs: CircomR1CS, +} + +impl FCircuit for CircomFCircuitBrowser { + /// (r1cs_file_bytes, state_len, external_inputs_len) + type Params = (Vec, usize, usize); + + fn new(params: Self::Params) -> Result { + let (r1cs_bytes, state_len, external_inputs_len) = params; + let reader = BufReader::new(Cursor::new(&r1cs_bytes[..])); + + let mut r1cs: R1CS = R1CSFile::::new(reader) + .expect("Error reading R1cs") + .into(); + + // That's some weird magic. Ask @dmpierre + r1cs.wire_mapping = None; + + Ok(Self { + state_len, + external_inputs_len, + witness: vec![F::zero(); r1cs.num_variables], + r1cs, + }) + } + + 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, + external_inputs: Vec, + ) -> Result, Error> { + // Should we keep these? + assert_eq!(z_i.len(), self.state_len()); + assert_eq!(external_inputs.len(), self.external_inputs_len()); + + // Extracts the z_i1(next state) from the witness vector and external inputs. + let z_i1 = z_i[..self.state_len()].to_vec(); + Ok(z_i1) + } + + fn generate_step_constraints( + &self, + cs: ConstraintSystemRef, + _i: usize, + z_i: Vec>, + // This in reality contains the `witness` passed from the `prove_step` call from the + // browser. + _external_inputs: Vec>, + ) -> Result>, SynthesisError> { + // 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(self.witness.clone()), + public_inputs_indexes: already_allocated_public_inputs, + // Since public inputs are already allocated variables, we will tell `circom-compat` to not re-allocate those + allocate_inputs_as_witnesses: true, + }; + + // Generates the constraints for the circom_circuit. + circom_circuit.generate_constraints(cs.clone())?; + + // Extracts the z_i1(next state) from the witness vector. + let z_i1: Vec> = Vec::>::new_witness(cs.clone(), || { + Ok(self.witness[1..1 + self.state_len()].to_vec()) + })?; + + Ok(z_i1) + } + + fn load_witness(&mut self, witness: Vec) { + self.witness = witness; + } +} + +/// Load Circom-generated witness from u8 array by a reader. +/// +/// This fn has been taken from +pub fn load_witness_from_bin_reader(mut reader: R) -> Vec { + let mut wtns_header = [0u8; 4]; + reader.read_exact(&mut wtns_header).expect("read_error"); + if wtns_header != [119, 116, 110, 115] { + // ruby -e 'p "wtns".bytes' => [119, 116, 110, 115] + panic!("invalid file header"); + } + let version = reader.read_u32::().expect("read_error"); + // println!("wtns version {}", version); + if version > 2 { + panic!("unsupported file version"); + } + let num_sections = reader.read_u32::().expect("read_error"); + if num_sections != 2 { + panic!("invalid num sections"); + } + // read the first section + let sec_type = reader.read_u32::().expect("read_error"); + if sec_type != 1 { + panic!("invalid section type"); + } + let sec_size = reader.read_u64::().expect("read_error"); + if sec_size != 4 + 32 + 4 { + panic!("invalid section len") + } + let field_size: u32 = reader.read_u32::().expect("read_error"); + if field_size != 32 { + panic!("invalid field byte size"); + } + let mut prime = vec![0u8; field_size as usize]; + reader.read_exact(&mut prime).expect("read_error"); + // if prime != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") { + // panic!("invalid curve prime {:?}", prime); + // } + let witness_len: u32 = reader.read_u32::().expect("read_error"); + // println!("witness len {}", witness_len); + let sec_type = reader.read_u32::().expect("read_error"); + if sec_type != 2 { + panic!("invalid section type"); + } + let sec_size = reader.read_u64::().expect("read_error"); + if sec_size != (witness_len * field_size) as u64 { + panic!("invalid witness section size"); + } + let mut result = Vec::with_capacity(witness_len as usize); + for _ in 0..witness_len { + result.push(read_field::<&mut R, F>(&mut reader).expect("read_error")); + } + result +} + +fn read_field( + mut reader: R, +) -> Result { + let mut repr: Vec = F::ZERO.into_bigint().to_bytes_le(); + for digit in repr.iter_mut() { + *digit = reader.read_u8()?; + } + Ok(F::from_le_bytes_mod_order(&repr[..])) +} diff --git a/folding-schemes/src/frontend/circom/mod.rs b/folding-schemes/src/frontend/circom/mod.rs index a261f722..00473e43 100644 --- a/folding-schemes/src/frontend/circom/mod.rs +++ b/folding-schemes/src/frontend/circom/mod.rs @@ -13,7 +13,10 @@ use num_bigint::BigInt; use std::rc::Rc; use std::{fmt, usize}; +pub mod browser; pub mod utils; + +pub use browser::{load_witness_from_bin_reader, CircomFCircuitBrowser}; use utils::CircomWrapper; type ClosurePointer = Rc, Vec) -> Result, Error>>; @@ -33,7 +36,8 @@ impl fmt::Debug for CustomStepNative { } } -/// Define CircomFCircuit +/// This circuit is the one we will use in order to fold circom circuits +/// from a non-browser environment. #[derive(Clone, Debug)] pub struct CircomFCircuit { circom_wrapper: CircomWrapper,