Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/better browser support #169

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/scripts/wasm-target-test-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ cp "${GIT_ROOT}/rust-toolchain" .
rustup target add wasm32-unknown-unknown wasm32-wasi

# add dependencies
cargo add --path "${GIT_ROOT}/frontends" --features wasm, parallel
cargo add --path "${GIT_ROOT}/folding-schemes" --features parallel
cargo add --path "${GIT_ROOT}/folding-schemes" --features circom-browser
cargo add getrandom --features js --target wasm32-unknown-unknown

# test build for wasm32-* targets
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: -p frontends --no-default-features --target ${{ matrix.target }} --features "wasm, parallel"
args: -p frontends --no-default-features --target ${{ matrix.target }} --features "circom-browser, parallel"
- name: Wasm-compat folding-schemes build
uses: actions-rs/cargo@v1
with:
Expand Down Expand Up @@ -167,7 +167,7 @@ jobs:
features: --features default,light-test
# We only want to test `frontends` package with `wasm` feature.
- feature_set: wasm
features: -p frontends --features wasm,parallel --target wasm32-unknown-unknown
features: -p frontends --features circom-browser,parallel --target wasm32-unknown-unknown
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
Expand Down
19 changes: 12 additions & 7 deletions folding-schemes/Cargo.toml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the formatting has some strange behaviour? Line 53 has 146 characters, while wrapping happens at line 78 at line 27.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Unsure why this happens TBH. Let me try to fix it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope 30a78d3 looks better

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to solve it and makes the parser unhappy...

I don't think I can do much with this..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related: #169 (comment)

Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,19 @@ sha3 = "0.10"
log = "0.4"

# tmp import for espresso's sumcheck
espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", package="subroutines"}
espresso_subroutines = { git = "https://github.com/EspressoSystems/hyperplonk", package = "subroutines" }
# Dependencies needed for browser folding
byteorder = { version = "1.4.3", optional = true }

[dev-dependencies]
ark-pallas = {version="0.4.0", features=["r1cs"]}
ark-vesta = {version="0.4.0", features=["r1cs"]}
ark-bn254 = {version="0.4.0", features=["r1cs"]}
ark-grumpkin = {version="0.4.0", features=["r1cs"]}
ark-pallas = { version = "0.4.0", features = ["r1cs"] }
ark-vesta = { version = "0.4.0", features = ["r1cs"] }
ark-bn254 = { version = "0.4.0", features = ["r1cs"] }
ark-grumpkin = { version = "0.4.0", features = ["r1cs"] }
# Note: do not use the MNTx_298 curves in practice due security reasons, here
# we only use them in the tests.
ark-mnt4-298 = {version="0.4.0", features=["r1cs"]}
ark-mnt6-298 = {version="0.4.0", features=["r1cs"]}
ark-mnt4-298 = { version = "0.4.0", features = ["r1cs"] }
ark-mnt6-298 = { version = "0.4.0", features = ["r1cs"] }
rand = "0.8.5"
num-bigint = {version = "0.4", features = ["rand"]}
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] }
Expand Down Expand Up @@ -64,3 +66,6 @@ path = "../examples/multi_inputs.rs"
[[example]]
name = "external_inputs"
path = "../examples/external_inputs.rs"

[lib]
crate-type = ["cdylib", "rlib"]
3 changes: 3 additions & 0 deletions folding-schemes/src/folding/nova/circuits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ where

// get z_{i+1} from the F circuit
let i_usize = self.i_usize.unwrap_or(0);

// If we are in the circom-browser-frontend case. The witness is already loaded within
// self.F.witness. This was done at `self.prove_step()` fn.
let z_i1 = self
.F
.generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?;
Expand Down
26 changes: 26 additions & 0 deletions folding-schemes/src/folding/nova/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,36 @@ where
fn prove_step(
&mut self,
mut rng: impl RngCore,
// This contains the full witness when we're in the circom-browser-frontend case
external_inputs: Vec<C1::ScalarField>,
// Nova does not support multi-instances folding
_other_instances: Option<Self::MultiCommittedInstanceWithWitness>,
) -> Result<(), Error> {
#[cfg(feature = "circom-browser")]
let external_inputs = {
// 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 circom-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)
};

external_inputs
};

// 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() {
Expand Down
7 changes: 7 additions & 0 deletions folding-schemes/src/frontend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ pub trait FCircuit<F: PrimeField>: Clone + Debug {
z_i: Vec<FpVar<F>>,
external_inputs: Vec<FpVar<F>>, // inputs that are not part of the state
) -> Result<Vec<FpVar<F>>, SynthesisError>;

/// Allows to load pre-generated witness into the FCircuit implementor.
/// This is only needed by the circom-browser use cases where we have already computed our
/// witness there. And we need a way to load it into the FCircuit since it's already computed.
///
/// By default this method will simply do nothing. Only in the circom-browser FCircuit implementors this will have usage.
fn load_witness(&mut self, _witness: Vec<F>) {}
}

#[cfg(test)]
Expand Down
11 changes: 6 additions & 5 deletions folding-schemes/src/utils/espresso/sum_check/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,15 @@ impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> {
self.challenges.push(*chal);

let r = self.challenges[self.round - 1];
// #[cfg(feature = "parallel")]
#[cfg(feature = "parallel")]
flattened_ml_extensions
.par_iter_mut()
.for_each(|mle| *mle = fix_variables(mle, &[r]));
// #[cfg(not(feature = "parallel"))]
// flattened_ml_extensions
// .iter_mut()
// .for_each(|mle| *mle = fix_variables(mle, &[r]));

#[cfg(not(feature = "parallel"))]
flattened_ml_extensions
.iter_mut()
.for_each(|mle| *mle = fix_variables(mle, &[r]));
} else if self.round > 0 {
return Err(PolyIOPErrors::InvalidProver(
"verifier message is empty".to_string(),
Expand Down
169 changes: 169 additions & 0 deletions frontends/src/circom/browser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
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:
/// <https://github.com/CPerezz/wasm-sonobe-integration>
#[derive(Clone, Debug)]
pub struct CircomFCircuitBrowser<F: PrimeField> {
pub state_len: usize,
pub external_inputs_len: usize,
pub witness: Vec<F>,
pub r1cs: CircomR1CS<F>,
}

impl<F: PrimeField> FCircuit<F> for CircomFCircuitBrowser<F> {
/// (r1cs_file_bytes, state_len, external_inputs_len)
type Params = (Vec<u8>, usize, usize);

fn new(params: Self::Params) -> Result<Self, Error> {
let (r1cs_bytes, state_len, external_inputs_len) = params;
let reader = BufReader::new(Cursor::new(&r1cs_bytes[..]));

let mut r1cs: R1CS<F> = R1CSFile::<F>::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<F>,
external_inputs: Vec<F>,
) -> Result<Vec<F>, 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[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>>,
_external_inputs: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, 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<FpVar<F>> = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
Ok(self.witness[1..1 + self.state_len()].to_vec())
})?;

Ok(z_i1)
}

fn load_witness(&mut self, witness: Vec<F>) {
self.witness = witness;
}
}

/// Load Circom-generated witness from u8 array by a reader.
///
/// This fn has been taken from <https://github.com/nalinbhardwaj/Nova-Scotia/blob/main/src/circom/file.rs>
pub fn load_witness_from_bin_reader<R: Read, F: PrimeField>(mut reader: R) -> Vec<F> {
let mut wtns_header = [0u8; 4];
reader.read_exact(&mut wtns_header).expect("read_error");
if wtns_header != [119, 116, 110, 115] {
panic!("invalid file header");
}
let version = reader.read_u32::<LittleEndian>().expect("read_error");
if version > 2 {
panic!("unsupported file version");
}
let num_sections = reader.read_u32::<LittleEndian>().expect("read_error");
if num_sections != 2 {
panic!("invalid num sections");
}
// read the first section
let sec_type = reader.read_u32::<LittleEndian>().expect("read_error");
if sec_type != 1 {
panic!("invalid section type");
}
let sec_size = reader.read_u64::<LittleEndian>().expect("read_error");
if sec_size != 4 + 32 + 4 {
panic!("invalid section len")
}
let field_size: u32 = reader.read_u32::<LittleEndian>().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");
let witness_len: u32 = reader.read_u32::<LittleEndian>().expect("read_error");
let sec_type = reader.read_u32::<LittleEndian>().expect("read_error");
if sec_type != 2 {
panic!("invalid section type");
}
let sec_size = reader.read_u64::<LittleEndian>().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<R: Read, F: PrimeField>(
mut reader: R,
) -> Result<F, ark_serialize::SerializationError> {
let mut repr: Vec<u8> = 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[..]))
}
8 changes: 7 additions & 1 deletion frontends/src/circom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ use num_bigint::BigInt;
use std::rc::Rc;
use std::{fmt, usize};

#[cfg(feature = "circom-browser")]
pub mod browser;
pub mod utils;

#[cfg(feature = "circom-browser")]
pub use browser::{load_witness_from_bin_reader, CircomFCircuitBrowser};
use utils::CircomWrapper;

type ClosurePointer<F> = Rc<dyn Fn(usize, Vec<F>, Vec<F>) -> Result<Vec<F>, Error>>;
Expand All @@ -31,7 +36,8 @@ impl<F: PrimeField> fmt::Debug for CustomStepNative<F> {
}
}

/// 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<F: PrimeField> {
circom_wrapper: CircomWrapper<F>,
Expand Down
Loading