-
Notifications
You must be signed in to change notification settings - Fork 51
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
39d4ee3
039a400
67a066d
9bcf84c
206984c
0cf1aae
47d908a
5c67849
29ba23e
291082e
d2d543f
9946b86
63f599e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,20 +4,40 @@ version = "0.1.0" | |
edition = "2021" | ||
|
||
[dependencies] | ||
ark-ec = { version = "^0.4.0", default-features = false, features = ["parallel"] } | ||
ark-ff = { version = "^0.4.0", default-features = false, features = ["parallel", "asm"] } | ||
ark-poly = { version = "^0.4.0", default-features = false, features = ["parallel"] } | ||
ark-std = { version = "^0.4.0", default-features = false, features = ["parallel"] } | ||
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = ["r1cs", "sponge", "crh", "parallel"] } | ||
ark-ec = { version = "^0.4.0", default-features = false, features = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One question, most of the changes in this file are formatting (mostly converting one-lines into multi-lines). Is there some standard that we could use, and if so could we apply it in the CI (and in our machines too) like we do with the rustfmt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it seems arbitrary as @dmpierre pointed in the comment #169 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VSCode did that for some reason I ignore.. Happy to bring this to the previous status or use whatever tool is suggested bu you guys. |
||
"parallel", | ||
] } | ||
ark-ff = { version = "^0.4.0", default-features = false, features = [ | ||
"parallel", | ||
"asm", | ||
] } | ||
ark-poly = { version = "^0.4.0", default-features = false, features = [ | ||
"parallel", | ||
] } | ||
ark-std = { version = "^0.4.0", default-features = false, features = [ | ||
"parallel", | ||
] } | ||
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = [ | ||
"r1cs", | ||
"sponge", | ||
"crh", | ||
"parallel", | ||
] } | ||
ark-grumpkin = { version = "0.4.0", default-features = false } | ||
ark-poly-commit = { version = "^0.4.0", default-features = false, features = ["parallel"] } | ||
ark-poly-commit = { version = "^0.4.0", default-features = false, features = [ | ||
"parallel", | ||
] } | ||
ark-relations = { version = "^0.4.0", default-features = false } | ||
# this is patched at the workspace level | ||
ark-r1cs-std = { version = "0.4.0", default-features = false, features = ["parallel"] } | ||
ark-r1cs-std = { version = "0.4.0", default-features = false, features = [ | ||
"parallel", | ||
] } | ||
ark-snark = { version = "^0.4.0", default-features = false } | ||
ark-serialize = { version = "^0.4.0", default-features = false } | ||
ark-circom = { git = "https://github.com/arnaucube/circom-compat", default-features = false } | ||
ark-groth16 = { version = "^0.4.0", default-features = false, features = ["parallel"]} | ||
ark-groth16 = { version = "^0.4.0", default-features = false, features = [ | ||
"parallel", | ||
] } | ||
ark-bn254 = { version = "^0.4.0", default-features = false } | ||
thiserror = "1.0" | ||
rayon = "1" | ||
|
@@ -27,26 +47,30 @@ color-eyre = "=0.6.2" | |
sha3 = "0.10" | ||
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_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 } | ||
noir_arkworks_backend = { package="arkworks_backend", git = "https://github.com/dmpierre/arkworks_backend", branch = "feat/sonobe-integration" } | ||
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" } | ||
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" | ||
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } | ||
tracing = { version = "0.1", default-features = false, features = [ | ||
"attributes", | ||
] } | ||
tracing-subscriber = { version = "0.2" } | ||
|
||
# This allows the crate to be built when targeting WASM. | ||
|
@@ -58,6 +82,7 @@ getrandom = { version = "0.2", features = ["js"] } | |
default = ["ark-circom/default", "parallel"] | ||
parallel = [] | ||
wasm = ["ark-circom/wasm"] | ||
browser = ["wasm", "byteorder"] | ||
CPerezz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
light-test = [] | ||
|
||
|
||
|
@@ -72,3 +97,6 @@ path = "../examples/multi_inputs.rs" | |
[[example]] | ||
name = "external_inputs" | ||
path = "../examples/external_inputs.rs" | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: | ||
/// <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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this can be changed into an 'if' that returns an error instead of panicking? so that the user of the lib does not have an uncontrolled crash of the run but an error that they can decide how to manage. Or at least under a test flag ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem that I want to avoid is having errors. Errors are really hard to optimize for whereas panics aren't. Also, this assert should only trigger when you're testing. In prod, you should always have a way to ensure the lengths are correct. (Notice that your'e not going to change the circuit for which you're generating witness nor anything similar. If honest, I'd entirely remove the panics and errors and simply do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Notice that under a testing flag, this will never be triggered. As you will need to compile the lib for testing to then compile also the tests within the WASM binary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with the test flag it will still be triggered when running the tests, no? (previous to going into wasm in the browser) The point of those checks is not to panic the runtime of the program that uses sonobe, but to make the dev aware that the lenghts don't match. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, should we add an error then? Or simply ignore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we can do it as in the lines linked in this comment: #169 (comment) |
||
|
||
// Extracts the z_i1(next state) from the witness vector and external inputs. | ||
let z_i1 = z_i[..self.state_len()].to_vec(); | ||
CPerezz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Ok(z_i1) | ||
} | ||
|
||
fn generate_step_constraints( | ||
&self, | ||
cs: ConstraintSystemRef<F>, | ||
_i: usize, | ||
z_i: Vec<FpVar<F>>, | ||
// This in reality contains the `witness` passed from the `prove_step` call from the | ||
// browser. | ||
_external_inputs: Vec<FpVar<F>>, | ||
CPerezz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> 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] { | ||
// ruby -e 'p "wtns".bytes' => [119, 116, 110, 115] | ||
CPerezz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
panic!("invalid file header"); | ||
} | ||
let version = reader.read_u32::<LittleEndian>().expect("read_error"); | ||
// println!("wtns version {}", version); | ||
CPerezz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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"); | ||
// if prime != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") { | ||
CPerezz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// panic!("invalid curve prime {:?}", prime); | ||
// } | ||
arnaucube marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let witness_len: u32 = reader.read_u32::<LittleEndian>().expect("read_error"); | ||
// println!("witness len {}", witness_len); | ||
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[..])) | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hope 30a78d3 looks better
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
related: #169 (comment)