mirror of
https://github.com/logos-storage/circom-compat.git
synced 2026-01-03 21:43:07 +00:00
commit
a1cd579d11
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
# Install for Anvil
|
# Install for Anvil
|
||||||
@ -34,11 +34,6 @@ jobs:
|
|||||||
export PATH=$HOME/bin:$PATH
|
export PATH=$HOME/bin:$PATH
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
- name: cargo test circom 2 feature flag
|
|
||||||
run: |
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
cargo test --features circom-2
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -48,10 +43,25 @@ jobs:
|
|||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: cargo clippy
|
- name: cargo clippy
|
||||||
run: cargo clippy -- -D warnings
|
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"
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
*.ptau
|
||||||
4942
Cargo.lock
generated
4942
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
64
Cargo.toml
64
Cargo.toml
@ -1,59 +1,63 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ark-circom"
|
name = "ark-circom"
|
||||||
version = "0.1.0"
|
version = "0.5.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
description = "Arkworks bindings to Circom's R1CS, for Groth16 Proof and Witness generation in Rust"
|
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"
|
license = "MIT OR Apache-2.0"
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# WASM operations
|
# WASM operations
|
||||||
wasmer = { version = "=2.3.0", default-features = false }
|
wasmer = { version = "4.4.0", default-features = false }
|
||||||
fnv = { version = "=1.0.7", default-features = false }
|
wasmer-wasix = { version = "0.28.0", default-features = false }
|
||||||
num = { version = "=0.4.0" }
|
fnv = { version = "1.0.7", default-features = false }
|
||||||
num-traits = { version = "=0.2.15", default-features = false }
|
num = { version = "0.4.3" }
|
||||||
num-bigint = { version = "=0.4.3", default-features = false, features = ["rand"] }
|
num-traits = { version = "0.2.16", default-features = false }
|
||||||
|
num-bigint = { version = "0.4.3", default-features = false, features = ["rand"] }
|
||||||
|
|
||||||
# ZKP Generation
|
# ZKP Generation
|
||||||
ark-crypto-primitives = { version = "=0.4.0" }
|
ark-crypto-primitives = { version = "0.5.0" }
|
||||||
ark-ec = { version = "=0.4.1", default-features = false, features = ["parallel"] }
|
ark-ff = { version = "0.5.0", default-features = false, features = ["parallel", "asm"] }
|
||||||
ark-ff = { version = "=0.4.1", default-features = false, features = ["parallel", "asm"] }
|
ark-ec = { version = "0.5.0", default-features = false, features = ["parallel"] }
|
||||||
ark-std = { version = "=0.4.0", default-features = false, features = ["parallel"] }
|
ark-std = { version = "0.5.0", default-features = false, features = ["parallel"] }
|
||||||
ark-bn254 = { version = "=0.4.0" }
|
ark-bn254 = { version = "0.5.0" }
|
||||||
ark-groth16 = { version = "=0.4.0", default-features = false, features = ["parallel"] }
|
ark-groth16 = { version = "0.5.0", default-features = false, features = ["parallel"] }
|
||||||
ark-poly = { version = "=0.4.1", default-features = false, features = ["parallel"] }
|
ark-poly = { version = "0.5.0", default-features = false, features = ["parallel"] }
|
||||||
ark-relations = { version = "=0.4.0", default-features = false }
|
ark-relations = { version = "0.5.0", default-features = false }
|
||||||
ark-serialize = { version = "=0.4.1", default-features = false }
|
ark-serialize = { version = "0.5.0", default-features = false }
|
||||||
|
|
||||||
|
|
||||||
# decoding of data
|
# decoding of data
|
||||||
hex = "=0.4.3"
|
hex = "0.4.3"
|
||||||
byteorder = "=1.4.3"
|
byteorder = "1.4.3"
|
||||||
|
|
||||||
# ethereum compat
|
# ethereum compat
|
||||||
ethers-core = { version = "=2.0.7", default-features = false, optional = true }
|
ethers-core = { version = "2.0.7", default-features = false, optional = true }
|
||||||
|
|
||||||
# error handling
|
# error handling
|
||||||
thiserror = "=1.0.39"
|
thiserror = "1.0.39"
|
||||||
color-eyre = "=0.6.2"
|
color-eyre = "0.6.2"
|
||||||
criterion = "=0.3.6"
|
|
||||||
|
|
||||||
cfg-if = "=1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "=0.2.2"
|
criterion = "0.5.1"
|
||||||
tokio = { version = "=1.29.1", features = ["macros"] }
|
hex-literal = "0.4.1"
|
||||||
serde_json = "=1.0.94"
|
tokio = { version = "1.29.1", features = ["macros"] }
|
||||||
ethers = "=2.0.7"
|
serde_json = "1.0.94"
|
||||||
|
ethers = "2.0.7"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "groth16"
|
name = "groth16"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wasmer/default", "circom-2", "ethereum"]
|
default = ["wasmer/default", "ethereum", "wasmer-wasix/default"]
|
||||||
wasm = ["wasmer/js-default"]
|
wasm = ["wasmer/js-default", "wasmer-wasix/js"]
|
||||||
bench-complex-all = []
|
bench-complex-all = []
|
||||||
circom-2 = []
|
|
||||||
ethereum = ["ethers-core"]
|
ethereum = ["ethers-core"]
|
||||||
|
|||||||
11
README.md
11
README.md
@ -13,7 +13,7 @@ Clone the repository and run `cd ark-circom/ && cargo doc --open`
|
|||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
ark-circom = { git = "https://github.com/gakonst/ark-circom.git" }
|
ark-circom = "0.5.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
@ -54,6 +54,7 @@ assert!(verified);
|
|||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
Tests require the following installed:
|
Tests require the following installed:
|
||||||
|
|
||||||
1. [`solc`](https://solidity.readthedocs.io/en/latest/installing-solidity.html). We also recommend using [solc-select](https://github.com/crytic/solc-select) for more flexibility.
|
1. [`solc`](https://solidity.readthedocs.io/en/latest/installing-solidity.html). We also recommend using [solc-select](https://github.com/crytic/solc-select) for more flexibility.
|
||||||
2. [`ganache-cli`](https://github.com/trufflesuite/ganache-cli#installation)
|
2. [`ganache-cli`](https://github.com/trufflesuite/ganache-cli#installation)
|
||||||
|
|
||||||
@ -65,13 +66,17 @@ Tests require the following installed:
|
|||||||
- [x] Proof generations and verification using Arkworks
|
- [x] Proof generations and verification using Arkworks
|
||||||
- [ ] CLI for common operations
|
- [ ] CLI for common operations
|
||||||
|
|
||||||
## Known limitations
|
## Notes
|
||||||
|
|
||||||
Currently, due to an issue in our upstream (https://github.com/wasmerio/wasmer/issues/4072), this crate works as expected only up to Rust version `1.67.0`; in newer Rust versions, `wasmer` is currently unsound.
|
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
|
## Acknowledgements
|
||||||
|
|
||||||
This library would not have been possibly without the great work done in:
|
This library would not have been possibly without the great work done in:
|
||||||
|
|
||||||
- [`zkutil`](https://github.com/poma/zkutil/)
|
- [`zkutil`](https://github.com/poma/zkutil/)
|
||||||
- [`snarkjs`](https://github.com/iden3/snarkjs/)
|
- [`snarkjs`](https://github.com/iden3/snarkjs/)
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,9 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|||||||
use ark_circom::{read_zkey, CircomReduction, WitnessCalculator};
|
use ark_circom::{read_zkey, CircomReduction, WitnessCalculator};
|
||||||
use ark_std::rand::thread_rng;
|
use ark_std::rand::thread_rng;
|
||||||
|
|
||||||
use ark_bn254::Bn254;
|
use ark_bn254::{Bn254, Fr};
|
||||||
use ark_groth16::Groth16;
|
use ark_groth16::Groth16;
|
||||||
|
use wasmer::Store;
|
||||||
|
|
||||||
use std::{collections::HashMap, fs::File};
|
use std::{collections::HashMap, fs::File};
|
||||||
|
|
||||||
@ -28,14 +29,17 @@ fn bench_groth(c: &mut Criterion, num_validators: u32, num_constraints: u32) {
|
|||||||
|
|
||||||
inputs
|
inputs
|
||||||
};
|
};
|
||||||
|
let mut store = Store::default();
|
||||||
let mut wtns = WitnessCalculator::new(format!(
|
let mut wtns = WitnessCalculator::new(
|
||||||
"./test-vectors/complex-circuit/complex-circuit-{}-{}.wasm",
|
&mut store,
|
||||||
i, j
|
format!(
|
||||||
))
|
"./test-vectors/complex-circuit/complex-circuit-{}-{}.wasm",
|
||||||
|
i, j
|
||||||
|
),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let full_assignment = wtns
|
let full_assignment = wtns
|
||||||
.calculate_witness_element::<Bn254, _>(inputs, false)
|
.calculate_witness_element::<Fr, _>(&mut store, inputs, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.67.0"
|
channel = "stable"
|
||||||
|
|||||||
@ -1,55 +1,75 @@
|
|||||||
use ark_ec::pairing::Pairing;
|
use ark_ff::PrimeField;
|
||||||
use std::{fs::File, path::Path, io::Cursor};
|
use num_bigint::BigInt;
|
||||||
|
use std::{collections::HashMap, fs::File, io::{BufReader, Cursor}, path::Path};
|
||||||
|
use wasmer::Store;
|
||||||
|
|
||||||
use super::{CircomCircuit, R1CS};
|
use super::{CircomCircuit, R1CS};
|
||||||
|
|
||||||
use num_bigint::BigInt;
|
use crate::{
|
||||||
use std::collections::HashMap;
|
circom::R1CSFile,
|
||||||
|
witness::{Wasm, WitnessCalculator},
|
||||||
use crate::{circom::R1CSFile, witness::WitnessCalculator};
|
};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CircomBuilder<E: Pairing> {
|
pub struct CircomBuilder<F: PrimeField> {
|
||||||
pub cfg: CircomConfig<E>,
|
pub cfg: CircomConfig<F>,
|
||||||
pub inputs: HashMap<String, Vec<BigInt>>,
|
pub inputs: HashMap<String, Vec<BigInt>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add utils for creating this from files / directly from bytes
|
// Add utils for creating this from files / directly from bytes
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CircomConfig<E: Pairing> {
|
pub struct CircomConfig<F: PrimeField> {
|
||||||
pub r1cs: R1CS<E>,
|
pub r1cs: R1CS<F>,
|
||||||
pub wtns: WitnessCalculator,
|
pub wtns: WitnessCalculator,
|
||||||
|
pub store: Store,
|
||||||
pub sanity_check: bool,
|
pub sanity_check: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> CircomConfig<E> {
|
impl<F: PrimeField> CircomConfig<F> {
|
||||||
pub fn new(wtns: impl AsRef<Path>, r1cs: impl AsRef<Path>) -> Result<Self> {
|
pub fn new(wtns: impl AsRef<Path>, r1cs: impl AsRef<Path>) -> Result<Self> {
|
||||||
let wtns = WitnessCalculator::new(wtns).unwrap();
|
let mut store = Store::default();
|
||||||
|
let wtns = WitnessCalculator::new(&mut store, wtns).unwrap();
|
||||||
|
let reader = BufReader::new(File::open(r1cs)?);
|
||||||
|
let r1cs = R1CSFile::new(reader)?.into();
|
||||||
|
Ok(Self {
|
||||||
|
wtns,
|
||||||
|
r1cs,
|
||||||
|
store,
|
||||||
|
sanity_check: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_wasm(wasm: Wasm, r1cs: impl AsRef<Path>) -> Result<Self> {
|
||||||
|
let mut store = Store::default();
|
||||||
|
let wtns = WitnessCalculator::new_from_wasm(&mut store, wasm).unwrap();
|
||||||
let reader = File::open(r1cs)?;
|
let reader = File::open(r1cs)?;
|
||||||
let r1cs = R1CSFile::new(reader)?.into();
|
let r1cs = R1CSFile::new(reader)?.into();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
wtns,
|
wtns,
|
||||||
r1cs,
|
r1cs,
|
||||||
|
store,
|
||||||
sanity_check: false,
|
sanity_check: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(wtns: &[u8], r1cs: &[u8]) -> Result<Self> {
|
pub fn new_from_bytes(wtns: &[u8], r1cs: &[u8]) -> Result<Self> {
|
||||||
let wtns = WitnessCalculator::from_bytes(wtns).unwrap();
|
let mut store = Store::default();
|
||||||
|
let wtns = WitnessCalculator::from_bytes(&mut store, wtns).unwrap();
|
||||||
let r1cs = R1CSFile::new(Cursor::new(r1cs))?.into();
|
let r1cs = R1CSFile::new(Cursor::new(r1cs))?.into();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
wtns,
|
wtns,
|
||||||
r1cs,
|
r1cs,
|
||||||
|
store,
|
||||||
sanity_check: false,
|
sanity_check: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> CircomBuilder<E> {
|
impl<F: PrimeField> CircomBuilder<F> {
|
||||||
/// Instantiates a new builder using the provided WitnessGenerator and R1CS files
|
/// Instantiates a new builder using the provided WitnessGenerator and R1CS files
|
||||||
/// for your circuit
|
/// for your circuit
|
||||||
pub fn new(cfg: CircomConfig<E>) -> Self {
|
pub fn new(cfg: CircomConfig<F>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cfg,
|
cfg,
|
||||||
inputs: HashMap::new(),
|
inputs: HashMap::new(),
|
||||||
@ -58,13 +78,13 @@ impl<E: Pairing> CircomBuilder<E> {
|
|||||||
|
|
||||||
/// Pushes a Circom input at the specified name.
|
/// Pushes a Circom input at the specified name.
|
||||||
pub fn push_input<T: Into<BigInt>>(&mut self, name: impl ToString, val: T) {
|
pub fn push_input<T: Into<BigInt>>(&mut self, name: impl ToString, val: T) {
|
||||||
let values = self.inputs.entry(name.to_string()).or_insert_with(Vec::new);
|
let values = self.inputs.entry(name.to_string()).or_default();
|
||||||
values.push(val.into());
|
values.push(val.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an empty circom circuit with no witness set, to be used for
|
/// Generates an empty circom circuit with no witness set, to be used for
|
||||||
/// generation of the trusted setup parameters
|
/// generation of the trusted setup parameters
|
||||||
pub fn setup(&self) -> CircomCircuit<E> {
|
pub fn setup(&self) -> CircomCircuit<F> {
|
||||||
let mut circom = CircomCircuit {
|
let mut circom = CircomCircuit {
|
||||||
r1cs: self.cfg.r1cs.clone(),
|
r1cs: self.cfg.r1cs.clone(),
|
||||||
witness: None,
|
witness: None,
|
||||||
@ -78,20 +98,21 @@ impl<E: Pairing> CircomBuilder<E> {
|
|||||||
|
|
||||||
/// Creates the circuit populated with the witness corresponding to the previously
|
/// Creates the circuit populated with the witness corresponding to the previously
|
||||||
/// provided inputs
|
/// provided inputs
|
||||||
pub fn build(mut self) -> Result<CircomCircuit<E>> {
|
pub fn build(mut self) -> Result<CircomCircuit<F>> {
|
||||||
let mut circom = self.setup();
|
let mut circom = self.setup();
|
||||||
|
|
||||||
// calculate the witness
|
// calculate the witness
|
||||||
let witness = self
|
let witness = self.cfg.wtns.calculate_witness_element::<F, _>(
|
||||||
.cfg
|
&mut self.cfg.store,
|
||||||
.wtns
|
self.inputs,
|
||||||
.calculate_witness_element::<E, _>(self.inputs, self.cfg.sanity_check)?;
|
self.cfg.sanity_check,
|
||||||
|
)?;
|
||||||
circom.witness = Some(witness);
|
circom.witness = Some(witness);
|
||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
debug_assert!({
|
debug_assert!({
|
||||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||||
let cs = ConstraintSystem::<E::ScalarField>::new_ref();
|
let cs = ConstraintSystem::<F>::new_ref();
|
||||||
circom.clone().generate_constraints(cs.clone()).unwrap();
|
circom.clone().generate_constraints(cs.clone()).unwrap();
|
||||||
let is_satisfied = cs.is_satisfied().unwrap();
|
let is_satisfied = cs.is_satisfied().unwrap();
|
||||||
if !is_satisfied {
|
if !is_satisfied {
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
use ark_ec::pairing::Pairing;
|
|
||||||
use ark_relations::r1cs::{
|
use ark_relations::r1cs::{
|
||||||
ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable,
|
ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use ark_ff::PrimeField;
|
||||||
|
|
||||||
use super::R1CS;
|
use super::R1CS;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CircomCircuit<E: Pairing> {
|
pub struct CircomCircuit<F: PrimeField> {
|
||||||
pub r1cs: R1CS<E>,
|
pub r1cs: R1CS<F>,
|
||||||
pub witness: Option<Vec<E::ScalarField>>,
|
pub witness: Option<Vec<F>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> CircomCircuit<E> {
|
impl<F: PrimeField> CircomCircuit<F> {
|
||||||
pub fn get_public_inputs(&self) -> Option<Vec<E::ScalarField>> {
|
pub fn get_public_inputs(&self) -> Option<Vec<F>> {
|
||||||
match &self.witness {
|
match &self.witness {
|
||||||
None => None,
|
None => None,
|
||||||
Some(w) => match &self.r1cs.wire_mapping {
|
Some(w) => match &self.r1cs.wire_mapping {
|
||||||
@ -25,11 +26,8 @@ impl<E: Pairing> CircomCircuit<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
|
impl<F: PrimeField> ConstraintSynthesizer<F> for CircomCircuit<F> {
|
||||||
fn generate_constraints(
|
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||||
self,
|
|
||||||
cs: ConstraintSystemRef<E::ScalarField>,
|
|
||||||
) -> Result<(), SynthesisError> {
|
|
||||||
let witness = &self.witness;
|
let witness = &self.witness;
|
||||||
let wire_mapping = &self.r1cs.wire_mapping;
|
let wire_mapping = &self.r1cs.wire_mapping;
|
||||||
|
|
||||||
@ -37,7 +35,7 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
|
|||||||
for i in 1..self.r1cs.num_inputs {
|
for i in 1..self.r1cs.num_inputs {
|
||||||
cs.new_input_variable(|| {
|
cs.new_input_variable(|| {
|
||||||
Ok(match witness {
|
Ok(match witness {
|
||||||
None => E::ScalarField::from(1u32),
|
None => F::from(1u32),
|
||||||
Some(w) => match wire_mapping {
|
Some(w) => match wire_mapping {
|
||||||
Some(m) => w[m[i]],
|
Some(m) => w[m[i]],
|
||||||
None => w[i],
|
None => w[i],
|
||||||
@ -49,7 +47,7 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
|
|||||||
for i in 0..self.r1cs.num_aux {
|
for i in 0..self.r1cs.num_aux {
|
||||||
cs.new_witness_variable(|| {
|
cs.new_witness_variable(|| {
|
||||||
Ok(match witness {
|
Ok(match witness {
|
||||||
None => E::ScalarField::from(1u32),
|
None => F::from(1u32),
|
||||||
Some(w) => match wire_mapping {
|
Some(w) => match wire_mapping {
|
||||||
Some(m) => w[m[i + self.r1cs.num_inputs]],
|
Some(m) => w[m[i + self.r1cs.num_inputs]],
|
||||||
None => w[i + self.r1cs.num_inputs],
|
None => w[i + self.r1cs.num_inputs],
|
||||||
@ -65,12 +63,10 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
|
|||||||
Variable::Witness(index - self.r1cs.num_inputs)
|
Variable::Witness(index - self.r1cs.num_inputs)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let make_lc = |lc_data: &[(usize, E::ScalarField)]| {
|
let make_lc = |lc_data: &[(usize, F)]| {
|
||||||
lc_data.iter().fold(
|
lc_data.iter().fold(
|
||||||
LinearCombination::<E::ScalarField>::zero(),
|
LinearCombination::<F>::zero(),
|
||||||
|lc: LinearCombination<E::ScalarField>, (index, coeff)| {
|
|lc: LinearCombination<F>, (index, coeff)| lc + (*coeff, make_index(*index)),
|
||||||
lc + (*coeff, make_index(*index))
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -90,13 +86,13 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{CircomBuilder, CircomConfig};
|
use crate::{CircomBuilder, CircomConfig};
|
||||||
use ark_bn254::{Bn254, Fr};
|
use ark_bn254::Fr;
|
||||||
use ark_relations::r1cs::ConstraintSystem;
|
use ark_relations::r1cs::ConstraintSystem;
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn satisfied() {
|
async fn satisfied() {
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
"./test-vectors/mycircuit.wasm",
|
"./test-vectors/mycircuit_js/mycircuit.wasm",
|
||||||
"./test-vectors/mycircuit.r1cs",
|
"./test-vectors/mycircuit.r1cs",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
use ark_ec::pairing::Pairing;
|
|
||||||
|
|
||||||
pub mod r1cs_reader;
|
pub mod r1cs_reader;
|
||||||
pub use r1cs_reader::{R1CSFile, R1CS};
|
pub use r1cs_reader::{R1CSFile, R1CS};
|
||||||
|
|
||||||
@ -12,5 +10,5 @@ pub use builder::{CircomBuilder, CircomConfig};
|
|||||||
mod qap;
|
mod qap;
|
||||||
pub use qap::CircomReduction;
|
pub use qap::CircomReduction;
|
||||||
|
|
||||||
pub type Constraints<E> = (ConstraintVec<E>, ConstraintVec<E>, ConstraintVec<E>);
|
pub type Constraints<F> = (ConstraintVec<F>, ConstraintVec<F>, ConstraintVec<F>);
|
||||||
pub type ConstraintVec<E> = Vec<(usize, <E as Pairing>::ScalarField)>;
|
pub type ConstraintVec<F> = Vec<(usize, F)>;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
//! R1CS circom file reader
|
//! R1CS circom file reader
|
||||||
//! Copied from <https://github.com/poma/zkutil>
|
//! Copied from <https://github.com/poma/zkutil>
|
||||||
//! Spec: <https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md>
|
//! Spec: <https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md>
|
||||||
|
use ark_ff::PrimeField;
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
|
||||||
use ark_ec::pairing::Pairing;
|
use ark_serialize::{SerializationError, SerializationError::IoError};
|
||||||
use ark_serialize::{CanonicalDeserialize, SerializationError, SerializationError::IoError};
|
|
||||||
use ark_std::io::{Read, Seek, SeekFrom};
|
use ark_std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -15,16 +15,16 @@ type IoResult<T> = Result<T, SerializationError>;
|
|||||||
use super::{ConstraintVec, Constraints};
|
use super::{ConstraintVec, Constraints};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct R1CS<E: Pairing> {
|
pub struct R1CS<F> {
|
||||||
pub num_inputs: usize,
|
pub num_inputs: usize,
|
||||||
pub num_aux: usize,
|
pub num_aux: usize,
|
||||||
pub num_variables: usize,
|
pub num_variables: usize,
|
||||||
pub constraints: Vec<Constraints<E>>,
|
pub constraints: Vec<Constraints<F>>,
|
||||||
pub wire_mapping: Option<Vec<usize>>,
|
pub wire_mapping: Option<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> From<R1CSFile<E>> for R1CS<E> {
|
impl<F: PrimeField> From<R1CSFile<F>> for R1CS<F> {
|
||||||
fn from(file: R1CSFile<E>) -> Self {
|
fn from(file: R1CSFile<F>) -> Self {
|
||||||
let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize;
|
let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize;
|
||||||
let num_variables = file.header.n_wires as usize;
|
let num_variables = file.header.n_wires as usize;
|
||||||
let num_aux = num_variables - num_inputs;
|
let num_aux = num_variables - num_inputs;
|
||||||
@ -38,20 +38,20 @@ impl<E: Pairing> From<R1CSFile<E>> for R1CS<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct R1CSFile<E: Pairing> {
|
pub struct R1CSFile<F: PrimeField> {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub header: Header,
|
pub header: Header,
|
||||||
pub constraints: Vec<Constraints<E>>,
|
pub constraints: Vec<Constraints<F>>,
|
||||||
pub wire_mapping: Vec<u64>,
|
pub wire_mapping: Vec<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Pairing> R1CSFile<E> {
|
impl<F: PrimeField> R1CSFile<F> {
|
||||||
/// reader must implement the Seek trait, for example with a Cursor
|
/// reader must implement the Seek trait, for example with a Cursor
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// let reader = BufReader::new(Cursor::new(&data[..]));
|
/// let reader = BufReader::new(Cursor::new(&data[..]));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new<R: Read + Seek>(mut reader: R) -> IoResult<R1CSFile<E>> {
|
pub fn new<R: Read + Seek>(mut reader: R) -> IoResult<R1CSFile<F>> {
|
||||||
let mut magic = [0u8; 4];
|
let mut magic = [0u8; 4];
|
||||||
reader.read_exact(&mut magic)?;
|
reader.read_exact(&mut magic)?;
|
||||||
if magic != [0x72, 0x31, 0x63, 0x73] {
|
if magic != [0x72, 0x31, 0x63, 0x73] {
|
||||||
@ -117,7 +117,7 @@ impl<E: Pairing> R1CSFile<E> {
|
|||||||
|
|
||||||
reader.seek(SeekFrom::Start(*constraint_offset?))?;
|
reader.seek(SeekFrom::Start(*constraint_offset?))?;
|
||||||
|
|
||||||
let constraints = read_constraints::<&mut R, E>(&mut reader, &header)?;
|
let constraints = read_constraints::<&mut R, F>(&mut reader, &header)?;
|
||||||
|
|
||||||
let wire2label_offset = sec_offsets.get(&wire2label_type).ok_or_else(|| {
|
let wire2label_offset = sec_offsets.get(&wire2label_type).ok_or_else(|| {
|
||||||
Error::new(
|
Error::new(
|
||||||
@ -200,29 +200,29 @@ impl Header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_constraint_vec<R: Read, E: Pairing>(mut reader: R) -> IoResult<ConstraintVec<E>> {
|
fn read_constraint_vec<R: Read, F: PrimeField>(mut reader: R) -> IoResult<ConstraintVec<F>> {
|
||||||
let n_vec = reader.read_u32::<LittleEndian>()? as usize;
|
let n_vec = reader.read_u32::<LittleEndian>()? as usize;
|
||||||
let mut vec = Vec::with_capacity(n_vec);
|
let mut vec = Vec::with_capacity(n_vec);
|
||||||
for _ in 0..n_vec {
|
for _ in 0..n_vec {
|
||||||
vec.push((
|
vec.push((
|
||||||
reader.read_u32::<LittleEndian>()? as usize,
|
reader.read_u32::<LittleEndian>()? as usize,
|
||||||
E::ScalarField::deserialize_uncompressed(&mut reader)?,
|
F::deserialize_uncompressed(&mut reader)?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_constraints<R: Read, E: Pairing>(
|
fn read_constraints<R: Read, F: PrimeField>(
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
header: &Header,
|
header: &Header,
|
||||||
) -> IoResult<Vec<Constraints<E>>> {
|
) -> IoResult<Vec<Constraints<F>>> {
|
||||||
// todo check section size
|
// todo check section size
|
||||||
let mut vec = Vec::with_capacity(header.n_constraints as usize);
|
let mut vec = Vec::with_capacity(header.n_constraints as usize);
|
||||||
for _ in 0..header.n_constraints {
|
for _ in 0..header.n_constraints {
|
||||||
vec.push((
|
vec.push((
|
||||||
read_constraint_vec::<&mut R, E>(&mut reader)?,
|
read_constraint_vec::<&mut R, F>(&mut reader)?,
|
||||||
read_constraint_vec::<&mut R, E>(&mut reader)?,
|
read_constraint_vec::<&mut R, F>(&mut reader)?,
|
||||||
read_constraint_vec::<&mut R, E>(&mut reader)?,
|
read_constraint_vec::<&mut R, F>(&mut reader)?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
@ -251,7 +251,7 @@ fn read_map<R: Read>(mut reader: R, size: u64, header: &Header) -> IoResult<Vec<
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ark_bn254::{Bn254, Fr};
|
use ark_bn254::Fr;
|
||||||
use ark_std::io::{BufReader, Cursor};
|
use ark_std::io::{BufReader, Cursor};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -309,7 +309,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let reader = BufReader::new(Cursor::new(&data[..]));
|
let reader = BufReader::new(Cursor::new(&data[..]));
|
||||||
let file = R1CSFile::<Bn254>::new(reader).unwrap();
|
let file = R1CSFile::<Fr>::new(reader).unwrap();
|
||||||
assert_eq!(file.version, 1);
|
assert_eq!(file.version, 1);
|
||||||
|
|
||||||
assert_eq!(file.header.field_size, 32);
|
assert_eq!(file.header.field_size, 32);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Provides bindings to Circom's R1CS, for Groth16 Proof and Witness generation in Rust.
|
//! Provides bindings to Circom's R1CS, for Groth16 Proof and Witness generation in Rust.
|
||||||
mod witness;
|
mod witness;
|
||||||
pub use witness::WitnessCalculator;
|
pub use witness::{Wasm, WitnessCalculator};
|
||||||
|
|
||||||
pub mod circom;
|
pub mod circom;
|
||||||
pub use circom::{CircomBuilder, CircomCircuit, CircomConfig, CircomReduction};
|
pub use circom::{CircomBuilder, CircomCircuit, CircomConfig, CircomReduction};
|
||||||
|
|||||||
@ -1,165 +1,78 @@
|
|||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use wasmer::{Function, Instance, Value};
|
use wasmer::{Exports, Function, Memory, Store, Value};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wasm(Instance);
|
pub struct Wasm {
|
||||||
|
pub exports: Exports,
|
||||||
pub trait CircomBase {
|
pub memory: Memory,
|
||||||
fn init(&self, sanity_check: bool) -> Result<()>;
|
|
||||||
fn func(&self, name: &str) -> &Function;
|
|
||||||
fn get_ptr_witness_buffer(&self) -> Result<u32>;
|
|
||||||
fn get_ptr_witness(&self, w: u32) -> Result<u32>;
|
|
||||||
fn get_n_vars(&self) -> Result<u32>;
|
|
||||||
fn get_signal_offset32(
|
|
||||||
&self,
|
|
||||||
p_sig_offset: u32,
|
|
||||||
component: u32,
|
|
||||||
hash_msb: u32,
|
|
||||||
hash_lsb: u32,
|
|
||||||
) -> Result<()>;
|
|
||||||
fn set_signal(&self, c_idx: u32, component: u32, signal: u32, p_val: u32) -> Result<()>;
|
|
||||||
fn get_u32(&self, name: &str) -> Result<u32>;
|
|
||||||
// Only exists natively in Circom2, hardcoded for Circom
|
|
||||||
fn get_version(&self) -> Result<u32>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Circom {
|
impl Wasm {
|
||||||
fn get_fr_len(&self) -> Result<u32>;
|
pub(crate) fn get_field_num_len32(&self, store: &mut Store) -> Result<u32> {
|
||||||
fn get_ptr_raw_prime(&self) -> Result<u32>;
|
self.get_u32(store, "getFieldNumLen32")
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Circom2 {
|
|
||||||
fn get_field_num_len32(&self) -> Result<u32>;
|
|
||||||
fn get_raw_prime(&self) -> Result<()>;
|
|
||||||
fn read_shared_rw_memory(&self, i: u32) -> Result<u32>;
|
|
||||||
fn write_shared_rw_memory(&self, i: u32, v: u32) -> Result<()>;
|
|
||||||
fn set_input_signal(&self, hmsb: u32, hlsb: u32, pos: u32) -> Result<()>;
|
|
||||||
fn get_witness(&self, i: u32) -> Result<()>;
|
|
||||||
fn get_witness_size(&self) -> Result<u32>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Circom for Wasm {
|
|
||||||
fn get_fr_len(&self) -> Result<u32> {
|
|
||||||
self.get_u32("getFrLen")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ptr_raw_prime(&self) -> Result<u32> {
|
pub(crate) fn get_raw_prime(&self, store: &mut Store) -> Result<()> {
|
||||||
self.get_u32("getPRawPrime")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "circom-2")]
|
|
||||||
impl Circom2 for Wasm {
|
|
||||||
fn get_field_num_len32(&self) -> Result<u32> {
|
|
||||||
self.get_u32("getFieldNumLen32")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_raw_prime(&self) -> Result<()> {
|
|
||||||
let func = self.func("getRawPrime");
|
let func = self.func("getRawPrime");
|
||||||
func.call(&[])?;
|
func.call(store, &[])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_shared_rw_memory(&self, i: u32) -> Result<u32> {
|
pub(crate) fn read_shared_rw_memory(&self, store: &mut Store, i: u32) -> Result<u32> {
|
||||||
let func = self.func("readSharedRWMemory");
|
let func = self.func("readSharedRWMemory");
|
||||||
let result = func.call(&[i.into()])?;
|
let result = func.call(store, &[i.into()])?;
|
||||||
Ok(result[0].unwrap_i32() as u32)
|
Ok(result[0].unwrap_i32() as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_shared_rw_memory(&self, 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");
|
let func = self.func("writeSharedRWMemory");
|
||||||
func.call(&[i.into(), v.into()])?;
|
func.call(store, &[i.into(), v.into()])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_input_signal(&self, hmsb: u32, hlsb: u32, pos: u32) -> Result<()> {
|
pub(crate) fn set_input_signal(
|
||||||
let func = self.func("setInputSignal");
|
|
||||||
func.call(&[hmsb.into(), hlsb.into(), pos.into()])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_witness(&self, i: u32) -> Result<()> {
|
|
||||||
let func = self.func("getWitness");
|
|
||||||
func.call(&[i.into()])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_witness_size(&self) -> Result<u32> {
|
|
||||||
self.get_u32("getWitnessSize")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CircomBase for Wasm {
|
|
||||||
fn init(&self, sanity_check: bool) -> Result<()> {
|
|
||||||
let func = self.func("init");
|
|
||||||
func.call(&[Value::I32(sanity_check as i32)])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_ptr_witness_buffer(&self) -> Result<u32> {
|
|
||||||
self.get_u32("getWitnessBuffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_ptr_witness(&self, w: u32) -> Result<u32> {
|
|
||||||
let func = self.func("getPWitness");
|
|
||||||
let res = func.call(&[w.into()])?;
|
|
||||||
|
|
||||||
Ok(res[0].unwrap_i32() as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_n_vars(&self) -> Result<u32> {
|
|
||||||
self.get_u32("getNVars")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_signal_offset32(
|
|
||||||
&self,
|
&self,
|
||||||
p_sig_offset: u32,
|
store: &mut Store,
|
||||||
component: u32,
|
hmsb: u32,
|
||||||
hash_msb: u32,
|
hlsb: u32,
|
||||||
hash_lsb: u32,
|
pos: u32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let func = self.func("getSignalOffset32");
|
let func = self.func("setInputSignal");
|
||||||
func.call(&[
|
func.call(store, &[hmsb.into(), hlsb.into(), pos.into()])?;
|
||||||
p_sig_offset.into(),
|
|
||||||
component.into(),
|
|
||||||
hash_msb.into(),
|
|
||||||
hash_lsb.into(),
|
|
||||||
])?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_signal(&self, c_idx: u32, component: u32, signal: u32, p_val: u32) -> Result<()> {
|
pub(crate) fn get_witness(&self, store: &mut Store, i: u32) -> Result<()> {
|
||||||
let func = self.func("setSignal");
|
let func = self.func("getWitness");
|
||||||
func.call(&[c_idx.into(), component.into(), signal.into(), p_val.into()])?;
|
func.call(store, &[i.into()])?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to version 1 if it isn't explicitly defined
|
pub(crate) fn get_witness_size(&self, store: &mut Store) -> Result<u32> {
|
||||||
fn get_version(&self) -> Result<u32> {
|
self.get_u32(store, "getWitnessSize")
|
||||||
match self.0.exports.get_function("getVersion") {
|
|
||||||
Ok(func) => Ok(func.call(&[])?[0].unwrap_i32() as u32),
|
|
||||||
Err(_) => Ok(1),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_u32(&self, name: &str) -> Result<u32> {
|
pub(crate) fn init(&self, store: &mut Store, sanity_check: bool) -> Result<()> {
|
||||||
let func = self.func(name);
|
let func = self.func("init");
|
||||||
let result = func.call(&[])?;
|
func.call(store, &[Value::I32(sanity_check as i32)])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_u32(&self, store: &mut Store, name: &str) -> Result<u32> {
|
||||||
|
let func = &self.func(name);
|
||||||
|
let result = func.call(store, &[])?;
|
||||||
Ok(result[0].unwrap_i32() as u32)
|
Ok(result[0].unwrap_i32() as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn func(&self, name: &str) -> &Function {
|
fn func(&self, name: &str) -> &Function {
|
||||||
self.0
|
self.exports
|
||||||
.exports
|
|
||||||
.get_function(name)
|
.get_function(name)
|
||||||
.unwrap_or_else(|_| panic!("function {} not found", name))
|
.unwrap_or_else(|_| panic!("function {} not found", name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wasm {
|
impl Wasm {
|
||||||
pub fn new(instance: Instance) -> Self {
|
pub fn new(exports: Exports, memory: Memory) -> Self {
|
||||||
Self(instance)
|
Self { exports, memory }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
//! Safe-ish interface for reading and writing specific types to the WASM runtime's memory
|
//! Safe-ish interface for reading and writing specific types to the WASM runtime's memory
|
||||||
use ark_serialize::CanonicalDeserialize;
|
use ark_serialize::CanonicalDeserialize;
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
use wasmer::{Memory, MemoryView};
|
use wasmer::{Memory, MemoryAccessError, MemoryView, Store};
|
||||||
|
|
||||||
// TODO: Decide whether we want Ark here or if it should use a generic BigInt package
|
// TODO: Decide whether we want Ark here or if it should use a generic BigInt package
|
||||||
use ark_bn254::FrConfig;
|
use ark_bn254::FrConfig;
|
||||||
@ -11,10 +11,11 @@ use ark_ff::{BigInteger, BigInteger256, Zero};
|
|||||||
use num_bigint::{BigInt, BigUint};
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use std::io::Cursor;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{convert::TryFrom, ops::Deref};
|
use std::{convert::TryFrom, ops::Deref};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SafeMemory {
|
pub struct SafeMemory {
|
||||||
pub memory: Memory,
|
pub memory: Memory,
|
||||||
pub prime: BigInt,
|
pub prime: BigInt,
|
||||||
@ -38,10 +39,9 @@ impl SafeMemory {
|
|||||||
pub fn new(memory: Memory, n32: usize, prime: BigInt) -> Self {
|
pub fn new(memory: Memory, n32: usize, prime: BigInt) -> Self {
|
||||||
// TODO: Figure out a better way to calculate these
|
// TODO: Figure out a better way to calculate these
|
||||||
let short_max = BigInt::from(0x8000_0000u64);
|
let short_max = BigInt::from(0x8000_0000u64);
|
||||||
let short_min = BigInt::from_biguint(
|
let short_min =
|
||||||
num_bigint::Sign::NoSign,
|
BigInt::from_biguint(num_bigint::Sign::NoSign, BigUint::from(FrConfig::MODULUS))
|
||||||
BigUint::try_from(FrConfig::MODULUS).unwrap(),
|
- &short_max;
|
||||||
) - &short_max;
|
|
||||||
let r_inv = BigInt::from_str(
|
let r_inv = BigInt::from_str(
|
||||||
"9915499612839321149637521777990102151350674507940716049588462388200839649614",
|
"9915499612839321149637521777990102151350674507940716049588462388200839649614",
|
||||||
)
|
)
|
||||||
@ -59,96 +59,103 @@ impl SafeMemory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets an immutable view to the memory in 32 byte chunks
|
/// Gets an immutable view to the memory in 32 byte chunks
|
||||||
pub fn view(&self) -> MemoryView<u32> {
|
pub fn view<'a>(&self, store: &'a mut Store) -> MemoryView<'a> {
|
||||||
self.memory.view()
|
self.memory.view(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the next free position in the memory
|
/// Returns the next free position in the memory
|
||||||
pub fn free_pos(&self) -> u32 {
|
pub fn free_pos(&self, store: &mut Store) -> Result<u32, MemoryAccessError> {
|
||||||
self.view()[0].get()
|
self.read_u32(store, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the next free position in the memory
|
/// Sets the next free position in the memory
|
||||||
pub fn set_free_pos(&mut self, ptr: u32) {
|
pub fn set_free_pos(&self, store: &mut Store, ptr: u32) -> Result<(), MemoryAccessError> {
|
||||||
self.write_u32(0, ptr);
|
self.write_u32(store, 0, ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocates a U32 in memory
|
/// Allocates a U32 in memory
|
||||||
pub fn alloc_u32(&mut self) -> u32 {
|
pub fn alloc_u32(&self, store: &mut Store) -> Result<u32, MemoryAccessError> {
|
||||||
let p = self.free_pos();
|
let p = self.free_pos(store)?;
|
||||||
self.set_free_pos(p + 8);
|
self.set_free_pos(store, p + 8)?;
|
||||||
p
|
Ok(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a u32 to the specified memory offset
|
/// Writes a u32 to the specified memory offset
|
||||||
pub fn write_u32(&mut self, ptr: usize, num: u32) {
|
pub fn write_u32(
|
||||||
let buf = unsafe { self.memory.data_unchecked_mut() };
|
&self,
|
||||||
buf[ptr..ptr + std::mem::size_of::<u32>()].copy_from_slice(&num.to_le_bytes());
|
store: &mut Store,
|
||||||
|
ptr: usize,
|
||||||
|
num: u32,
|
||||||
|
) -> Result<(), MemoryAccessError> {
|
||||||
|
let bytes = num.to_le_bytes();
|
||||||
|
self.view(store).write(ptr as u64, &bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a u32 from the specified memory offset
|
/// Reads a u32 from the specified memory offset
|
||||||
pub fn read_u32(&self, ptr: usize) -> u32 {
|
pub fn read_u32(&self, store: &mut Store, ptr: usize) -> Result<u32, MemoryAccessError> {
|
||||||
let buf = unsafe { self.memory.data_unchecked() };
|
|
||||||
|
|
||||||
let mut bytes = [0; 4];
|
let mut bytes = [0; 4];
|
||||||
bytes.copy_from_slice(&buf[ptr..ptr + std::mem::size_of::<u32>()]);
|
self.view(store).read(ptr as u64, &mut bytes)?;
|
||||||
|
Ok(u32::from_le_bytes(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
u32::from_le_bytes(bytes)
|
pub fn read_byte(&self, store: &mut Store, ptr: usize) -> Result<u8, MemoryAccessError> {
|
||||||
|
let mut bytes = [0; 1];
|
||||||
|
self.view(store).read(ptr as u64, &mut bytes)?;
|
||||||
|
Ok(u8::from_le_bytes(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocates `self.n32 * 4 + 8` bytes in the memory
|
/// Allocates `self.n32 * 4 + 8` bytes in the memory
|
||||||
pub fn alloc_fr(&mut self) -> u32 {
|
pub fn alloc_fr(&self, store: &mut Store) -> Result<u32, MemoryAccessError> {
|
||||||
let p = self.free_pos();
|
let p = self.free_pos(store)?;
|
||||||
self.set_free_pos(p + self.n32 as u32 * 4 + 8);
|
self.set_free_pos(store, p + self.n32 as u32 * 4 + 8)?;
|
||||||
p
|
Ok(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a Field Element to memory at the specified offset, truncating
|
/// Writes a Field Element to memory at the specified offset, truncating
|
||||||
/// to smaller u32 types if needed and adjusting the sign via 2s complement
|
/// to smaller u32 types if needed and adjusting the sign via 2s complement
|
||||||
pub fn write_fr(&mut self, ptr: usize, fr: &BigInt) -> Result<()> {
|
pub fn write_fr(&self, store: &mut Store, ptr: usize, fr: &BigInt) -> Result<()> {
|
||||||
if fr < &self.short_max && fr > &self.short_min {
|
if fr < &self.short_max && fr > &self.short_min {
|
||||||
if fr >= &BigInt::zero() {
|
if fr >= &BigInt::zero() {
|
||||||
self.write_short_positive(ptr, fr)?;
|
self.write_short_positive(store, ptr, fr)?;
|
||||||
} else {
|
} else {
|
||||||
self.write_short_negative(ptr, fr)?;
|
self.write_short_negative(store, ptr, fr)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.write_long_normal(ptr, fr)?;
|
self.write_long_normal(store, ptr, fr)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a Field Element from the memory at the specified offset
|
/// Reads a Field Element from the memory at the specified offset
|
||||||
pub fn read_fr(&self, ptr: usize) -> Result<BigInt> {
|
pub fn read_fr(&self, store: &mut Store, ptr: usize) -> Result<BigInt, MemoryAccessError> {
|
||||||
let view = self.memory.view::<u8>();
|
let test_byte = self.read_byte(store, ptr + 4 + 3)?;
|
||||||
|
let test_byte2 = self.read_byte(store, ptr + 3)?;
|
||||||
|
|
||||||
let res = if view[ptr + 4 + 3].get() & 0x80 != 0 {
|
if test_byte & 0x80 != 0 {
|
||||||
let mut num = self.read_big(ptr + 8, self.n32)?;
|
let mut num = self.read_big(store, ptr + 8, self.n32)?;
|
||||||
if view[ptr + 4 + 3].get() & 0x40 != 0 {
|
if test_byte & 0x40 != 0 {
|
||||||
num = (num * &self.r_inv) % &self.prime
|
num = (num * &self.r_inv) % &self.prime
|
||||||
}
|
}
|
||||||
num
|
Ok(num)
|
||||||
} else if view[ptr + 3].get() & 0x40 != 0 {
|
} else if test_byte2 & 0x40 != 0 {
|
||||||
let mut num = self.read_u32(ptr).into();
|
let mut num = self.read_u32(store, ptr).map(|x| x.into())?;
|
||||||
// handle small negative
|
// handle small negative
|
||||||
num -= BigInt::from(0x100000000i64);
|
num -= BigInt::from(0x100000000i64);
|
||||||
num
|
Ok(num)
|
||||||
} else {
|
} else {
|
||||||
self.read_u32(ptr).into()
|
self.read_u32(store, ptr).map(|x| x.into())
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_short_positive(&mut self, ptr: usize, fr: &BigInt) -> Result<()> {
|
fn write_short_positive(&self, store: &mut Store, ptr: usize, fr: &BigInt) -> Result<()> {
|
||||||
let num = fr.to_i32().expect("not a short positive");
|
let num = fr.to_i32().expect("not a short positive");
|
||||||
self.write_u32(ptr, num as u32);
|
self.write_u32(store, ptr, num as u32)?;
|
||||||
self.write_u32(ptr + 4, 0);
|
self.write_u32(store, ptr + 4, 0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_short_negative(&mut self, ptr: usize, fr: &BigInt) -> Result<()> {
|
fn write_short_negative(&self, store: &mut Store, ptr: usize, fr: &BigInt) -> Result<()> {
|
||||||
// 2s complement
|
// 2s complement
|
||||||
let num = fr - &self.short_min;
|
let num = fr - &self.short_min;
|
||||||
let num = num - &self.short_max;
|
let num = num - &self.short_max;
|
||||||
@ -158,40 +165,43 @@ impl SafeMemory {
|
|||||||
.to_u32()
|
.to_u32()
|
||||||
.expect("could not cast as u32 (should never happen)");
|
.expect("could not cast as u32 (should never happen)");
|
||||||
|
|
||||||
self.write_u32(ptr, num);
|
self.write_u32(store, ptr, num)?;
|
||||||
self.write_u32(ptr + 4, 0);
|
self.write_u32(store, ptr + 4, 0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_long_normal(&mut self, ptr: usize, fr: &BigInt) -> Result<()> {
|
fn write_long_normal(&self, store: &mut Store, ptr: usize, fr: &BigInt) -> Result<()> {
|
||||||
self.write_u32(ptr, 0);
|
self.write_u32(store, ptr, 0)?;
|
||||||
self.write_u32(ptr + 4, i32::MIN as u32); // 0x80000000
|
self.write_u32(store, ptr + 4, i32::MIN as u32)?; // 0x80000000
|
||||||
self.write_big(ptr + 8, fr)?;
|
self.write_big(store, ptr + 8, fr)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_big(&self, ptr: usize, num: &BigInt) -> Result<()> {
|
fn write_big(
|
||||||
let buf = unsafe { self.memory.data_unchecked_mut() };
|
&self,
|
||||||
|
store: &mut Store,
|
||||||
// TODO: How do we handle negative bignums?
|
ptr: usize,
|
||||||
|
num: &BigInt,
|
||||||
|
) -> Result<(), MemoryAccessError> {
|
||||||
let (_, num) = num.clone().into_parts();
|
let (_, num) = num.clone().into_parts();
|
||||||
let num = BigInteger256::try_from(num).unwrap();
|
let num = BigInteger256::try_from(num).unwrap();
|
||||||
|
|
||||||
let bytes = num.to_bytes_le();
|
let bytes = num.to_bytes_le();
|
||||||
let len = bytes.len();
|
self.view(store).write(ptr as u64, &bytes)
|
||||||
buf[ptr..ptr + len].copy_from_slice(&bytes);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads `num_bytes * 32` from the specified memory offset in a Big Integer
|
/// Reads `num_bytes * 32` from the specified memory offset in a Big Integer
|
||||||
pub fn read_big(&self, ptr: usize, num_bytes: usize) -> Result<BigInt> {
|
pub fn read_big(
|
||||||
let buf = unsafe { self.memory.data_unchecked() };
|
&self,
|
||||||
let buf = &buf[ptr..ptr + num_bytes * 32];
|
store: &mut Store,
|
||||||
|
ptr: usize,
|
||||||
|
num_bytes: usize,
|
||||||
|
) -> Result<BigInt, MemoryAccessError> {
|
||||||
|
let mut buf = vec![0; num_bytes * 32];
|
||||||
|
self.view(store).read(ptr as u64, &mut buf)?;
|
||||||
// TODO: Is there a better way to read big integers?
|
// TODO: Is there a better way to read big integers?
|
||||||
let big = BigInteger256::deserialize_uncompressed(buf).unwrap();
|
let big = BigInteger256::deserialize_uncompressed(&mut Cursor::new(buf)).unwrap();
|
||||||
let big = BigUint::try_from(big).unwrap();
|
let big = BigUint::from(big);
|
||||||
Ok(big.into())
|
Ok(big.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,20 +220,22 @@ mod tests {
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use wasmer::{MemoryType, Store};
|
use wasmer::{MemoryType, Store};
|
||||||
|
|
||||||
fn new() -> SafeMemory {
|
fn new() -> (SafeMemory, Store) {
|
||||||
SafeMemory::new(
|
let mut store = Store::default();
|
||||||
Memory::new(&Store::default(), MemoryType::new(1, None, false)).unwrap(),
|
let mem = SafeMemory::new(
|
||||||
|
Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap(),
|
||||||
2,
|
2,
|
||||||
BigInt::from_str(
|
BigInt::from_str(
|
||||||
"21888242871839275222246405745257275088548364400416034343698204186575808495617",
|
"21888242871839275222246405745257275088548364400416034343698204186575808495617",
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
);
|
||||||
|
(mem, store)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn i32_bounds() {
|
fn i32_bounds() {
|
||||||
let mem = new();
|
let (mem, _) = new();
|
||||||
let i32_max = i32::MAX as i64 + 1;
|
let i32_max = i32::MAX as i64 + 1;
|
||||||
assert_eq!(mem.short_min.to_i64().unwrap(), -i32_max);
|
assert_eq!(mem.short_min.to_i64().unwrap(), -i32_max);
|
||||||
assert_eq!(mem.short_max.to_i64().unwrap(), i32_max);
|
assert_eq!(mem.short_max.to_i64().unwrap(), i32_max);
|
||||||
@ -231,14 +243,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_write_32() {
|
fn read_write_32() {
|
||||||
let mut mem = new();
|
let (mem, mut store) = new();
|
||||||
let num = u32::MAX;
|
let num = u32::MAX;
|
||||||
|
|
||||||
let inp = mem.read_u32(0);
|
let inp = mem.read_u32(&mut store, 0).unwrap();
|
||||||
assert_eq!(inp, 0);
|
assert_eq!(inp, 0);
|
||||||
|
|
||||||
mem.write_u32(0, num);
|
mem.write_u32(&mut store, 0, num).unwrap();
|
||||||
let inp = mem.read_u32(0);
|
let inp = mem.read_u32(&mut store, 0).unwrap();
|
||||||
assert_eq!(inp, num);
|
assert_eq!(inp, num);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,9 +277,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_write_fr(num: BigInt) {
|
fn read_write_fr(num: BigInt) {
|
||||||
let mut mem = new();
|
let (mem, mut store) = new();
|
||||||
mem.write_fr(0, &num).unwrap();
|
mem.write_fr(&mut store, 0, &num).unwrap();
|
||||||
let res = mem.read_fr(0).unwrap();
|
let res = mem.read_fr(&mut store, 0).unwrap();
|
||||||
assert_eq!(res, num);
|
assert_eq!(res, num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,7 @@ mod memory;
|
|||||||
pub(super) use memory::SafeMemory;
|
pub(super) use memory::SafeMemory;
|
||||||
|
|
||||||
mod circom;
|
mod circom;
|
||||||
pub(super) use circom::{CircomBase, Wasm};
|
pub use circom::Wasm;
|
||||||
|
|
||||||
#[cfg(feature = "circom-2")]
|
|
||||||
pub(super) use circom::Circom2;
|
|
||||||
|
|
||||||
pub(super) use circom::Circom;
|
|
||||||
|
|
||||||
use fnv::FnvHasher;
|
use fnv::FnvHasher;
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
|
|||||||
@ -1,24 +1,19 @@
|
|||||||
use super::{fnv, CircomBase, SafeMemory, Wasm};
|
use super::{fnv, SafeMemory, Wasm};
|
||||||
|
use ark_ff::PrimeField;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use std::cell::Cell;
|
|
||||||
use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, RuntimeError, Store};
|
use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, RuntimeError, Store};
|
||||||
|
use wasmer_wasix::WasiEnv;
|
||||||
|
|
||||||
#[cfg(feature = "circom-2")]
|
|
||||||
use num::ToPrimitive;
|
use num::ToPrimitive;
|
||||||
|
|
||||||
#[cfg(feature = "circom-2")]
|
#[derive(Debug)]
|
||||||
use super::Circom2;
|
|
||||||
|
|
||||||
use super::Circom;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct WitnessCalculator {
|
pub struct WitnessCalculator {
|
||||||
pub instance: Wasm,
|
pub instance: Wasm,
|
||||||
pub memory: SafeMemory,
|
pub memory: Option<SafeMemory>,
|
||||||
pub n64: u32,
|
pub n64: u32,
|
||||||
pub circom_version: u32,
|
pub prime: BigInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error type to signal end of execution.
|
// Error type to signal end of execution.
|
||||||
@ -27,7 +22,6 @@ pub struct WitnessCalculator {
|
|||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
struct ExitCode(u32);
|
struct ExitCode(u32);
|
||||||
|
|
||||||
#[cfg(feature = "circom-2")]
|
|
||||||
fn from_array32(arr: Vec<u32>) -> BigInt {
|
fn from_array32(arr: Vec<u32>) -> BigInt {
|
||||||
let mut res = BigInt::zero();
|
let mut res = BigInt::zero();
|
||||||
let radix = BigInt::from(0x100000000u64);
|
let radix = BigInt::from(0x100000000u64);
|
||||||
@ -37,7 +31,6 @@ fn from_array32(arr: Vec<u32>) -> BigInt {
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "circom-2")]
|
|
||||||
fn to_array32(s: &BigInt, size: usize) -> Vec<u32> {
|
fn to_array32(s: &BigInt, size: usize) -> Vec<u32> {
|
||||||
let mut res = vec![0; size];
|
let mut res = vec![0; size];
|
||||||
let mut rem = s.clone();
|
let mut rem = s.clone();
|
||||||
@ -53,26 +46,26 @@ fn to_array32(s: &BigInt, size: usize) -> Vec<u32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WitnessCalculator {
|
impl WitnessCalculator {
|
||||||
pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self> {
|
pub fn new(store: &mut Store, path: impl AsRef<std::path::Path>) -> Result<Self> {
|
||||||
Self::from_file(path)
|
Self::from_file(store, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
pub fn from_bytes(store: &mut Store, bytes: &[u8]) -> Result<Self> {
|
||||||
let store = Store::default();
|
|
||||||
let module = Module::new(&store, bytes)?;
|
let module = Module::new(&store, bytes)?;
|
||||||
Self::from_module(module)
|
Self::from_module(store, module)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
|
pub fn from_file(store: &mut Store, path: impl AsRef<std::path::Path>) -> Result<Self> {
|
||||||
let store = Store::default();
|
|
||||||
let module = Module::from_file(&store, path)?;
|
let module = Module::from_file(&store, path)?;
|
||||||
Self::from_module(module)
|
Self::from_module(store, module)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_module(module: Module) -> Result<Self> {
|
pub fn from_module(store: &mut Store, module: Module) -> Result<Self> {
|
||||||
let store = module.store();
|
let wasm = Self::make_wasm_runtime(store, module)?;
|
||||||
|
Self::new_from_wasm(store, wasm)
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the memory
|
pub fn make_wasm_runtime(store: &mut Store, module: Module) -> Result<Wasm> {
|
||||||
let memory = Memory::new(store, MemoryType::new(2000, None, false)).unwrap();
|
let memory = Memory::new(store, MemoryType::new(2000, None, false)).unwrap();
|
||||||
let import_object = imports! {
|
let import_object = imports! {
|
||||||
"env" => {
|
"env" => {
|
||||||
@ -92,144 +85,43 @@ impl WitnessCalculator {
|
|||||||
"writeBufferMessage" => runtime::write_buffer_message(store),
|
"writeBufferMessage" => runtime::write_buffer_message(store),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let instance = Wasm::new(Instance::new(&module, &import_object)?);
|
let instance = Instance::new(store, &module, &import_object)?;
|
||||||
|
let exports = instance.exports.clone();
|
||||||
|
let mut wasi_env = WasiEnv::builder("calculateWitness").finalize(store)?;
|
||||||
|
wasi_env.initialize_with_memory(store, instance, Some(memory.clone()), false)?;
|
||||||
|
let wasm = Wasm::new(exports, memory);
|
||||||
|
Ok(wasm)
|
||||||
|
}
|
||||||
|
|
||||||
let version = instance.get_version().unwrap_or(1);
|
pub fn new_from_wasm(store: &mut Store, instance: Wasm) -> Result<Self> {
|
||||||
|
let n32 = instance.get_field_num_len32(store)?;
|
||||||
// Circom 2 feature flag with version 2
|
instance.get_raw_prime(store)?;
|
||||||
#[cfg(feature = "circom-2")]
|
let mut arr = vec![0; n32 as usize];
|
||||||
fn new_circom2(instance: Wasm, memory: Memory, version: u32) -> Result<WitnessCalculator> {
|
for i in 0..n32 {
|
||||||
let n32 = instance.get_field_num_len32()?;
|
let res = instance.read_shared_rw_memory(store, i)?;
|
||||||
let mut safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
|
arr[(n32 as usize) - (i as usize) - 1] = res;
|
||||||
instance.get_raw_prime()?;
|
|
||||||
let mut arr = vec![0; n32 as usize];
|
|
||||||
for i in 0..n32 {
|
|
||||||
let res = instance.read_shared_rw_memory(i)?;
|
|
||||||
arr[(n32 as usize) - (i as usize) - 1] = res;
|
|
||||||
}
|
|
||||||
let prime = from_array32(arr);
|
|
||||||
|
|
||||||
let n64 = ((prime.bits() - 1) / 64 + 1) as u32;
|
|
||||||
safe_memory.prime = prime;
|
|
||||||
|
|
||||||
Ok(WitnessCalculator {
|
|
||||||
instance,
|
|
||||||
memory: safe_memory,
|
|
||||||
n64,
|
|
||||||
circom_version: version,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
let prime = from_array32(arr);
|
||||||
|
|
||||||
fn new_circom1(instance: Wasm, memory: Memory, version: u32) -> Result<WitnessCalculator> {
|
let n64 = ((prime.bits() - 1) / 64 + 1) as u32;
|
||||||
// Fallback to Circom 1 behavior
|
|
||||||
let n32 = (instance.get_fr_len()? >> 2) - 2;
|
|
||||||
let mut safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
|
|
||||||
let ptr = instance.get_ptr_raw_prime()?;
|
|
||||||
let prime = safe_memory.read_big(ptr as usize, n32 as usize)?;
|
|
||||||
|
|
||||||
let n64 = ((prime.bits() - 1) / 64 + 1) as u32;
|
Ok(WitnessCalculator {
|
||||||
safe_memory.prime = prime;
|
instance,
|
||||||
|
memory: None,
|
||||||
Ok(WitnessCalculator {
|
n64,
|
||||||
instance,
|
prime,
|
||||||
memory: safe_memory,
|
})
|
||||||
n64,
|
|
||||||
circom_version: version,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(instance, memory, version),
|
|
||||||
1 => new_circom1(instance, memory, version),
|
|
||||||
_ => panic!("Unknown Circom version")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_circom1(instance, memory, version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
|
pub fn calculate_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
store: &mut Store,
|
||||||
inputs: I,
|
inputs: I,
|
||||||
sanity_check: bool,
|
sanity_check: bool,
|
||||||
) -> Result<Vec<BigInt>> {
|
) -> Result<Vec<BigInt>> {
|
||||||
self.instance.init(sanity_check)?;
|
self.instance.init(store, sanity_check)?;
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
let n32 = self.instance.get_field_num_len32(store)?;
|
||||||
if #[cfg(feature = "circom-2")] {
|
|
||||||
match self.circom_version {
|
|
||||||
2 => self.calculate_witness_circom2(inputs, sanity_check),
|
|
||||||
1 => self.calculate_witness_circom1(inputs, sanity_check),
|
|
||||||
_ => panic!("Unknown Circom version")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.calculate_witness_circom1(inputs, sanity_check)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Circom 1 default behavior
|
|
||||||
fn calculate_witness_circom1<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
|
|
||||||
&mut self,
|
|
||||||
inputs: I,
|
|
||||||
sanity_check: bool,
|
|
||||||
) -> Result<Vec<BigInt>> {
|
|
||||||
self.instance.init(sanity_check)?;
|
|
||||||
|
|
||||||
let old_mem_free_pos = self.memory.free_pos();
|
|
||||||
let p_sig_offset = self.memory.alloc_u32();
|
|
||||||
let p_fr = self.memory.alloc_fr();
|
|
||||||
|
|
||||||
// allocate the inputs
|
|
||||||
for (name, values) in inputs.into_iter() {
|
|
||||||
let (msb, lsb) = fnv(&name);
|
|
||||||
|
|
||||||
self.instance
|
|
||||||
.get_signal_offset32(p_sig_offset, 0, msb, lsb)?;
|
|
||||||
|
|
||||||
let sig_offset = self.memory.read_u32(p_sig_offset as usize) as usize;
|
|
||||||
|
|
||||||
for (i, value) in values.into_iter().enumerate() {
|
|
||||||
self.memory.write_fr(p_fr as usize, &value)?;
|
|
||||||
self.instance
|
|
||||||
.set_signal(0, 0, (sig_offset + i) as u32, p_fr)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut w = Vec::new();
|
|
||||||
|
|
||||||
let n_vars = self.instance.get_n_vars()?;
|
|
||||||
for i in 0..n_vars {
|
|
||||||
let ptr = self.instance.get_ptr_witness(i)? as usize;
|
|
||||||
let el = self.memory.read_fr(ptr)?;
|
|
||||||
w.push(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.memory.set_free_pos(old_mem_free_pos);
|
|
||||||
|
|
||||||
Ok(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Circom 2 feature flag with version 2
|
|
||||||
#[cfg(feature = "circom-2")]
|
|
||||||
fn calculate_witness_circom2<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
|
|
||||||
&mut self,
|
|
||||||
inputs: I,
|
|
||||||
sanity_check: bool,
|
|
||||||
) -> Result<Vec<BigInt>> {
|
|
||||||
self.instance.init(sanity_check)?;
|
|
||||||
|
|
||||||
let n32 = self.instance.get_field_num_len32()?;
|
|
||||||
|
|
||||||
// allocate the inputs
|
// allocate the inputs
|
||||||
for (name, values) in inputs.into_iter() {
|
for (name, values) in inputs.into_iter() {
|
||||||
@ -238,21 +130,25 @@ impl WitnessCalculator {
|
|||||||
for (i, value) in values.into_iter().enumerate() {
|
for (i, value) in values.into_iter().enumerate() {
|
||||||
let f_arr = to_array32(&value, n32 as usize);
|
let f_arr = to_array32(&value, n32 as usize);
|
||||||
for j in 0..n32 {
|
for j in 0..n32 {
|
||||||
self.instance
|
self.instance.write_shared_rw_memory(
|
||||||
.write_shared_rw_memory(j, f_arr[(n32 as usize) - 1 - (j as usize)])?;
|
store,
|
||||||
|
j,
|
||||||
|
f_arr[(n32 as usize) - 1 - (j as usize)],
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
self.instance.set_input_signal(msb, lsb, i as u32)?;
|
self.instance.set_input_signal(store, msb, lsb, i as u32)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut w = Vec::new();
|
let mut w = Vec::new();
|
||||||
|
|
||||||
let witness_size = self.instance.get_witness_size()?;
|
let witness_size = self.instance.get_witness_size(store)?;
|
||||||
for i in 0..witness_size {
|
for i in 0..witness_size {
|
||||||
self.instance.get_witness(i)?;
|
self.instance.get_witness(store, i)?;
|
||||||
let mut arr = vec![0; n32 as usize];
|
let mut arr = vec![0; n32 as usize];
|
||||||
for j in 0..n32 {
|
for j in 0..n32 {
|
||||||
arr[(n32 as usize) - 1 - (j as usize)] = self.instance.read_shared_rw_memory(j)?;
|
arr[(n32 as usize) - 1 - (j as usize)] =
|
||||||
|
self.instance.read_shared_rw_memory(store, j)?;
|
||||||
}
|
}
|
||||||
w.push(from_array32(arr));
|
w.push(from_array32(arr));
|
||||||
}
|
}
|
||||||
@ -261,16 +157,16 @@ impl WitnessCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_witness_element<
|
pub fn calculate_witness_element<
|
||||||
E: ark_ec::pairing::Pairing,
|
F: PrimeField,
|
||||||
I: IntoIterator<Item = (String, Vec<BigInt>)>,
|
I: IntoIterator<Item = (String, Vec<BigInt>)>,
|
||||||
>(
|
>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
store: &mut Store,
|
||||||
inputs: I,
|
inputs: I,
|
||||||
sanity_check: bool,
|
sanity_check: bool,
|
||||||
) -> Result<Vec<E::ScalarField>> {
|
) -> Result<Vec<F>> {
|
||||||
use ark_ff::PrimeField;
|
let modulus = F::MODULUS;
|
||||||
let witness = self.calculate_witness(inputs, sanity_check)?;
|
let witness = self.calculate_witness(store, inputs, sanity_check)?;
|
||||||
let modulus = <E::ScalarField as PrimeField>::MODULUS;
|
|
||||||
|
|
||||||
// convert it to field elements
|
// convert it to field elements
|
||||||
use num_traits::Signed;
|
use num_traits::Signed;
|
||||||
@ -283,33 +179,19 @@ impl WitnessCalculator {
|
|||||||
} else {
|
} else {
|
||||||
w.to_biguint().unwrap()
|
w.to_biguint().unwrap()
|
||||||
};
|
};
|
||||||
E::ScalarField::from(w)
|
F::from(w)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(witness)
|
Ok(witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_witness_buffer(&self) -> Result<Vec<u8>> {
|
|
||||||
let ptr = self.instance.get_ptr_witness_buffer()? as usize;
|
|
||||||
|
|
||||||
let view = self.memory.memory.view::<u8>();
|
|
||||||
|
|
||||||
let len = self.instance.get_n_vars()? * self.n64 * 8;
|
|
||||||
let arr = view[ptr..ptr + len as usize]
|
|
||||||
.iter()
|
|
||||||
.map(Cell::get)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(arr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// callback hooks for debugging
|
// callback hooks for debugging
|
||||||
mod runtime {
|
mod runtime {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn error(store: &Store) -> Function {
|
pub fn error(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
fn func(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> Result<(), RuntimeError> {
|
fn func(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> Result<(), RuntimeError> {
|
||||||
@ -318,47 +200,47 @@ mod runtime {
|
|||||||
println!("runtime error, exiting early: {a} {b} {c} {d} {e} {f}",);
|
println!("runtime error, exiting early: {a} {b} {c} {d} {e} {f}",);
|
||||||
Err(RuntimeError::user(Box::new(ExitCode(1))))
|
Err(RuntimeError::user(Box::new(ExitCode(1))))
|
||||||
}
|
}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circom 2.0
|
// Circom 2.0
|
||||||
pub fn exception_handler(store: &Store) -> Function {
|
pub fn exception_handler(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn func(a: i32) {}
|
fn func(a: i32) {}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circom 2.0
|
// Circom 2.0
|
||||||
pub fn show_memory(store: &Store) -> Function {
|
pub fn show_memory(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn func() {}
|
fn func() {}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circom 2.0
|
// Circom 2.0
|
||||||
pub fn print_error_message(store: &Store) -> Function {
|
pub fn print_error_message(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn func() {}
|
fn func() {}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circom 2.0
|
// Circom 2.0
|
||||||
pub fn write_buffer_message(store: &Store) -> Function {
|
pub fn write_buffer_message(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn func() {}
|
fn func() {}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_signal(store: &Store) -> Function {
|
pub fn log_signal(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn func(a: i32, b: i32) {}
|
fn func(a: i32, b: i32) {}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_component(store: &Store) -> Function {
|
pub fn log_component(store: &mut Store) -> Function {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn func(a: i32) {}
|
fn func(a: i32) {}
|
||||||
Function::new_native(store, func)
|
Function::new_typed(store, func)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +252,6 @@ mod tests {
|
|||||||
struct TestCase<'a> {
|
struct TestCase<'a> {
|
||||||
circuit_path: &'a str,
|
circuit_path: &'a str,
|
||||||
inputs_path: &'a str,
|
inputs_path: &'a str,
|
||||||
n_vars: u32,
|
|
||||||
n64: u32,
|
n64: u32,
|
||||||
witness: &'a [&'a str],
|
witness: &'a [&'a str],
|
||||||
}
|
}
|
||||||
@ -381,23 +262,21 @@ mod tests {
|
|||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn multiplier_1() {
|
async fn multiplier_1() {
|
||||||
run_test(TestCase {
|
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(),
|
inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
|
||||||
n_vars: 4,
|
|
||||||
n64: 4,
|
n64: 4,
|
||||||
witness: &["1", "33", "3", "11"],
|
witness: &["1", "33", "3", "11"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn multiplier_2() {
|
async fn multiplier_2() {
|
||||||
run_test(TestCase {
|
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(),
|
inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(),
|
||||||
n_vars: 4,
|
|
||||||
n64: 4,
|
n64: 4,
|
||||||
witness: &[
|
witness: &[
|
||||||
"1",
|
"1",
|
||||||
@ -408,12 +287,11 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn multiplier_3() {
|
async fn multiplier_3() {
|
||||||
run_test(TestCase {
|
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(),
|
inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(),
|
||||||
n_vars: 4,
|
|
||||||
n64: 4,
|
n64: 4,
|
||||||
witness: &[
|
witness: &[
|
||||||
"1",
|
"1",
|
||||||
@ -424,32 +302,15 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn safe_multipler() {
|
async fn safe_multipler() {
|
||||||
let witness =
|
let witness =
|
||||||
std::fs::read_to_string(root_path("test-vectors/safe-circuit-witness.json")).unwrap();
|
std::fs::read_to_string(root_path("test-vectors/safe-circuit-witness.json")).unwrap();
|
||||||
let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
|
let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
|
||||||
let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
|
let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
|
||||||
run_test(TestCase {
|
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(),
|
inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
|
||||||
n_vars: 132, // 128 + 4
|
|
||||||
n64: 4,
|
|
||||||
witness,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn smt_verifier() {
|
|
||||||
let witness =
|
|
||||||
std::fs::read_to_string(root_path("test-vectors/smtverifier10-witness.json")).unwrap();
|
|
||||||
let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
|
|
||||||
let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
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,
|
n64: 4,
|
||||||
witness,
|
witness,
|
||||||
});
|
});
|
||||||
@ -467,12 +328,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_test(case: TestCase) {
|
fn run_test(case: TestCase) {
|
||||||
let mut wtns = WitnessCalculator::new(case.circuit_path).unwrap();
|
let mut store = Store::default();
|
||||||
|
let mut wtns = WitnessCalculator::new(&mut store, case.circuit_path).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
wtns.memory.prime.to_str_radix(16),
|
wtns.prime.to_str_radix(16),
|
||||||
"30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase()
|
"30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase()
|
||||||
);
|
);
|
||||||
assert_eq!({ wtns.instance.get_n_vars().unwrap() }, case.n_vars);
|
|
||||||
assert_eq!({ wtns.n64 }, case.n64);
|
assert_eq!({ wtns.n64 }, case.n64);
|
||||||
|
|
||||||
let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap();
|
let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap();
|
||||||
@ -497,7 +358,7 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let res = wtns.calculate_witness(inputs, false).unwrap();
|
let res = wtns.calculate_witness(&mut store, inputs, false).unwrap();
|
||||||
for (r, w) in res.iter().zip(case.witness) {
|
for (r, w) in res.iter().zip(case.witness) {
|
||||||
assert_eq!(r, &BigInt::from_str(w).unwrap());
|
assert_eq!(r, &BigInt::from_str(w).unwrap());
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/zkey.rs
21
src/zkey.rs
@ -375,6 +375,7 @@ mod tests {
|
|||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use wasmer::Store;
|
||||||
|
|
||||||
use crate::circom::CircomReduction;
|
use crate::circom::CircomReduction;
|
||||||
use crate::witness::WitnessCalculator;
|
use crate::witness::WitnessCalculator;
|
||||||
@ -842,14 +843,14 @@ mod tests {
|
|||||||
G2Affine::from(G2Projective::new(x, y, z))
|
G2Affine::from(G2Projective::new(x, y, z))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn verify_proof_with_zkey_with_r1cs() {
|
async fn verify_proof_with_zkey_with_r1cs() {
|
||||||
let path = "./test-vectors/test.zkey";
|
let path = "./test-vectors/test.zkey";
|
||||||
let mut file = File::open(path).unwrap();
|
let mut file = File::open(path).unwrap();
|
||||||
let (params, _matrices) = read_zkey(&mut file).unwrap(); // binfile.proving_key().unwrap();
|
let (params, _matrices) = read_zkey(&mut file).unwrap(); // binfile.proving_key().unwrap();
|
||||||
|
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
"./test-vectors/mycircuit.wasm",
|
"./test-vectors/mycircuit_js/mycircuit.wasm",
|
||||||
"./test-vectors/mycircuit.r1cs",
|
"./test-vectors/mycircuit.r1cs",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -871,13 +872,15 @@ mod tests {
|
|||||||
assert!(verified);
|
assert!(verified);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn verify_proof_with_zkey_without_r1cs() {
|
async fn verify_proof_with_zkey_without_r1cs() {
|
||||||
let path = "./test-vectors/test.zkey";
|
let path = "./test-vectors/test.zkey";
|
||||||
let mut file = File::open(path).unwrap();
|
let mut file = File::open(path).unwrap();
|
||||||
let (params, matrices) = read_zkey(&mut file).unwrap();
|
let (params, matrices) = read_zkey(&mut file).unwrap();
|
||||||
|
let mut store = Store::default();
|
||||||
let mut wtns = WitnessCalculator::new("./test-vectors/mycircuit.wasm").unwrap();
|
let mut wtns =
|
||||||
|
WitnessCalculator::new(&mut store, "./test-vectors/mycircuit_js/mycircuit.wasm")
|
||||||
|
.unwrap();
|
||||||
let mut inputs: HashMap<String, Vec<num_bigint::BigInt>> = HashMap::new();
|
let mut inputs: HashMap<String, Vec<num_bigint::BigInt>> = HashMap::new();
|
||||||
let values = inputs.entry("a".to_string()).or_insert_with(Vec::new);
|
let values = inputs.entry("a".to_string()).or_insert_with(Vec::new);
|
||||||
values.push(3.into());
|
values.push(3.into());
|
||||||
@ -895,7 +898,7 @@ mod tests {
|
|||||||
let s = ark_bn254::Fr::rand(rng);
|
let s = ark_bn254::Fr::rand(rng);
|
||||||
|
|
||||||
let full_assignment = wtns
|
let full_assignment = wtns
|
||||||
.calculate_witness_element::<Bn254, _>(inputs, false)
|
.calculate_witness_element::<Fr, _>(&mut store, inputs, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let proof = Groth16::<Bn254, CircomReduction>::create_proof_with_reduction_and_matrices(
|
let proof = Groth16::<Bn254, CircomReduction>::create_proof_with_reduction_and_matrices(
|
||||||
¶ms,
|
¶ms,
|
||||||
|
|||||||
@ -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();
|
|
||||||
});
|
|
||||||
Binary file not shown.
@ -1,3 +1,5 @@
|
|||||||
|
pragma circom 2.0.0;
|
||||||
|
|
||||||
template CheckBits(n) {
|
template CheckBits(n) {
|
||||||
signal input in;
|
signal input in;
|
||||||
signal bits[n];
|
signal bits[n];
|
||||||
@ -15,8 +17,8 @@ template CheckBits(n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template Multiplier(n) {
|
template Multiplier(n) {
|
||||||
signal private input a;
|
signal input a;
|
||||||
signal private input b;
|
signal input b;
|
||||||
signal output c;
|
signal output c;
|
||||||
signal inva;
|
signal inva;
|
||||||
signal invb;
|
signal invb;
|
||||||
|
|||||||
BIN
test-vectors/circuit2.r1cs
Normal file
BIN
test-vectors/circuit2.r1cs
Normal file
Binary file not shown.
Binary file not shown.
BIN
test-vectors/circuit2_js/circuit2.wasm
Normal file
BIN
test-vectors/circuit2_js/circuit2.wasm
Normal file
Binary file not shown.
20
test-vectors/circuit2_js/generate_witness.js
Normal file
20
test-vectors/circuit2_js/generate_witness.js
Normal file
@ -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 <file.wasm> <input.json> <output.wtns>");
|
||||||
|
} 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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
BIN
test-vectors/circuit2_js/witness.wtns
Normal file
BIN
test-vectors/circuit2_js/witness.wtns
Normal file
Binary file not shown.
337
test-vectors/circuit2_js/witness_calculator.js
Normal file
337
test-vectors/circuit2_js/witness_calculator.js
Normal file
@ -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<shared_rw_memory_size; j++) {
|
||||||
|
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've buffered other content, put a space in between the items
|
||||||
|
if (msgStr !== "") {
|
||||||
|
msgStr += " "
|
||||||
|
}
|
||||||
|
// Then append the value to the message we are creating
|
||||||
|
msgStr += (fromArray32(arr).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class WitnessCalculator {
|
||||||
|
constructor(instance, sanityCheck) {
|
||||||
|
this.instance = instance;
|
||||||
|
|
||||||
|
this.version = this.instance.exports.getVersion();
|
||||||
|
this.n32 = this.instance.exports.getFieldNumLen32();
|
||||||
|
|
||||||
|
this.instance.exports.getRawPrime();
|
||||||
|
const arr = new Uint32Array(this.n32);
|
||||||
|
for (let i=0; i<this.n32; i++) {
|
||||||
|
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
||||||
|
}
|
||||||
|
this.prime = fromArray32(arr);
|
||||||
|
|
||||||
|
this.witnessSize = this.instance.exports.getWitnessSize();
|
||||||
|
|
||||||
|
this.sanityCheck = sanityCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
circom_version() {
|
||||||
|
return this.instance.exports.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _doCalculateWitness(input, sanityCheck) {
|
||||||
|
//input is assumed to be a map from signals to arrays of bigints
|
||||||
|
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
||||||
|
const keys = Object.keys(input);
|
||||||
|
var input_counter = 0;
|
||||||
|
keys.forEach( (k) => {
|
||||||
|
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; i<fArr.length; i++) {
|
||||||
|
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32)
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
||||||
|
input_counter++;
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`After adding signal ${i} of ${k}`)
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
if (input_counter < this.instance.exports.getInputSize()) {
|
||||||
|
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateWitness(input, sanityCheck) {
|
||||||
|
|
||||||
|
const w = [];
|
||||||
|
|
||||||
|
await this._doCalculateWitness(input, sanityCheck);
|
||||||
|
|
||||||
|
for (let i=0; i<this.witnessSize; i++) {
|
||||||
|
this.instance.exports.getWitness(i);
|
||||||
|
const arr = new Uint32Array(this.n32);
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
w.push(fromArray32(arr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async calculateBinWitness(input, sanityCheck) {
|
||||||
|
|
||||||
|
const buff32 = new Uint32Array(this.witnessSize*this.n32);
|
||||||
|
const buff = new Uint8Array( buff32.buffer);
|
||||||
|
await this._doCalculateWitness(input, sanityCheck);
|
||||||
|
|
||||||
|
for (let i=0; i<this.witnessSize; i++) {
|
||||||
|
this.instance.exports.getWitness(i);
|
||||||
|
const pos = i*this.n32;
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async calculateWTNSBin(input, sanityCheck) {
|
||||||
|
|
||||||
|
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
|
||||||
|
const buff = new Uint8Array( buff32.buffer);
|
||||||
|
await this._doCalculateWitness(input, sanityCheck);
|
||||||
|
|
||||||
|
//"wtns"
|
||||||
|
buff[0] = "w".charCodeAt(0)
|
||||||
|
buff[1] = "t".charCodeAt(0)
|
||||||
|
buff[2] = "n".charCodeAt(0)
|
||||||
|
buff[3] = "s".charCodeAt(0)
|
||||||
|
|
||||||
|
//version 2
|
||||||
|
buff32[1] = 2;
|
||||||
|
|
||||||
|
//number of sections: 2
|
||||||
|
buff32[2] = 2;
|
||||||
|
|
||||||
|
//id section 1
|
||||||
|
buff32[3] = 1;
|
||||||
|
|
||||||
|
const n8 = this.n32*4;
|
||||||
|
//id section 1 length in 64bytes
|
||||||
|
const idSection1length = 8 + n8;
|
||||||
|
const idSection1lengthHex = idSection1length.toString(16);
|
||||||
|
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
||||||
|
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
||||||
|
|
||||||
|
//this.n32
|
||||||
|
buff32[6] = n8;
|
||||||
|
|
||||||
|
//prime number
|
||||||
|
this.instance.exports.getRawPrime();
|
||||||
|
|
||||||
|
var pos = 7;
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
pos += this.n32;
|
||||||
|
|
||||||
|
// witness size
|
||||||
|
buff32[pos] = this.witnessSize;
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
//id section 2
|
||||||
|
buff32[pos] = 2;
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
// section 2 length
|
||||||
|
const idSection2length = n8*this.witnessSize;
|
||||||
|
const idSection2lengthHex = idSection2length.toString(16);
|
||||||
|
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
|
||||||
|
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
|
||||||
|
|
||||||
|
pos += 2;
|
||||||
|
for (let i=0; i<this.witnessSize; i++) {
|
||||||
|
this.instance.exports.getWitness(i);
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
pos += this.n32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toArray32(rem,size) {
|
||||||
|
const res = []; //new Uint32Array(size); //has no unshift
|
||||||
|
const radix = BigInt(0x100000000);
|
||||||
|
while (rem) {
|
||||||
|
res.unshift( Number(rem % radix));
|
||||||
|
rem = rem / radix;
|
||||||
|
}
|
||||||
|
if (size) {
|
||||||
|
var i = size - res.length;
|
||||||
|
while (i>0) {
|
||||||
|
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<arr.length; i++) {
|
||||||
|
res = res*radix + BigInt(arr[i]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flatArray(a) {
|
||||||
|
var res = [];
|
||||||
|
fillArray(res, a);
|
||||||
|
return res;
|
||||||
|
|
||||||
|
function fillArray(res, a) {
|
||||||
|
if (Array.isArray(a)) {
|
||||||
|
for (let i=0; i<a.length; i++) {
|
||||||
|
fillArray(res, a[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(n, prime) {
|
||||||
|
let res = BigInt(n) % prime
|
||||||
|
if (res < 0) res += prime
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnvHash(str) {
|
||||||
|
const uint64_max = BigInt(2) ** BigInt(64);
|
||||||
|
let hash = BigInt("0xCBF29CE484222325");
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
hash ^= BigInt(str[i].charCodeAt());
|
||||||
|
hash *= BigInt(0x100000001B3);
|
||||||
|
hash %= uint64_max;
|
||||||
|
}
|
||||||
|
let shash = hash.toString(16);
|
||||||
|
let n = 16 - shash.length;
|
||||||
|
shash = '0'.repeat(n).concat(shash);
|
||||||
|
return shash;
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
{ "a": 3, "b": 11 }
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
|
pragma circom 2.1.0;
|
||||||
|
|
||||||
template Multiplier() {
|
template Multiplier() {
|
||||||
signal private input a;
|
signal input a;
|
||||||
signal private input b;
|
signal input b;
|
||||||
signal output c;
|
signal output c;
|
||||||
|
|
||||||
c <== a*b;
|
c <== a*b;
|
||||||
|
|||||||
Binary file not shown.
23
test-vectors/mycircuit.sh
Executable file
23
test-vectors/mycircuit.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
# run from within test-vectors dir
|
||||||
|
DIR="test-vectors"
|
||||||
|
if [ ! -d "$DIR" ]; then
|
||||||
|
echo "Directory $DIR does not exist. Please ensure you are running this script from the correct location."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
echo "compiling"
|
||||||
|
circom mycircuit.circom --r1cs --wasm
|
||||||
|
|
||||||
|
FILE="powersOfTau28_hez_final_17.ptau"
|
||||||
|
if [ ! -f "$FILE" ]; then
|
||||||
|
echo "getting powers of tau"
|
||||||
|
curl -O https://storage.googleapis.com/zkevm/ptau/powersOfTau28_hez_final_17.ptau
|
||||||
|
else
|
||||||
|
echo "$FILE already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echo "writing zkey"
|
||||||
|
snarkjs zkey new mycircuit.r1cs powersOfTau28_hez_final_17.ptau test.zkey
|
||||||
@ -1,3 +0,0 @@
|
|||||||
1,2,0,main.a
|
|
||||||
2,3,0,main.b
|
|
||||||
3,1,0,main.c
|
|
||||||
Binary file not shown.
20
test-vectors/mycircuit_js/generate_witness.js
Normal file
20
test-vectors/mycircuit_js/generate_witness.js
Normal file
@ -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 <file.wasm> <input.json> <output.wtns>");
|
||||||
|
} 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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Binary file not shown.
337
test-vectors/mycircuit_js/witness_calculator.js
Normal file
337
test-vectors/mycircuit_js/witness_calculator.js
Normal file
@ -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<shared_rw_memory_size; j++) {
|
||||||
|
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've buffered other content, put a space in between the items
|
||||||
|
if (msgStr !== "") {
|
||||||
|
msgStr += " "
|
||||||
|
}
|
||||||
|
// Then append the value to the message we are creating
|
||||||
|
msgStr += (fromArray32(arr).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class WitnessCalculator {
|
||||||
|
constructor(instance, sanityCheck) {
|
||||||
|
this.instance = instance;
|
||||||
|
|
||||||
|
this.version = this.instance.exports.getVersion();
|
||||||
|
this.n32 = this.instance.exports.getFieldNumLen32();
|
||||||
|
|
||||||
|
this.instance.exports.getRawPrime();
|
||||||
|
const arr = new Uint32Array(this.n32);
|
||||||
|
for (let i=0; i<this.n32; i++) {
|
||||||
|
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
||||||
|
}
|
||||||
|
this.prime = fromArray32(arr);
|
||||||
|
|
||||||
|
this.witnessSize = this.instance.exports.getWitnessSize();
|
||||||
|
|
||||||
|
this.sanityCheck = sanityCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
circom_version() {
|
||||||
|
return this.instance.exports.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _doCalculateWitness(input, sanityCheck) {
|
||||||
|
//input is assumed to be a map from signals to arrays of bigints
|
||||||
|
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
||||||
|
const keys = Object.keys(input);
|
||||||
|
var input_counter = 0;
|
||||||
|
keys.forEach( (k) => {
|
||||||
|
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; i<fArr.length; i++) {
|
||||||
|
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32)
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
||||||
|
input_counter++;
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`After adding signal ${i} of ${k}`)
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
if (input_counter < this.instance.exports.getInputSize()) {
|
||||||
|
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async calculateWitness(input, sanityCheck) {
|
||||||
|
|
||||||
|
const w = [];
|
||||||
|
|
||||||
|
await this._doCalculateWitness(input, sanityCheck);
|
||||||
|
|
||||||
|
for (let i=0; i<this.witnessSize; i++) {
|
||||||
|
this.instance.exports.getWitness(i);
|
||||||
|
const arr = new Uint32Array(this.n32);
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
w.push(fromArray32(arr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async calculateBinWitness(input, sanityCheck) {
|
||||||
|
|
||||||
|
const buff32 = new Uint32Array(this.witnessSize*this.n32);
|
||||||
|
const buff = new Uint8Array( buff32.buffer);
|
||||||
|
await this._doCalculateWitness(input, sanityCheck);
|
||||||
|
|
||||||
|
for (let i=0; i<this.witnessSize; i++) {
|
||||||
|
this.instance.exports.getWitness(i);
|
||||||
|
const pos = i*this.n32;
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async calculateWTNSBin(input, sanityCheck) {
|
||||||
|
|
||||||
|
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
|
||||||
|
const buff = new Uint8Array( buff32.buffer);
|
||||||
|
await this._doCalculateWitness(input, sanityCheck);
|
||||||
|
|
||||||
|
//"wtns"
|
||||||
|
buff[0] = "w".charCodeAt(0)
|
||||||
|
buff[1] = "t".charCodeAt(0)
|
||||||
|
buff[2] = "n".charCodeAt(0)
|
||||||
|
buff[3] = "s".charCodeAt(0)
|
||||||
|
|
||||||
|
//version 2
|
||||||
|
buff32[1] = 2;
|
||||||
|
|
||||||
|
//number of sections: 2
|
||||||
|
buff32[2] = 2;
|
||||||
|
|
||||||
|
//id section 1
|
||||||
|
buff32[3] = 1;
|
||||||
|
|
||||||
|
const n8 = this.n32*4;
|
||||||
|
//id section 1 length in 64bytes
|
||||||
|
const idSection1length = 8 + n8;
|
||||||
|
const idSection1lengthHex = idSection1length.toString(16);
|
||||||
|
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
||||||
|
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
||||||
|
|
||||||
|
//this.n32
|
||||||
|
buff32[6] = n8;
|
||||||
|
|
||||||
|
//prime number
|
||||||
|
this.instance.exports.getRawPrime();
|
||||||
|
|
||||||
|
var pos = 7;
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
pos += this.n32;
|
||||||
|
|
||||||
|
// witness size
|
||||||
|
buff32[pos] = this.witnessSize;
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
//id section 2
|
||||||
|
buff32[pos] = 2;
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
// section 2 length
|
||||||
|
const idSection2length = n8*this.witnessSize;
|
||||||
|
const idSection2lengthHex = idSection2length.toString(16);
|
||||||
|
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
|
||||||
|
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
|
||||||
|
|
||||||
|
pos += 2;
|
||||||
|
for (let i=0; i<this.witnessSize; i++) {
|
||||||
|
this.instance.exports.getWitness(i);
|
||||||
|
for (let j=0; j<this.n32; j++) {
|
||||||
|
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||||
|
}
|
||||||
|
pos += this.n32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toArray32(rem,size) {
|
||||||
|
const res = []; //new Uint32Array(size); //has no unshift
|
||||||
|
const radix = BigInt(0x100000000);
|
||||||
|
while (rem) {
|
||||||
|
res.unshift( Number(rem % radix));
|
||||||
|
rem = rem / radix;
|
||||||
|
}
|
||||||
|
if (size) {
|
||||||
|
var i = size - res.length;
|
||||||
|
while (i>0) {
|
||||||
|
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<arr.length; i++) {
|
||||||
|
res = res*radix + BigInt(arr[i]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flatArray(a) {
|
||||||
|
var res = [];
|
||||||
|
fillArray(res, a);
|
||||||
|
return res;
|
||||||
|
|
||||||
|
function fillArray(res, a) {
|
||||||
|
if (Array.isArray(a)) {
|
||||||
|
for (let i=0; i<a.length; i++) {
|
||||||
|
fillArray(res, a[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(n, prime) {
|
||||||
|
let res = BigInt(n) % prime
|
||||||
|
if (res < 0) res += prime
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnvHash(str) {
|
||||||
|
const uint64_max = BigInt(2) ** BigInt(64);
|
||||||
|
let hash = BigInt("0xCBF29CE484222325");
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
hash ^= BigInt(str[i].charCodeAt());
|
||||||
|
hash *= BigInt(0x100000001B3);
|
||||||
|
hash %= uint64_max;
|
||||||
|
}
|
||||||
|
let shash = hash.toString(16);
|
||||||
|
let n = 16 - shash.length;
|
||||||
|
shash = '0'.repeat(n).concat(shash);
|
||||||
|
return shash;
|
||||||
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
template A(n) {
|
|
||||||
signal input in;
|
|
||||||
signal output out;
|
|
||||||
|
|
||||||
signal intermediate[n];
|
|
||||||
|
|
||||||
intermediate[0] <== in;
|
|
||||||
for (var i=1; i<n; i++) {
|
|
||||||
intermediate[i] <== intermediate[i-1] * intermediate[i-1] + i;
|
|
||||||
}
|
|
||||||
out <== intermediate[n-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
component main = A({{N}});
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"pi_a": [
|
|
||||||
"6235746210673106891683313862449025023627555115162992763714613342703079170148",
|
|
||||||
"12399711040178466467332477784020211067450597599035925008336813728555144120335",
|
|
||||||
"1"
|
|
||||||
],
|
|
||||||
"pi_b": [
|
|
||||||
[
|
|
||||||
"3878778368997395576585378205610237973645840367459368364069435490380759882761",
|
|
||||||
"6372446288114997398874714591076628465205914797783709795970421045876437438845"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"1848758415443668505660007055104065117685874818267116126442518003813688485119",
|
|
||||||
"13151381207181352787620244234186261096498266850828100859417113864178798435289"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"1",
|
|
||||||
"0"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"pi_c": [
|
|
||||||
"20702909955866523755177574141774962608204777398014771961964213040023134853917",
|
|
||||||
"11380617408700662148925472638480792573061992967930438232337416544412668869948",
|
|
||||||
"1"
|
|
||||||
],
|
|
||||||
"protocol": "groth16",
|
|
||||||
"curve": "bn128"
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
[
|
|
||||||
"20227169454906525228014700210166866282343639252280745415680311389428188660505"
|
|
||||||
]
|
|
||||||
@ -1 +1,134 @@
|
|||||||
["1","33","3","11","10944121435919637611123202872628637544274182200208017171849102093287904247809","15321770010287492655572484021680092561983855080291224040588742930603065946932","1","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","0","1","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"]
|
[
|
||||||
|
"1",
|
||||||
|
"33",
|
||||||
|
"3",
|
||||||
|
"11",
|
||||||
|
"10944121435919637611123202872628637544274182200208017171849102093287904247809",
|
||||||
|
"15321770010287492655572484021680092561983855080291224040588742930603065946932",
|
||||||
|
"1",
|
||||||
|
"1",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"1",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
]
|
||||||
15
test-vectors/safe-circuit.sh
Executable file
15
test-vectors/safe-circuit.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
# run from within test-vectors dir
|
||||||
|
DIR="test-vectors"
|
||||||
|
if [ ! -d "$DIR" ]; then
|
||||||
|
echo "Directory $DIR does not exist. Please ensure you are running this script from the correct location."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
echo "compiling"
|
||||||
|
circom circuit2.circom --wasm --r1cs
|
||||||
|
|
||||||
|
node circuit2_js/generate_witness.js circuit2_js/circuit2.wasm mycircuit-input1.json circuit2_js/witness.wtns
|
||||||
|
|
||||||
|
snarkjs wej circuit2_js/witness.wtns safe-circuit-witness.json
|
||||||
@ -1 +0,0 @@
|
|||||||
{"enabled":1,"fnc":0,"root":"4677130581325536491486966387607462164138332022971476080171400451642918512081","siblings":["3663166078965935940798554689567237216195612079341396621785946741270885707796","0","0","15268343501033916092396853374199187988748455820543796633535012025134089057292","0","0","0","0","0","0"],"oldKey":0,"oldValue":0,"isOld0":0,"key":8,"value":"88"}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2,16 +2,16 @@ use ark_circom::{CircomBuilder, CircomConfig};
|
|||||||
use ark_std::rand::thread_rng;
|
use ark_std::rand::thread_rng;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
|
||||||
use ark_bn254::Bn254;
|
use ark_bn254::{Bn254, Fr};
|
||||||
use ark_crypto_primitives::snark::SNARK;
|
use ark_crypto_primitives::snark::SNARK;
|
||||||
use ark_groth16::Groth16;
|
use ark_groth16::Groth16;
|
||||||
|
|
||||||
type GrothBn = Groth16<Bn254>;
|
type GrothBn = Groth16<Bn254>;
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn groth16_proof() -> Result<()> {
|
async fn groth16_proof() -> Result<()> {
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
"./test-vectors/mycircuit.wasm",
|
"./test-vectors/mycircuit_js/mycircuit.wasm",
|
||||||
"./test-vectors/mycircuit.r1cs",
|
"./test-vectors/mycircuit.r1cs",
|
||||||
)?;
|
)?;
|
||||||
let mut builder = CircomBuilder::new(cfg);
|
let mut builder = CircomBuilder::new(cfg);
|
||||||
@ -39,33 +39,44 @@ fn groth16_proof() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn groth16_proof_wrong_input() {
|
async fn groth16_proof_wrong_input() -> Result<()> {
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
"./test-vectors/mycircuit.wasm",
|
"./test-vectors/mycircuit_js/mycircuit.wasm",
|
||||||
"./test-vectors/mycircuit.r1cs",
|
"./test-vectors/mycircuit.r1cs",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut builder = CircomBuilder::new(cfg);
|
let mut builder = CircomBuilder::new(cfg);
|
||||||
builder.push_input("a", 3);
|
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);
|
builder.push_input("foo", 11);
|
||||||
|
|
||||||
// create an empty instance for setting it up
|
// create an empty instance for setting it up
|
||||||
let circom = builder.setup();
|
let circom = builder.setup();
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
#[cfg(feature = "circom-2")]
|
async fn groth16_proof_circom() -> Result<()> {
|
||||||
fn groth16_proof_circom2() -> Result<()> {
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
"test-vectors/circuit2_js/circuit2.wasm",
|
||||||
"./test-vectors/circom2_multiplier2.wasm",
|
"test-vectors/circuit2.r1cs",
|
||||||
"./test-vectors/circom2_multiplier2.r1cs",
|
|
||||||
)?;
|
)?;
|
||||||
let mut builder = CircomBuilder::new(cfg);
|
let mut builder = CircomBuilder::new(cfg);
|
||||||
builder.push_input("a", 3);
|
builder.push_input("a", 3);
|
||||||
@ -92,12 +103,11 @@ fn groth16_proof_circom2() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
#[cfg(feature = "circom-2")]
|
async fn witness_generation_circom() -> Result<()> {
|
||||||
fn witness_generation_circom2() -> Result<()> {
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
"test-vectors/circuit2_js/circuit2.wasm",
|
||||||
"./test-vectors/circom2_multiplier2.wasm",
|
"test-vectors/circuit2.r1cs",
|
||||||
"./test-vectors/circom2_multiplier2.r1cs",
|
|
||||||
)?;
|
)?;
|
||||||
let mut builder = CircomBuilder::new(cfg);
|
let mut builder = CircomBuilder::new(cfg);
|
||||||
builder.push_input("a", 3);
|
builder.push_input("a", 3);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use ark_circom::{ethereum, CircomBuilder, CircomConfig};
|
|||||||
use ark_std::rand::thread_rng;
|
use ark_std::rand::thread_rng;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
|
||||||
use ark_bn254::Bn254;
|
use ark_bn254::{Bn254, Fr};
|
||||||
use ark_crypto_primitives::snark::SNARK;
|
use ark_crypto_primitives::snark::SNARK;
|
||||||
use ark_groth16::Groth16;
|
use ark_groth16::Groth16;
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ use std::{convert::TryFrom, sync::Arc};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn solidity_verifier() -> Result<()> {
|
async fn solidity_verifier() -> Result<()> {
|
||||||
let cfg = CircomConfig::<Bn254>::new(
|
let cfg = CircomConfig::<Fr>::new(
|
||||||
"./test-vectors/mycircuit.wasm",
|
"./test-vectors/mycircuit_js/mycircuit.wasm",
|
||||||
"./test-vectors/mycircuit.r1cs",
|
"./test-vectors/mycircuit.r1cs",
|
||||||
)?;
|
)?;
|
||||||
let mut builder = CircomBuilder::new(cfg);
|
let mut builder = CircomBuilder::new(cfg);
|
||||||
@ -61,7 +61,6 @@ async fn solidity_verifier() -> Result<()> {
|
|||||||
// the ones expected by the abigen'd types. Could we maybe provide a convenience
|
// the ones expected by the abigen'd types. Could we maybe provide a convenience
|
||||||
// macro for these, given that there's room for implementation error?
|
// macro for these, given that there's room for implementation error?
|
||||||
abigen!(Groth16Verifier, "./tests/verifier_artifact.json");
|
abigen!(Groth16Verifier, "./tests/verifier_artifact.json");
|
||||||
use groth_16_verifier::{G1Point, G2Point, Proof, VerifyingKey};
|
|
||||||
impl From<ethereum::G1> for G1Point {
|
impl From<ethereum::G1> for G1Point {
|
||||||
fn from(src: ethereum::G1) -> Self {
|
fn from(src: ethereum::G1) -> Self {
|
||||||
Self { x: src.x, y: src.y }
|
Self { x: src.x, y: src.y }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user