diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c6484c..96cb472 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.75.0 + toolchain: stable override: true # Install for Anvil @@ -34,11 +34,6 @@ jobs: export PATH=$HOME/bin:$PATH cargo test - - name: cargo test circom 2 feature flag - run: | - export PATH=$HOME/bin:$PATH - cargo test --features circom-2 - lint: runs-on: ubuntu-latest steps: @@ -48,10 +43,25 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.75.0 + toolchain: stable override: true components: rustfmt, clippy - name: cargo fmt run: cargo fmt --all -- --check - name: cargo clippy run: cargo clippy -- -D warnings + + wasm32-wasip1-check: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + target: wasm32-wasip1 + - name: Check wasm32-wasip compilation + run: cargo check --target wasm32-wasip1 --no-default-features --features="wasm" diff --git a/.gitignore b/.gitignore index 96ef6c0..4ae3942 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +*.ptau \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6118ef7..0f66d8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,32 +1,35 @@ [package] name = "ark-circom" -version = "0.1.0" +version = "0.5.0" edition = "2021" description = "Arkworks bindings to Circom's R1CS, for Groth16 Proof and Witness generation in Rust" +homepage = "https://arkworks.rs" +repository = "https://github.com/arkworks-rs/circom-compat" license = "MIT OR Apache-2.0" +resolver = "2" [lib] crate-type = ["cdylib", "rlib"] [dependencies] # WASM operations -wasmer = "4.4.0" -wasmer-wasix = { version = "0.28.0" } +wasmer = { version = "4.4.0", default-features = false } +wasmer-wasix = { version = "0.28.0", default-features = false } fnv = { version = "1.0.7", default-features = false } num = { version = "0.4.3" } num-traits = { version = "0.2.16", default-features = false } num-bigint = { version = "0.4.3", default-features = false, features = ["rand"] } # ZKP Generation -ark-crypto-primitives = { version = "0.4.0" } -ark-ec = { version = "0.4.2", default-features = false, features = ["parallel"] } -ark-ff = { version = "0.4.2", default-features = false, features = ["parallel", "asm"] } -ark-std = { version = "0.4.0", default-features = false, features = ["parallel"] } -ark-bn254 = { version = "0.4.0" } -ark-groth16 = { version = "0.4.0", default-features = false, features = ["parallel"] } -ark-poly = { version = "0.4.2", default-features = false, features = ["parallel"] } -ark-relations = { version = "0.4.0", default-features = false } -ark-serialize = { version = "0.4.2", default-features = false } +ark-crypto-primitives = { version = "0.5.0" } +ark-ff = { version = "0.5.0", default-features = false, features = ["parallel", "asm"] } +ark-ec = { version = "0.5.0", default-features = false, features = ["parallel"] } +ark-std = { version = "0.5.0", default-features = false, features = ["parallel"] } +ark-bn254 = { version = "0.5.0" } +ark-groth16 = { version = "0.5.0", default-features = false, features = ["parallel"] } +ark-poly = { version = "0.5.0", default-features = false, features = ["parallel"] } +ark-relations = { version = "0.5.0", default-features = false } +ark-serialize = { version = "0.5.0", default-features = false } # decoding of data @@ -54,8 +57,7 @@ name = "groth16" harness = false [features] -default = ["wasmer/default", "circom-2", "ethereum"] -wasm = ["wasmer/js-default"] +default = ["wasmer/default", "ethereum", "wasmer-wasix/default"] +wasm = ["wasmer/js-default", "wasmer-wasix/js"] bench-complex-all = [] -circom-2 = [] ethereum = ["ethers-core"] diff --git a/README.md b/README.md index 0bbc9bb..72ba5bf 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Clone the repository and run `cd ark-circom/ && cargo doc --open` ```toml [dependencies] -ark-circom = { git = "https://github.com/gakonst/ark-circom.git" } +ark-circom = "0.5.0" ``` ## Example @@ -66,6 +66,13 @@ Tests require the following installed: - [x] Proof generations and verification using Arkworks - [ ] CLI for common operations +## Notes + +The prover key generated by circom differs from the one generated by arkworks' groth16 library. While the format is the same, it represents different values. +Circom 'prepares' the powers of tau by converting them to Lagrange base, i.e. from `s^i.G` -> `L_i(s).G`. This affects the witness generation process, and the caller needs to ensure the correct `R1CSToQAP` implementer is used: +- use [`CircomReduction`](https://github.com/arkworks-rs/circom-compat/blob/b892c62597687c23341cda1e8e89d58bb6428f36/src/circom/qap.rs#L12) for working with circom-generated files, +- use [`LibsnarkReduction`](https://github.com/arkworks-rs/groth16/blob/5272c935bda290a24cd18d0a3f994b0af70d5f27/src/r1cs_to_qap.rs#L101) for setup produced using the arkworks backend. + ## Acknowledgements This library would not have been possibly without the great work done in: diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d41dce5..31578d3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,2 @@ [toolchain] -channel = "stable" -version = "1.75.0" +channel = "stable" \ No newline at end of file diff --git a/src/circom/circuit.rs b/src/circom/circuit.rs index 4954279..be76518 100644 --- a/src/circom/circuit.rs +++ b/src/circom/circuit.rs @@ -92,7 +92,7 @@ mod tests { #[tokio::test] async fn satisfied() { let cfg = CircomConfig::::new( - "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit_js/mycircuit.wasm", "./test-vectors/mycircuit.r1cs", ) .unwrap(); diff --git a/src/witness/circom.rs b/src/witness/circom.rs index c88c1b9..ad3053d 100644 --- a/src/witness/circom.rs +++ b/src/witness/circom.rs @@ -7,164 +7,57 @@ pub struct Wasm { pub memory: Memory, } -pub trait CircomBase { - fn init(&self, store: &mut Store, sanity_check: bool) -> Result<()>; - fn func(&self, name: &str) -> &Function; - fn get_n_vars(&self, store: &mut Store) -> Result; - fn get_u32(&self, store: &mut Store, name: &str) -> Result; - // Only exists natively in Circom2, hardcoded for Circom - fn get_version(&self, store: &mut Store) -> Result; -} - -pub trait Circom1 { - fn get_ptr_witness(&self, store: &mut Store, w: u32) -> Result; - fn get_fr_len(&self, store: &mut Store) -> Result; - fn get_signal_offset32( - &self, - store: &mut Store, - p_sig_offset: u32, - component: u32, - hash_msb: u32, - hash_lsb: u32, - ) -> Result<()>; - fn set_signal( - &self, - store: &mut Store, - c_idx: u32, - component: u32, - signal: u32, - p_val: u32, - ) -> Result<()>; - fn get_ptr_raw_prime(&self, store: &mut Store) -> Result; -} - -pub trait Circom2 { - fn get_field_num_len32(&self, store: &mut Store) -> Result; - fn get_raw_prime(&self, store: &mut Store) -> Result<()>; - fn read_shared_rw_memory(&self, store: &mut Store, i: u32) -> Result; - fn write_shared_rw_memory(&self, store: &mut Store, i: u32, v: u32) -> Result<()>; - fn set_input_signal(&self, store: &mut Store, hmsb: u32, hlsb: u32, pos: u32) -> Result<()>; - fn get_witness(&self, store: &mut Store, i: u32) -> Result<()>; - fn get_witness_size(&self, store: &mut Store) -> Result; -} - -impl Circom1 for Wasm { - fn get_fr_len(&self, store: &mut Store) -> Result { - self.get_u32(store, "getFrLen") - } - - fn get_ptr_raw_prime(&self, store: &mut Store) -> Result { - self.get_u32(store, "getPRawPrime") - } - - fn get_ptr_witness(&self, store: &mut Store, w: u32) -> Result { - let func = self.func("getPWitness"); - - let res = func.call(store, &[w.into()])?; - - Ok(res[0].unwrap_i32() as u32) - } - - fn get_signal_offset32( - &self, - store: &mut Store, - p_sig_offset: u32, - component: u32, - hash_msb: u32, - hash_lsb: u32, - ) -> Result<()> { - let func = self.func("getSignalOffset32"); - func.call( - store, - &[ - p_sig_offset.into(), - component.into(), - hash_msb.into(), - hash_lsb.into(), - ], - )?; - - Ok(()) - } - - fn set_signal( - &self, - store: &mut Store, - c_idx: u32, - component: u32, - signal: u32, - p_val: u32, - ) -> Result<()> { - let func = self.func("setSignal"); - func.call( - store, - &[c_idx.into(), component.into(), signal.into(), p_val.into()], - )?; - - Ok(()) - } -} - -#[cfg(feature = "circom-2")] -impl Circom2 for Wasm { - fn get_field_num_len32(&self, store: &mut Store) -> Result { +impl Wasm { + pub(crate) fn get_field_num_len32(&self, store: &mut Store) -> Result { self.get_u32(store, "getFieldNumLen32") } - fn get_raw_prime(&self, store: &mut Store) -> Result<()> { + pub(crate) fn get_raw_prime(&self, store: &mut Store) -> Result<()> { let func = self.func("getRawPrime"); func.call(store, &[])?; Ok(()) } - fn read_shared_rw_memory(&self, store: &mut Store, i: u32) -> Result { + pub(crate) fn read_shared_rw_memory(&self, store: &mut Store, i: u32) -> Result { let func = self.func("readSharedRWMemory"); let result = func.call(store, &[i.into()])?; Ok(result[0].unwrap_i32() as u32) } - fn write_shared_rw_memory(&self, store: &mut Store, i: u32, v: u32) -> Result<()> { + pub(crate) fn write_shared_rw_memory(&self, store: &mut Store, i: u32, v: u32) -> Result<()> { let func = self.func("writeSharedRWMemory"); func.call(store, &[i.into(), v.into()])?; Ok(()) } - fn set_input_signal(&self, store: &mut Store, hmsb: u32, hlsb: u32, pos: u32) -> Result<()> { + pub(crate) fn set_input_signal( + &self, + store: &mut Store, + hmsb: u32, + hlsb: u32, + pos: u32, + ) -> Result<()> { let func = self.func("setInputSignal"); func.call(store, &[hmsb.into(), hlsb.into(), pos.into()])?; Ok(()) } - fn get_witness(&self, store: &mut Store, i: u32) -> Result<()> { + pub(crate) fn get_witness(&self, store: &mut Store, i: u32) -> Result<()> { let func = self.func("getWitness"); func.call(store, &[i.into()])?; Ok(()) } - fn get_witness_size(&self, store: &mut Store) -> Result { + pub(crate) fn get_witness_size(&self, store: &mut Store) -> Result { self.get_u32(store, "getWitnessSize") } -} -impl CircomBase for Wasm { - fn init(&self, store: &mut Store, sanity_check: bool) -> Result<()> { + pub(crate) fn init(&self, store: &mut Store, sanity_check: bool) -> Result<()> { let func = self.func("init"); func.call(store, &[Value::I32(sanity_check as i32)])?; Ok(()) } - fn get_n_vars(&self, store: &mut Store) -> Result { - self.get_u32(store, "getNVars") - } - - // Default to version 1 if it isn't explicitly defined - fn get_version(&self, store: &mut Store) -> Result { - match self.exports.get_function("getVersion") { - Ok(func) => Ok(func.call(store, &[])?[0].unwrap_i32() as u32), - Err(_) => Ok(1), - } - } - fn get_u32(&self, store: &mut Store, name: &str) -> Result { let func = &self.func(name); let result = func.call(store, &[])?; diff --git a/src/witness/mod.rs b/src/witness/mod.rs index cbb8a8a..f396bd2 100644 --- a/src/witness/mod.rs +++ b/src/witness/mod.rs @@ -5,14 +5,8 @@ mod memory; pub(super) use memory::SafeMemory; mod circom; -pub(super) use circom::CircomBase; pub use circom::Wasm; -#[cfg(feature = "circom-2")] -pub(super) use circom::Circom2; - -pub(super) use circom::Circom1; - use fnv::FnvHasher; use std::hash::Hasher; diff --git a/src/witness/witness_calculator.rs b/src/witness/witness_calculator.rs index 40729bd..c9e1888 100644 --- a/src/witness/witness_calculator.rs +++ b/src/witness/witness_calculator.rs @@ -1,4 +1,4 @@ -use super::{fnv, CircomBase, SafeMemory, Wasm}; +use super::{fnv, SafeMemory, Wasm}; use ark_ff::PrimeField; use color_eyre::Result; use num_bigint::BigInt; @@ -6,19 +6,13 @@ use num_traits::Zero; use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, RuntimeError, Store}; use wasmer_wasix::WasiEnv; -#[cfg(feature = "circom-2")] use num::ToPrimitive; -use super::Circom1; -#[cfg(feature = "circom-2")] -use super::Circom2; - #[derive(Debug)] pub struct WitnessCalculator { pub instance: Wasm, pub memory: Option, pub n64: u32, - pub circom_version: u32, pub prime: BigInt, } @@ -28,7 +22,6 @@ pub struct WitnessCalculator { #[error("{0}")] struct ExitCode(u32); -#[cfg(feature = "circom-2")] fn from_array32(arr: Vec) -> BigInt { let mut res = BigInt::zero(); let radix = BigInt::from(0x100000000u64); @@ -38,7 +31,6 @@ fn from_array32(arr: Vec) -> BigInt { res } -#[cfg(feature = "circom-2")] fn to_array32(s: &BigInt, size: usize) -> Vec { let mut res = vec![0; size]; let mut rem = s.clone(); @@ -101,78 +93,24 @@ impl WitnessCalculator { Ok(wasm) } - pub fn new_from_wasm(store: &mut Store, wasm: Wasm) -> Result { - let version = wasm.get_version(store).unwrap_or(1); - // Circom 2 feature flag with version 2 - #[cfg(feature = "circom-2")] - fn new_circom2( - instance: Wasm, - store: &mut Store, - version: u32, - ) -> Result { - let n32 = instance.get_field_num_len32(store)?; - instance.get_raw_prime(store)?; - let mut arr = vec![0; n32 as usize]; - for i in 0..n32 { - let res = instance.read_shared_rw_memory(store, i)?; - arr[(n32 as usize) - (i as usize) - 1] = res; - } - let prime = from_array32(arr); - - let n64 = ((prime.bits() - 1) / 64 + 1) as u32; - - Ok(WitnessCalculator { - instance, - memory: None, - n64, - circom_version: version, - prime, - }) + pub fn new_from_wasm(store: &mut Store, instance: Wasm) -> Result { + let n32 = instance.get_field_num_len32(store)?; + instance.get_raw_prime(store)?; + let mut arr = vec![0; n32 as usize]; + for i in 0..n32 { + let res = instance.read_shared_rw_memory(store, i)?; + arr[(n32 as usize) - (i as usize) - 1] = res; } + let prime = from_array32(arr); - fn new_circom1( - instance: Wasm, - store: &mut Store, - version: u32, - ) -> Result { - // Fallback to Circom 1 behavior - let n32 = (instance.get_fr_len(store)? >> 2) - 2; - let mut safe_memory = - SafeMemory::new(instance.memory.clone(), n32 as usize, BigInt::zero()); - let ptr = instance.get_ptr_raw_prime(store)?; - let prime = safe_memory.read_big(store, ptr as usize, n32 as usize)?; + let n64 = ((prime.bits() - 1) / 64 + 1) as u32; - let n64 = ((prime.bits() - 1) / 64 + 1) as u32; - safe_memory.prime = prime.clone(); - - Ok(WitnessCalculator { - instance, - memory: Some(safe_memory), - n64, - circom_version: version, - prime, - }) - } - - // Three possibilities: - // a) Circom 2 feature flag enabled, WASM runtime version 2 - // b) Circom 2 feature flag enabled, WASM runtime version 1 - // c) Circom 1 default behavior - // - // Once Circom 2 support is more stable, feature flag can be removed - - cfg_if::cfg_if! { - if #[cfg(feature = "circom-2")] { - match version { - 2 => new_circom2(wasm, store, version), - 1 => new_circom1(wasm, store, version), - - _ => panic!("Unknown Circom version") - } - } else { - new_circom1(instance, memory, version) - } - } + Ok(WitnessCalculator { + instance, + memory: None, + n64, + prime, + }) } pub fn calculate_witness)>>( @@ -183,77 +121,6 @@ impl WitnessCalculator { ) -> Result> { self.instance.init(store, sanity_check)?; - cfg_if::cfg_if! { - if #[cfg(feature = "circom-2")] { - match self.circom_version { - 2 => self.calculate_witness_circom2(store, inputs), - 1 => self.calculate_witness_circom1(store, inputs), - _ => panic!("Unknown Circom version") - } - } else { - self.calculate_witness_circom1(inputs, sanity_check) - } - } - } - - // Circom 1 default behavior - fn calculate_witness_circom1)>>( - &mut self, - store: &mut Store, - inputs: I, - ) -> Result> { - let old_mem_free_pos = self.memory.as_ref().unwrap().free_pos(store)?; - let p_sig_offset = self.memory.as_mut().unwrap().alloc_u32(store)?; - let p_fr = self.memory.as_mut().unwrap().alloc_fr(store)?; - - // allocate the inputs - for (name, values) in inputs.into_iter() { - let (msb, lsb) = fnv(&name); - - self.instance - .get_signal_offset32(store, p_sig_offset, 0, msb, lsb)?; - - let sig_offset = self - .memory - .as_ref() - .unwrap() - .read_u32(store, p_sig_offset as usize) - .unwrap() as usize; - - for (i, value) in values.into_iter().enumerate() { - self.memory - .as_mut() - .unwrap() - .write_fr(store, p_fr as usize, &value)?; - self.instance - .set_signal(store, 0, 0, (sig_offset + i) as u32, p_fr)?; - } - } - - let mut w = Vec::new(); - - let n_vars = self.instance.get_n_vars(store)?; - for i in 0..n_vars { - let ptr = self.instance.get_ptr_witness(store, i)? as usize; - let el = self.memory.as_ref().unwrap().read_fr(store, ptr)?; - w.push(el); - } - - self.memory - .as_mut() - .unwrap() - .set_free_pos(store, old_mem_free_pos)?; - - Ok(w) - } - - // Circom 2 feature flag with version 2 - #[cfg(feature = "circom-2")] - fn calculate_witness_circom2)>>( - &mut self, - store: &mut Store, - inputs: I, - ) -> Result> { let n32 = self.instance.get_field_num_len32(store)?; // allocate the inputs @@ -385,7 +252,6 @@ mod tests { struct TestCase<'a> { circuit_path: &'a str, inputs_path: &'a str, - n_vars: u32, n64: u32, witness: &'a [&'a str], } @@ -399,9 +265,8 @@ mod tests { #[tokio::test] async fn multiplier_1() { run_test(TestCase { - circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + circuit_path: root_path("test-vectors/mycircuit_js/mycircuit.wasm").as_str(), inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(), - n_vars: 4, n64: 4, witness: &["1", "33", "3", "11"], }); @@ -410,9 +275,8 @@ mod tests { #[tokio::test] async fn multiplier_2() { run_test(TestCase { - circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + circuit_path: root_path("test-vectors/mycircuit_js/mycircuit.wasm").as_str(), inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(), - n_vars: 4, n64: 4, witness: &[ "1", @@ -426,9 +290,8 @@ mod tests { #[tokio::test] async fn multiplier_3() { run_test(TestCase { - circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + circuit_path: root_path("test-vectors/mycircuit_js/mycircuit.wasm").as_str(), inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(), - n_vars: 4, n64: 4, witness: &[ "1", @@ -446,25 +309,8 @@ mod tests { let witness: Vec = serde_json::from_str(&witness).unwrap(); let witness = &witness.iter().map(|x| x.as_ref()).collect::>(); run_test(TestCase { - circuit_path: root_path("test-vectors/circuit2.wasm").as_str(), + circuit_path: root_path("test-vectors/circuit2_js/circuit2.wasm").as_str(), inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(), - n_vars: 132, // 128 + 4 - n64: 4, - witness, - }); - } - - #[tokio::test] - async fn smt_verifier() { - let witness = - std::fs::read_to_string(root_path("test-vectors/smtverifier10-witness.json")).unwrap(); - let witness: Vec = serde_json::from_str(&witness).unwrap(); - let witness = &witness.iter().map(|x| x.as_ref()).collect::>(); - - run_test(TestCase { - circuit_path: root_path("test-vectors/smtverifier10.wasm").as_str(), - inputs_path: root_path("test-vectors/smtverifier10-input.json").as_str(), - n_vars: 4794, n64: 4, witness, }); @@ -488,10 +334,6 @@ mod tests { wtns.prime.to_str_radix(16), "30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase() ); - assert_eq!( - { wtns.instance.get_n_vars(&mut store).unwrap() }, - case.n_vars - ); assert_eq!({ wtns.n64 }, case.n64); let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap(); diff --git a/src/zkey.rs b/src/zkey.rs index d8ba1cb..d52479f 100644 --- a/src/zkey.rs +++ b/src/zkey.rs @@ -850,7 +850,7 @@ mod tests { let (params, _matrices) = read_zkey(&mut file).unwrap(); // binfile.proving_key().unwrap(); let cfg = CircomConfig::::new( - "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit_js/mycircuit.wasm", "./test-vectors/mycircuit.r1cs", ) .unwrap(); @@ -878,7 +878,9 @@ mod tests { let mut file = File::open(path).unwrap(); let (params, matrices) = read_zkey(&mut file).unwrap(); let mut store = Store::default(); - let mut wtns = WitnessCalculator::new(&mut store, "./test-vectors/mycircuit.wasm").unwrap(); + let mut wtns = + WitnessCalculator::new(&mut store, "./test-vectors/mycircuit_js/mycircuit.wasm") + .unwrap(); let mut inputs: HashMap> = HashMap::new(); let values = inputs.entry("a".to_string()).or_insert_with(Vec::new); values.push(3.into()); diff --git a/test-vectors/calculatewitness.js b/test-vectors/calculatewitness.js deleted file mode 100755 index d562d27..0000000 --- a/test-vectors/calculatewitness.js +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env node - -const fs = require("fs"); -const {stringifyBigInts, unstringifyBigInts} = require("snarkjs"); -const WitnessCalculatorBuilder = require("./witness_calculator.js"); - -// const wasmName = "smtverifier10.wasm" -// const inputName = "smtverifier10-input.json" - -const wasmName = "nconstraints.wasm" -const inputName = "nconstraints-input.json" - -async function run () { - const wasm = await fs.promises.readFile(wasmName); - const input = unstringifyBigInts(JSON.parse(await fs.promises.readFile(inputName, "utf8"))); - - console.log("input:", input); - let options; - const wc = await WitnessCalculatorBuilder(wasm, options); - - const w = await wc.calculateWitness(input); - - console.log("witness:\n", JSON.stringify(stringifyBigInts(w))); - - // const wb = await wc.calculateBinWitness(input); - - // console.log("witnessBin:", Buffer.from(wb).toString('hex')); - - // await fs.promises.writeFile(witnessName, JSON.stringify(stringifyBigInts(w), null, 1)); - -} - -run().then(() => { - process.exit(); -}); diff --git a/test-vectors/circom2_multiplier2.r1cs b/test-vectors/circom2_multiplier2.r1cs deleted file mode 100644 index e61b9b0..0000000 Binary files a/test-vectors/circom2_multiplier2.r1cs and /dev/null differ diff --git a/test-vectors/circuit2.circom b/test-vectors/circuit2.circom index 1cd0da2..db8dd2a 100644 --- a/test-vectors/circuit2.circom +++ b/test-vectors/circuit2.circom @@ -1,3 +1,5 @@ +pragma circom 2.0.0; + template CheckBits(n) { signal input in; signal bits[n]; @@ -15,8 +17,8 @@ template CheckBits(n) { } template Multiplier(n) { - signal private input a; - signal private input b; + signal input a; + signal input b; signal output c; signal inva; signal invb; diff --git a/test-vectors/circuit2.r1cs b/test-vectors/circuit2.r1cs new file mode 100644 index 0000000..cb3fbdf Binary files /dev/null and b/test-vectors/circuit2.r1cs differ diff --git a/test-vectors/circuit2.wasm b/test-vectors/circuit2.wasm deleted file mode 100644 index 3495132..0000000 Binary files a/test-vectors/circuit2.wasm and /dev/null differ diff --git a/test-vectors/circuit2_js/circuit2.wasm b/test-vectors/circuit2_js/circuit2.wasm new file mode 100644 index 0000000..706eee9 Binary files /dev/null and b/test-vectors/circuit2_js/circuit2.wasm differ diff --git a/test-vectors/circuit2_js/generate_witness.js b/test-vectors/circuit2_js/generate_witness.js new file mode 100644 index 0000000..eabb86e --- /dev/null +++ b/test-vectors/circuit2_js/generate_witness.js @@ -0,0 +1,20 @@ +const wc = require("./witness_calculator.js"); +const { readFileSync, writeFile } = require("fs"); + +if (process.argv.length != 5) { + console.log("Usage: node generate_witness.js "); +} else { + const input = JSON.parse(readFileSync(process.argv[3], "utf8")); + + const buffer = readFileSync(process.argv[2]); + wc(buffer).then(async witnessCalculator => { + // const w= await witnessCalculator.calculateWitness(input,0); + // for (let i=0; i< w.length; i++){ + // console.log(w[i]); + // } + const buff= await witnessCalculator.calculateWTNSBin(input,0); + writeFile(process.argv[4], buff, function(err) { + if (err) throw err; + }); + }); +} diff --git a/test-vectors/circuit2_js/witness.wtns b/test-vectors/circuit2_js/witness.wtns new file mode 100644 index 0000000..d9dce5d Binary files /dev/null and b/test-vectors/circuit2_js/witness.wtns differ diff --git a/test-vectors/circuit2_js/witness_calculator.js b/test-vectors/circuit2_js/witness_calculator.js new file mode 100644 index 0000000..20e6e20 --- /dev/null +++ b/test-vectors/circuit2_js/witness_calculator.js @@ -0,0 +1,337 @@ +module.exports = async function builder(code, options) { + + options = options || {}; + + let wasmModule; + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log("\nTry to run circom --c in order to generate c++ code instead\n"); + throw new Error(err); + } + + let wc; + + let errStr = ""; + let msgStr = ""; + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler : function(code) { + let err; + if (code == 1) { + err = "Signal not found.\n"; + } else if (code == 2) { + err = "Too many signals set.\n"; + } else if (code == 3) { + err = "Signal already set.\n"; + } else if (code == 4) { + err = "Assert Failed.\n"; + } else if (code == 5) { + err = "Not enough memory.\n"; + } else if (code == 6) { + err = "Input signal array access exceeds the size.\n"; + } else { + err = "Unknown error.\n"; + } + throw new Error(err + errStr); + }, + printErrorMessage : function() { + errStr += getMessage() + "\n"; + // console.error(getMessage()); + }, + writeBufferMessage : function() { + const msg = getMessage(); + // Any calls to `log()` will always end with a `\n`, so that's when we print and reset + if (msg === "\n") { + console.log(msgStr); + msgStr = ""; + } else { + // If we've buffered other content, put a space in between the items + if (msgStr !== "") { + msgStr += " " + } + // Then append the message to the message we are creating + msgStr += msg; + } + }, + showSharedRWMemory : function() { + printSharedRWMemory (); + } + + } + }); + + const sanityCheck = + options +// options && +// ( +// options.sanityCheck || +// options.logGetSignal || +// options.logSetSignal || +// options.logStartComponent || +// options.logFinishComponent +// ); + + + wc = new WitnessCalculator(instance, sanityCheck); + return wc; + + function getMessage() { + var message = ""; + var c = instance.exports.getMessageChar(); + while ( c != 0 ) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + return message; + } + + function printSharedRWMemory () { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + for (let j=0; j { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + const fArr = flatArray(input[k]); + let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + if (signalSize < 0){ + throw new Error(`Signal ${k} not found\n`); + } + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + for (let i=0; i0) { + res.unshift(0); + i--; + } + } + return res; +} + +function fromArray32(arr) { //returns a BigInt + var res = BigInt(0); + const radix = BigInt(0x100000000); + for (let i = 0; i "); +} else { + const input = JSON.parse(readFileSync(process.argv[3], "utf8")); + + const buffer = readFileSync(process.argv[2]); + wc(buffer).then(async witnessCalculator => { + // const w= await witnessCalculator.calculateWitness(input,0); + // for (let i=0; i< w.length; i++){ + // console.log(w[i]); + // } + const buff= await witnessCalculator.calculateWTNSBin(input,0); + writeFile(process.argv[4], buff, function(err) { + if (err) throw err; + }); + }); +} diff --git a/test-vectors/circom2_multiplier2.wasm b/test-vectors/mycircuit_js/mycircuit.wasm similarity index 50% rename from test-vectors/circom2_multiplier2.wasm rename to test-vectors/mycircuit_js/mycircuit.wasm index 7ea487c..ef5493a 100644 Binary files a/test-vectors/circom2_multiplier2.wasm and b/test-vectors/mycircuit_js/mycircuit.wasm differ diff --git a/test-vectors/mycircuit_js/witness_calculator.js b/test-vectors/mycircuit_js/witness_calculator.js new file mode 100644 index 0000000..20e6e20 --- /dev/null +++ b/test-vectors/mycircuit_js/witness_calculator.js @@ -0,0 +1,337 @@ +module.exports = async function builder(code, options) { + + options = options || {}; + + let wasmModule; + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log("\nTry to run circom --c in order to generate c++ code instead\n"); + throw new Error(err); + } + + let wc; + + let errStr = ""; + let msgStr = ""; + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler : function(code) { + let err; + if (code == 1) { + err = "Signal not found.\n"; + } else if (code == 2) { + err = "Too many signals set.\n"; + } else if (code == 3) { + err = "Signal already set.\n"; + } else if (code == 4) { + err = "Assert Failed.\n"; + } else if (code == 5) { + err = "Not enough memory.\n"; + } else if (code == 6) { + err = "Input signal array access exceeds the size.\n"; + } else { + err = "Unknown error.\n"; + } + throw new Error(err + errStr); + }, + printErrorMessage : function() { + errStr += getMessage() + "\n"; + // console.error(getMessage()); + }, + writeBufferMessage : function() { + const msg = getMessage(); + // Any calls to `log()` will always end with a `\n`, so that's when we print and reset + if (msg === "\n") { + console.log(msgStr); + msgStr = ""; + } else { + // If we've buffered other content, put a space in between the items + if (msgStr !== "") { + msgStr += " " + } + // Then append the message to the message we are creating + msgStr += msg; + } + }, + showSharedRWMemory : function() { + printSharedRWMemory (); + } + + } + }); + + const sanityCheck = + options +// options && +// ( +// options.sanityCheck || +// options.logGetSignal || +// options.logSetSignal || +// options.logStartComponent || +// options.logFinishComponent +// ); + + + wc = new WitnessCalculator(instance, sanityCheck); + return wc; + + function getMessage() { + var message = ""; + var c = instance.exports.getMessageChar(); + while ( c != 0 ) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + return message; + } + + function printSharedRWMemory () { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + for (let j=0; j { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + const fArr = flatArray(input[k]); + let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + if (signalSize < 0){ + throw new Error(`Signal ${k} not found\n`); + } + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + for (let i=0; i0) { + res.unshift(0); + i--; + } + } + return res; +} + +function fromArray32(arr) { //returns a BigInt + var res = BigInt(0); + const radix = BigInt(0x100000000); + for (let i = 0; i; #[tokio::test] async fn groth16_proof() -> Result<()> { let cfg = CircomConfig::::new( - "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit_js/mycircuit.wasm", "./test-vectors/mycircuit.r1cs", )?; let mut builder = CircomBuilder::new(cfg); @@ -40,32 +40,43 @@ async fn groth16_proof() -> Result<()> { } #[tokio::test] -async fn groth16_proof_wrong_input() { +async fn groth16_proof_wrong_input() -> Result<()> { let cfg = CircomConfig::::new( - "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit_js/mycircuit.wasm", "./test-vectors/mycircuit.r1cs", ) .unwrap(); let mut builder = CircomBuilder::new(cfg); builder.push_input("a", 3); - // This isn't a public input to the circuit, should fail + // This isn't a public input to the circuit, should fail verification builder.push_input("foo", 11); // create an empty instance for setting it up let circom = builder.setup(); let mut rng = thread_rng(); - let _params = GrothBn::generate_random_parameters_with_reduction(circom, &mut rng).unwrap(); + let params = GrothBn::generate_random_parameters_with_reduction(circom, &mut rng).unwrap(); - let _ = builder.build().unwrap_err(); + let circom = builder.build().unwrap(); + + // we need to manually specify the public input, else the circuit builder will take the default for b = 0, and set public input to 0 (=11*0). + let inputs = vec![Fr::from(33u64)]; + + let proof = GrothBn::prove(¶ms, circom, &mut rng).unwrap(); + + let pvk = GrothBn::process_vk(¶ms.vk).unwrap(); + + let verified = GrothBn::verify_with_processed_vk(&pvk, &inputs, &proof).unwrap(); + assert!(!verified); + + Ok(()) } #[tokio::test] -#[cfg(feature = "circom-2")] -async fn groth16_proof_circom2() -> Result<()> { +async fn groth16_proof_circom() -> Result<()> { let cfg = CircomConfig::::new( - "./test-vectors/circom2_multiplier2.wasm", - "./test-vectors/circom2_multiplier2.r1cs", + "test-vectors/circuit2_js/circuit2.wasm", + "test-vectors/circuit2.r1cs", )?; let mut builder = CircomBuilder::new(cfg); builder.push_input("a", 3); @@ -93,11 +104,10 @@ async fn groth16_proof_circom2() -> Result<()> { } #[tokio::test] -#[cfg(feature = "circom-2")] -async fn witness_generation_circom2() -> Result<()> { +async fn witness_generation_circom() -> Result<()> { let cfg = CircomConfig::::new( - "./test-vectors/circom2_multiplier2.wasm", - "./test-vectors/circom2_multiplier2.r1cs", + "test-vectors/circuit2_js/circuit2.wasm", + "test-vectors/circuit2.r1cs", )?; let mut builder = CircomBuilder::new(cfg); builder.push_input("a", 3); diff --git a/tests/solidity.rs b/tests/solidity.rs index bd7235d..f22a1bb 100644 --- a/tests/solidity.rs +++ b/tests/solidity.rs @@ -17,7 +17,7 @@ use std::{convert::TryFrom, sync::Arc}; #[tokio::test] async fn solidity_verifier() -> Result<()> { let cfg = CircomConfig::::new( - "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit_js/mycircuit.wasm", "./test-vectors/mycircuit.r1cs", )?; let mut builder = CircomBuilder::new(cfg);