commit b64f038283ee98a3c3e52b664df790759a6e820c Author: Georgios Konstantopoulos Date: Mon Jul 26 12:45:07 2021 +0300 initial commit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b676657 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +on: [pull_request, push] + +name: Tests + +jobs: + tests: + 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 + + - name: Install node + uses: actions/setup-node@v1 + with: + node-version: 10 + + - name: Install ganache + run: npm install -g ganache-cli + + - name: Install Solc + run: | + mkdir -p "$HOME/bin" + wget -q https://github.com/ethereum/solidity/releases/download/v0.7.6/solc-static-linux -O $HOME/bin/solc + chmod u+x "$HOME/bin/solc" + export PATH=$HOME/bin:$PATH + solc --version + + - name: cargo test + run: | + export PATH=$HOME/bin:$PATH + cargo test + + lint: + 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 + components: rustfmt, clippy + - name: cargo fmt + run: cargo fmt --all -- --check + - name: cargo clippy + run: cargo clippy -- -D warnings diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..277500f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "ark-circom" +version = "0.1.0" +edition = "2018" + +[dependencies] +# WASM operations +wasmer = "1.0.1" +fnv = "1.0.3" +num-traits = "0.2.0" +num-bigint = { version = "0.4", features = ["rand"] } + + +color-eyre = "0.5" + +# ZKP Generation +ark-ec = "0.3.0" +ark-ff = "0.3.0" +ark-std = "0.3.0" +ark-bn254 = "0.3.0" +ark-r1cs-std = "0.3.1" +ark-groth16 = { git = "https://github.com/kobigurk/groth16", version = "0.3.0", branch = "feat/customizable-r1cs-to-qap" } +ark-poly = { version = "^0.3.0", default-features = false } +ark-relations = "0.3.0" + +hex = "0.4.3" +byteorder = "1.4.3" + +ethers = { git = "https://github.com/gakonst/ethers-rs", features = ["abigen"] } +serde_json = "1.0.64" +serde = "1.0.126" +thiserror = "1.0.26" +memmap = "0.7.0" +ark-serialize = "0.3.0" + +[dev-dependencies] +hex-literal = "0.2.1" +tokio = { version = "1.7.1", features = ["macros"] } + diff --git a/README.md b/README.md new file mode 100644 index 0000000..25812df --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ark-circom diff --git a/src/circom_qap.rs b/src/circom_qap.rs new file mode 100644 index 0000000..4b94b8d --- /dev/null +++ b/src/circom_qap.rs @@ -0,0 +1,115 @@ +use ark_ff::PrimeField; +use ark_groth16::r1cs_to_qap::{evaluate_constraint, QAPCalculator, R1CStoQAP}; +use ark_poly::EvaluationDomain; +use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; +use ark_std::{cfg_into_iter, cfg_iter, cfg_iter_mut, vec}; +use core::ops::Deref; + +/// Implements the witness map used by snarkjs. The arkworks witness map calculates the +/// coefficients of H through computing (AB-C)/Z in the evaluation domain and going back to the +/// coefficients domain. snarkjs instead precomputes the Lagrange form of the powers of tau bases +/// in a domain twice as large and the witness map is computed as the odd coefficients of (AB-C) +/// in that domain. This serves as HZ when computing the C proof element. +pub struct R1CStoQAPCircom; + +impl QAPCalculator for R1CStoQAPCircom { + #[allow(clippy::type_complexity)] + fn instance_map_with_evaluation>( + cs: ConstraintSystemRef, + t: &F, + ) -> Result<(Vec, Vec, Vec, F, usize, usize), SynthesisError> { + R1CStoQAP::instance_map_with_evaluation::(cs, t) + } + + fn witness_map>( + prover: ConstraintSystemRef, + ) -> Result, SynthesisError> { + let matrices = prover.to_matrices().unwrap(); + let zero = F::zero(); + let num_inputs = prover.num_instance_variables(); + let num_constraints = prover.num_constraints(); + let cs = prover.borrow().unwrap(); + let prover = cs.deref(); + + let full_assignment = [ + prover.instance_assignment.as_slice(), + prover.witness_assignment.as_slice(), + ] + .concat(); + + let domain = + D::new(num_constraints + num_inputs).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + let domain_size = domain.size(); + + let mut a = vec![zero; domain_size]; + let mut b = vec![zero; domain_size]; + + cfg_iter_mut!(a[..num_constraints]) + .zip(cfg_iter_mut!(b[..num_constraints])) + .zip(cfg_iter!(&matrices.a)) + .zip(cfg_iter!(&matrices.b)) + .for_each(|(((a, b), at_i), bt_i)| { + *a = evaluate_constraint(&at_i, &full_assignment); + *b = evaluate_constraint(&bt_i, &full_assignment); + }); + + { + let start = num_constraints; + let end = start + num_inputs; + a[start..end].clone_from_slice(&full_assignment[..num_inputs]); + } + + domain.ifft_in_place(&mut a); + domain.ifft_in_place(&mut b); + + let root_of_unity = { + let domain_size_double = 2 * domain_size; + let domain_double = + D::new(domain_size_double).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + domain_double.element(1) + }; + D::distribute_powers_and_mul_by_const(&mut a, root_of_unity, F::one()); + D::distribute_powers_and_mul_by_const(&mut b, root_of_unity, F::one()); + + domain.fft_in_place(&mut a); + domain.fft_in_place(&mut b); + + let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b); + drop(a); + drop(b); + + let mut c = vec![zero; domain_size]; + cfg_iter_mut!(c[..prover.num_constraints]) + .enumerate() + .for_each(|(i, c)| { + *c = evaluate_constraint(&matrices.c[i], &full_assignment); + }); + + domain.ifft_in_place(&mut c); + D::distribute_powers_and_mul_by_const(&mut c, root_of_unity, F::one()); + domain.fft_in_place(&mut c); + + cfg_iter_mut!(ab) + .zip(c) + .for_each(|(ab_i, c_i)| *ab_i -= &c_i); + + Ok(ab) + } + + fn h_query_scalars>( + max_power: usize, + t: F, + _: F, + delta_inverse: F, + ) -> Result, SynthesisError> { + // the usual H query has domain-1 powers. Z has domain powers. So HZ has 2*domain-1 powers. + let mut scalars = cfg_into_iter!(0..2 * max_power + 1) + .map(|i| delta_inverse * t.pow([i as u64])) + .collect::>(); + let domain_size = scalars.len(); + let domain = D::new(domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?; + // generate the lagrange coefficients + domain.ifft_in_place(&mut scalars); + Ok(cfg_into_iter!(scalars).skip(1).step_by(2).collect()) + } +} diff --git a/src/circom_wasm/circom.rs b/src/circom_wasm/circom.rs new file mode 100644 index 0000000..a637009 --- /dev/null +++ b/src/circom_wasm/circom.rs @@ -0,0 +1,79 @@ +use color_eyre::Result; +use wasmer::{Function, Instance, Value}; + +#[derive(Clone, Debug)] +pub struct CircomInstance(Instance); + +// binds to the circom functions +impl CircomInstance { + pub fn new(instance: Instance) -> Self { + CircomInstance(instance) + } + + pub fn init(&self, sanity_check: bool) -> Result<()> { + let func = self.func("init"); + func.call(&[Value::I32(sanity_check as i32)])?; + Ok(()) + } + + pub fn get_fr_len(&self) -> Result { + self.get_i32("getFrLen") + } + + pub fn get_ptr_raw_prime(&self) -> Result { + self.get_i32("getPRawPrime") + } + + pub fn get_n_vars(&self) -> Result { + self.get_i32("getNVars") + } + + pub fn get_ptr_witness_buffer(&self) -> Result { + self.get_i32("getWitnessBuffer") + } + + pub fn get_ptr_witness(&self, w: i32) -> Result { + let func = self.func("getPWitness"); + let res = func.call(&[w.into()])?; + + Ok(res[0].unwrap_i32()) + } + + pub fn get_signal_offset32( + &self, + p_sig_offset: u32, + component: u32, + hash_msb: u32, + hash_lsb: u32, + ) -> Result<()> { + let func = self.func("getSignalOffset32"); + func.call(&[ + p_sig_offset.into(), + component.into(), + hash_msb.into(), + hash_lsb.into(), + ])?; + + Ok(()) + } + + pub fn set_signal(&self, c_idx: i32, component: i32, signal: i32, p_val: i32) -> Result<()> { + let func = self.func("setSignal"); + func.call(&[c_idx.into(), component.into(), signal.into(), p_val.into()])?; + + Ok(()) + } + + fn get_i32(&self, name: &str) -> Result { + let func = self.func(name); + let result = func.call(&[])?; + Ok(result[0].unwrap_i32()) + } + + fn func(&self, name: &str) -> &Function { + self.0 + .exports + .get_function(name) + .unwrap_or_else(|_| panic!("function {} not found", name)) + } +} diff --git a/src/circom_wasm/memory.rs b/src/circom_wasm/memory.rs new file mode 100644 index 0000000..1ef7fcb --- /dev/null +++ b/src/circom_wasm/memory.rs @@ -0,0 +1,271 @@ +//! Safe-ish interface for reading and writing specific types to the WASM runtime's memory +use num_traits::ToPrimitive; +use wasmer::{Memory, MemoryView}; + +// TODO: Decide whether we want Ark here or if it should use a generic BigInt package +use ark_bn254::FrParameters; +use ark_ff::{BigInteger, BigInteger256, FpParameters, FromBytes, Zero}; + +use num_bigint::{BigInt, BigUint}; + +use color_eyre::Result; +use std::str::FromStr; +use std::{convert::TryFrom, ops::Deref}; + +#[derive(Clone, Debug)] +pub struct SafeMemory { + pub memory: Memory, + pub prime: BigInt, + + short_max: BigInt, + short_min: BigInt, + r_inv: BigInt, + n32: usize, +} + +impl Deref for SafeMemory { + type Target = Memory; + + fn deref(&self) -> &Self::Target { + &self.memory + } +} + +impl SafeMemory { + /// Creates a new SafeMemory + pub fn new(memory: Memory, n32: usize, prime: BigInt) -> Self { + // TODO: Figure out a better way to calculate these + let short_max = BigInt::from(0x8000_0000u64); + let short_min = BigInt::from_biguint( + num_bigint::Sign::NoSign, + BigUint::try_from(FrParameters::MODULUS).unwrap(), + ) - &short_max; + let r_inv = BigInt::from_str( + "9915499612839321149637521777990102151350674507940716049588462388200839649614", + ) + .unwrap(); + + Self { + memory, + prime, + + short_max, + short_min, + r_inv, + n32, + } + } + + /// Gets an immutable view to the memory in 32 byte chunks + pub fn view(&self) -> MemoryView { + self.memory.view() + } + + /// Returns the next free position in the memory + pub fn free_pos(&self) -> u32 { + self.view()[0].get() + } + + /// Sets the next free position in the memory + pub fn set_free_pos(&mut self, ptr: u32) { + self.write_u32(0, ptr); + } + + /// Allocates a U32 in memory + pub fn alloc_u32(&mut self) -> u32 { + let p = self.free_pos(); + self.set_free_pos(p + 8); + p + } + + /// Writes a u32 to the specified memory offset + pub fn write_u32(&mut self, ptr: usize, num: u32) { + let buf = unsafe { self.memory.data_unchecked_mut() }; + buf[ptr..ptr + std::mem::size_of::()].copy_from_slice(&num.to_le_bytes()); + } + + /// Reads a u32 from the specified memory offset + pub fn read_u32(&self, ptr: usize) -> u32 { + let buf = unsafe { self.memory.data_unchecked() }; + + let mut bytes = [0; 4]; + bytes.copy_from_slice(&buf[ptr..ptr + std::mem::size_of::()]); + + u32::from_le_bytes(bytes) + } + + /// Allocates `self.n32 * 4 + 8` bytes in the memory + pub fn alloc_fr(&mut self) -> u32 { + let p = self.free_pos(); + self.set_free_pos(p + self.n32 as u32 * 4 + 8); + p + } + + /// Writes a Field Element to memory at the specified offset, truncating + /// to smaller u32 types if needed and adjusting the sign via 2s complement + pub fn write_fr(&mut self, ptr: usize, fr: &BigInt) -> Result<()> { + if fr < &self.short_max && fr > &self.short_min { + if fr >= &BigInt::zero() { + self.write_short_positive(ptr, fr)?; + } else { + self.write_short_negative(ptr, fr)?; + } + } else { + self.write_long_normal(ptr, fr)?; + } + + Ok(()) + } + + /// Reads a Field Element from the memory at the specified offset + pub fn read_fr(&self, ptr: usize) -> Result { + let view = self.memory.view::(); + + let res = if view[ptr + 4 + 3].get() & 0x80 != 0 { + let mut num = self.read_big(ptr + 8, self.n32)?; + if view[ptr + 4 + 3].get() & 0x40 != 0 { + num = (num * &self.r_inv) % &self.prime + } + num + } else if view[ptr + 3].get() & 0x40 != 0 { + let mut num = self.read_u32(ptr).into(); + // handle small negative + num -= BigInt::from(0x100000000i64); + num + } else { + self.read_u32(ptr).into() + }; + + Ok(res) + } + + fn write_short_positive(&mut self, ptr: usize, fr: &BigInt) -> Result<()> { + let num = fr.to_i32().expect("not a short positive"); + self.write_u32(ptr, num as u32); + self.write_u32(ptr + 4, 0); + Ok(()) + } + + fn write_short_negative(&mut self, ptr: usize, fr: &BigInt) -> Result<()> { + // 2s complement + let num = fr - &self.short_min; + let num = num - &self.short_max; + let num = num + BigInt::from(0x0001_0000_0000i64); + + let num = num + .to_u32() + .expect("could not cast as u32 (should never happen)"); + + self.write_u32(ptr, num); + self.write_u32(ptr + 4, 0); + Ok(()) + } + + fn write_long_normal(&mut self, ptr: usize, fr: &BigInt) -> Result<()> { + self.write_u32(ptr, 0); + self.write_u32(ptr + 4, i32::MIN as u32); // 0x80000000 + self.write_big(ptr + 8, fr)?; + Ok(()) + } + + fn write_big(&self, ptr: usize, num: &BigInt) -> Result<()> { + let buf = unsafe { self.memory.data_unchecked_mut() }; + + // TODO: How do we handle negative bignums? + let (_, num) = num.clone().into_parts(); + let num = BigInteger256::try_from(num).unwrap(); + + let bytes = num.to_bytes_le(); + let len = bytes.len(); + buf[ptr..ptr + len].copy_from_slice(&bytes); + + Ok(()) + } + + /// Reads `num_bytes * 32` from the specified memory offset in a Big Integer + pub fn read_big(&self, ptr: usize, num_bytes: usize) -> Result { + let buf = unsafe { self.memory.data_unchecked() }; + let buf = &buf[ptr..ptr + num_bytes * 32]; + + // TODO: Is there a better way to read big integers? + let big = BigInteger256::read(buf).unwrap(); + let big = BigUint::try_from(big).unwrap(); + Ok(big.into()) + } +} + +// TODO: Figure out how to read / write numbers > u32 +// circom-witness-calculator: Wasm + Memory -> expose BigInts so that they can be consumed by any proof system +// ark-circom: +// 1. can read zkey +// 2. can generate witness from inputs +// 3. can generate proofs +// 4. can serialize proofs in the desired format +#[cfg(test)] +mod tests { + use super::*; + use num_traits::ToPrimitive; + use std::str::FromStr; + use wasmer::{MemoryType, Store}; + + fn new() -> SafeMemory { + SafeMemory::new( + Memory::new(&Store::default(), MemoryType::new(1, None, false)).unwrap(), + 2, + BigInt::from_str( + "21888242871839275222246405745257275088548364400416034343698204186575808495617", + ) + .unwrap(), + ) + } + + #[test] + fn i32_bounds() { + let mem = new(); + let i32_max = i32::MAX as i64 + 1; + assert_eq!(mem.short_min.to_i64().unwrap(), -i32_max); + assert_eq!(mem.short_max.to_i64().unwrap(), i32_max); + } + + #[test] + fn read_write_32() { + let mut mem = new(); + let num = u32::MAX; + + let inp = mem.read_u32(0); + assert_eq!(inp, 0); + + mem.write_u32(0, num); + let inp = mem.read_u32(0); + assert_eq!(inp, num); + } + + #[test] + fn read_write_fr_small_positive() { + read_write_fr(BigInt::from(1_000_000)); + } + + #[test] + fn read_write_fr_small_negative() { + read_write_fr(BigInt::from(-1_000_000)); + } + + #[test] + fn read_write_fr_big_positive() { + read_write_fr(BigInt::from(500000000000i64)); + } + + // TODO: How should this be handled? + #[test] + #[ignore] + fn read_write_fr_big_negative() { + read_write_fr(BigInt::from_str("-500000000000").unwrap()) + } + + fn read_write_fr(num: BigInt) { + let mut mem = new(); + mem.write_fr(0, &num).unwrap(); + let res = mem.read_fr(0).unwrap(); + assert_eq!(res, num); + } +} diff --git a/src/circom_wasm/mod.rs b/src/circom_wasm/mod.rs new file mode 100644 index 0000000..9499f2a --- /dev/null +++ b/src/circom_wasm/mod.rs @@ -0,0 +1,21 @@ +mod wasm; +pub use wasm::WitnessCalculator; + +mod memory; +pub use memory::SafeMemory; + +mod circom; +pub use circom::CircomInstance; + +use fnv::FnvHasher; +use std::hash::Hasher; + +pub use num_bigint::BigInt; + +pub(crate) fn fnv(inp: &str) -> (u32, u32) { + let mut hasher = FnvHasher::default(); + hasher.write(inp.as_bytes()); + let h = hasher.finish(); + + ((h >> 32) as u32, h as u32) +} diff --git a/src/circom_wasm/wasm.rs b/src/circom_wasm/wasm.rs new file mode 100644 index 0000000..2c5ac6d --- /dev/null +++ b/src/circom_wasm/wasm.rs @@ -0,0 +1,274 @@ +use color_eyre::Result; +use num_bigint::BigInt; +use num_traits::Zero; +use std::cell::Cell; +use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, Store}; + +use super::{fnv, CircomInstance, SafeMemory}; + +#[derive(Clone, Debug)] +pub struct WitnessCalculator { + pub instance: CircomInstance, + pub memory: SafeMemory, + pub n64: i32, +} + +impl WitnessCalculator { + pub fn new(path: impl AsRef) -> Result { + let store = Store::default(); + let module = Module::from_file(&store, path)?; + + // Set up the memory + let memory = Memory::new(&store, MemoryType::new(2000, None, false)).unwrap(); + let import_object = imports! { + "env" => { + "memory" => memory.clone(), + }, + // Host function callbacks from the WASM + "runtime" => { + "error" => runtime::error(&store), + "logSetSignal" => runtime::log_signal(&store), + "logGetSignal" => runtime::log_signal(&store), + "logFinishComponent" => runtime::log_component(&store), + "logStartComponent" => runtime::log_component(&store), + "log" => runtime::log_component(&store), + } + }; + let instance = CircomInstance::new(Instance::new(&module, &import_object)?); + + let n32 = (instance.get_fr_len()? >> 2) - 2; + + let mut memory = SafeMemory::new(memory, n32 as usize, BigInt::zero()); + + let ptr = instance.get_ptr_raw_prime()?; + let prime = memory.read_big(ptr as usize, n32 as usize)?; + let n64 = ((prime.bits() - 1) / 64 + 1) as i32; + memory.prime = prime; + + Ok(WitnessCalculator { + instance, + memory, + n64, + }) + } + + pub fn calculate_witness)>>( + &mut self, + inputs: I, + sanity_check: bool, + ) -> Result> { + let old_mem_free_pos = self.memory.free_pos(); + + self.instance.init(sanity_check)?; + + 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 i32, p_fr as i32)?; + } + } + + 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) + } + + pub fn get_witness_buffer(&self) -> Result> { + let ptr = self.instance.get_ptr_witness_buffer()? as usize; + + let view = self.memory.memory.view::(); + + let len = self.instance.get_n_vars()? * self.n64 * 8; + let arr = view[ptr..ptr + len as usize] + .iter() + .map(Cell::get) + .collect::>(); + + Ok(arr) + } +} + +// callback hooks for debugging +mod runtime { + use super::*; + + pub fn error(store: &Store) -> Function { + #[allow(unused)] + #[allow(clippy::many_single_char_names)] + fn func(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) {} + Function::new_native(&store, func) + } + + pub fn log_signal(store: &Store) -> Function { + #[allow(unused)] + fn func(a: i32, b: i32) {} + Function::new_native(&store, func) + } + + pub fn log_component(store: &Store) -> Function { + #[allow(unused)] + fn func(a: i32) {} + Function::new_native(&store, func) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{collections::HashMap, path::PathBuf}; + + struct TestCase<'a> { + circuit_path: &'a str, + inputs_path: &'a str, + n_vars: u32, + n64: u32, + witness: &'a [&'a str], + } + + pub fn root_path(p: &str) -> String { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push(p); + path.to_string_lossy().to_string() + } + + #[test] + fn multiplier_1() { + run_test(TestCase { + circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(), + n_vars: 4, + n64: 4, + witness: &["1", "33", "3", "11"], + }); + } + + #[test] + fn multiplier_2() { + run_test(TestCase { + circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(), + n_vars: 4, + n64: 4, + witness: &[ + "1", + "21888242871839275222246405745257275088548364400416034343698204186575672693159", + "21888242871839275222246405745257275088548364400416034343698204186575796149939", + "11", + ], + }); + } + + #[test] + fn multiplier_3() { + run_test(TestCase { + circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(), + inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(), + n_vars: 4, + n64: 4, + witness: &[ + "1", + "21888242871839275222246405745257275088548364400416034343698204186575808493616", + "10944121435919637611123202872628637544274182200208017171849102093287904246808", + "2", + ], + }); + } + + #[test] + fn safe_multipler() { + let witness = + std::fs::read_to_string(&root_path("test-vectors/safe-circuit-witness.json")).unwrap(); + let witness: Vec = serde_json::from_str(&witness).unwrap(); + let witness = &witness.iter().map(|x| x.as_ref()).collect::>(); + run_test(TestCase { + circuit_path: root_path("test-vectors/circuit2.wasm").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 = serde_json::from_str(&witness).unwrap(); + let witness = &witness.iter().map(|x| x.as_ref()).collect::>(); + + run_test(TestCase { + circuit_path: root_path("test-vectors/smtverifier10.wasm").as_str(), + inputs_path: root_path("test-vectors/smtverifier10-input.json").as_str(), + n_vars: 4794, + n64: 4, + witness, + }); + } + + use serde_json::Value; + use std::str::FromStr; + + fn value_to_bigint(v: Value) -> BigInt { + match v { + Value::String(inner) => BigInt::from_str(&inner).unwrap(), + Value::Number(inner) => BigInt::from(inner.as_u64().expect("not a u32")), + _ => panic!("unsupported type"), + } + } + + fn run_test(case: TestCase) { + let mut wtns = WitnessCalculator::new(case.circuit_path).unwrap(); + assert_eq!( + wtns.memory.prime.to_str_radix(16), + "30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase() + ); + assert_eq!(wtns.instance.get_n_vars().unwrap() as u32, case.n_vars); + assert_eq!(wtns.n64 as u32, case.n64); + + let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap(); + let inputs: std::collections::HashMap = + serde_json::from_str(&inputs_str).unwrap(); + + let inputs = inputs + .iter() + .map(|(key, value)| { + let res = match value { + Value::String(inner) => { + vec![BigInt::from_str(inner).unwrap()] + } + Value::Number(inner) => { + vec![BigInt::from(inner.as_u64().expect("not a u32"))] + } + Value::Array(inner) => inner.iter().cloned().map(value_to_bigint).collect(), + _ => panic!(), + }; + + (key.clone(), res) + }) + .collect::>(); + + let res = wtns.calculate_witness(inputs, false).unwrap(); + for (r, w) in res.iter().zip(case.witness) { + assert_eq!(r, &BigInt::from_str(w).unwrap()); + } + } +} diff --git a/src/circuit/builder.rs b/src/circuit/builder.rs new file mode 100644 index 0000000..deca738 --- /dev/null +++ b/src/circuit/builder.rs @@ -0,0 +1,95 @@ +use ark_ec::PairingEngine; +use std::{fs::File, path::Path}; + +use super::{CircomCircuit, R1CS}; + +use num_bigint::BigInt; +use std::collections::HashMap; + +use crate::{circuit::R1CSFile, WitnessCalculator}; +use color_eyre::Result; + +pub struct CircomBuilder { + pub cfg: CircuitConfig, + pub inputs: HashMap>, +} + +// Add utils for creating this from files / directly from bytes +pub struct CircuitConfig { + pub r1cs: R1CS, + pub wtns: WitnessCalculator, + pub sanity_check: bool, +} + +impl CircuitConfig { + pub fn new(wtns: impl AsRef, r1cs: impl AsRef) -> Result { + let wtns = WitnessCalculator::new(wtns).unwrap(); + let reader = File::open(r1cs)?; + let r1cs = R1CSFile::new(reader)?.into(); + Ok(Self { + wtns, + r1cs, + sanity_check: false, + }) + } +} + +impl CircomBuilder { + /// Instantiates a new builder using the provided WitnessGenerator and R1CS files + /// for your circuit + pub fn new(cfg: CircuitConfig) -> Self { + Self { + cfg, + inputs: HashMap::new(), + } + } + + /// Pushes a Circom input at the specified name. + pub fn push_input>(&mut self, name: impl ToString, val: T) { + let values = self.inputs.entry(name.to_string()).or_insert_with(Vec::new); + values.push(val.into()); + } + + /// Generates an empty circom circuit with no witness set, to be used for + /// generation of the trusted setup parameters + pub fn setup(&self) -> CircomCircuit { + let mut circom = CircomCircuit { + r1cs: self.cfg.r1cs.clone(), + witness: None, + }; + + // Disable the wire mapping + circom.r1cs.wire_mapping = None; + + circom + } + + /// Creates the circuit populated with the witness corresponding to the previously + /// provided inputs + pub fn build(mut self) -> Result> { + let mut circom = self.setup(); + + // calculate the witness + let witness = self + .cfg + .wtns + .calculate_witness(self.inputs, self.cfg.sanity_check)?; + + // convert it to field elements + let witness = witness + .into_iter() + .map(|w| E::Fr::from(w.to_biguint().unwrap())) + .collect::>(); + circom.witness = Some(witness); + + // sanity check + debug_assert!({ + use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; + let cs = ConstraintSystem::::new_ref(); + circom.clone().generate_constraints(cs.clone()).unwrap(); + cs.is_satisfied().unwrap() + }); + + Ok(circom) + } +} diff --git a/src/circuit/circom.rs b/src/circuit/circom.rs new file mode 100644 index 0000000..d79dbde --- /dev/null +++ b/src/circuit/circom.rs @@ -0,0 +1,107 @@ +use ark_ec::PairingEngine; +use ark_relations::r1cs::{ + ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable, +}; + +use crate::circuit::R1CS; + +use color_eyre::Result; + +#[derive(Clone, Debug)] +pub struct CircomCircuit { + pub r1cs: R1CS, + pub witness: Option>, +} + +impl<'a, E: PairingEngine> CircomCircuit { + pub fn get_public_inputs(&self) -> Option> { + match &self.witness { + None => None, + Some(w) => match &self.r1cs.wire_mapping { + None => Some(w[1..self.r1cs.num_inputs].to_vec()), + Some(m) => Some(m[1..self.r1cs.num_inputs].iter().map(|i| w[*i]).collect()), + }, + } + } +} + +impl ConstraintSynthesizer for CircomCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let witness = &self.witness; + let wire_mapping = &self.r1cs.wire_mapping; + + // Start from 1 because Arkworks implicitly allocates One for the first input + for i in 1..self.r1cs.num_inputs { + cs.new_input_variable(|| { + Ok(match witness { + None => E::Fr::from(1u32), + Some(w) => match wire_mapping { + Some(m) => w[m[i]], + None => w[i], + }, + }) + })?; + } + + for i in 0..self.r1cs.num_aux { + cs.new_witness_variable(|| { + Ok(match witness { + None => E::Fr::from(1u32), + Some(w) => match wire_mapping { + Some(m) => w[m[i + self.r1cs.num_inputs]], + None => w[i + self.r1cs.num_inputs], + }, + }) + })?; + } + + let make_index = |index| { + if index < self.r1cs.num_inputs { + Variable::Instance(index) + } else { + Variable::Witness(index - self.r1cs.num_inputs) + } + }; + let make_lc = |lc_data: &[(usize, E::Fr)]| { + lc_data.iter().fold( + LinearCombination::::zero(), + |lc: LinearCombination, (index, coeff)| lc + (*coeff, make_index(*index)), + ) + }; + + for constraint in &self.r1cs.constraints { + cs.enforce_constraint( + make_lc(&constraint.0), + make_lc(&constraint.1), + make_lc(&constraint.2), + )?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{CircomBuilder, CircuitConfig}; + use ark_bn254::{Bn254, Fr}; + use ark_relations::r1cs::ConstraintSystem; + + #[test] + fn satisfied() { + let cfg = CircuitConfig::::new( + "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit.r1cs", + ) + .unwrap(); + let mut builder = CircomBuilder::new(cfg); + builder.push_input("a", 3); + builder.push_input("b", 11); + + let circom = builder.build().unwrap(); + let cs = ConstraintSystem::::new_ref(); + circom.generate_constraints(cs.clone()).unwrap(); + assert!(cs.is_satisfied().unwrap()); + } +} diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs new file mode 100644 index 0000000..ddd47ee --- /dev/null +++ b/src/circuit/mod.rs @@ -0,0 +1,13 @@ +use ark_ec::PairingEngine; + +pub mod r1cs_reader; +pub use r1cs_reader::{R1CSFile, R1CS}; + +mod circom; +pub use circom::CircomCircuit; + +mod builder; +pub use builder::{CircomBuilder, CircuitConfig}; + +pub type Constraints = (ConstraintVec, ConstraintVec, ConstraintVec); +pub type ConstraintVec = Vec<(usize, ::Fr)>; diff --git a/src/circuit/r1cs_reader.rs b/src/circuit/r1cs_reader.rs new file mode 100644 index 0000000..2c1b73d --- /dev/null +++ b/src/circuit/r1cs_reader.rs @@ -0,0 +1,270 @@ +//! R1CS circom file reader +//! Copied from https://github.com/poma/zkutil +use byteorder::{LittleEndian, ReadBytesExt}; +use std::io::{Error, ErrorKind, Result}; + +use ark_ec::PairingEngine; +use ark_ff::FromBytes; +use ark_std::io::Read; + +use crate::circuit::{ConstraintVec, Constraints}; + +#[derive(Clone, Debug)] +pub struct R1CS { + pub num_inputs: usize, + pub num_aux: usize, + pub num_variables: usize, + pub constraints: Vec>, + pub wire_mapping: Option>, +} + +impl From> for R1CS { + fn from(file: R1CSFile) -> Self { + 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_aux = num_variables - num_inputs; + R1CS { + num_aux, + num_inputs, + num_variables, + constraints: file.constraints, + wire_mapping: Some(file.wire_mapping.iter().map(|e| *e as usize).collect()), + } + } +} + +pub struct R1CSFile { + pub version: u32, + pub header: Header, + pub constraints: Vec>, + pub wire_mapping: Vec, +} + +impl R1CSFile { + pub fn new(mut reader: R) -> Result> { + let mut magic = [0u8; 4]; + reader.read_exact(&mut magic)?; + if magic != [0x72, 0x31, 0x63, 0x73] { + // magic = "r1cs" + return Err(Error::new(ErrorKind::InvalidData, "Invalid magic number")); + } + + let version = reader.read_u32::()?; + if version != 1 { + return Err(Error::new(ErrorKind::InvalidData, "Unsupported version")); + } + + let _num_sections = reader.read_u32::()?; + + // todo: rewrite this to support different section order and unknown sections + // todo: handle sec_size correctly + let _sec_type = reader.read_u32::()?; + let sec_size = reader.read_u64::()?; + + let header = Header::new(&mut reader, sec_size)?; + let _sec_type = reader.read_u32::()?; + let _sec_size = reader.read_u64::()?; + let constraints = read_constraints::<&mut R, E>(&mut reader, &header)?; + + let _sec_type = reader.read_u32::()?; + let sec_size = reader.read_u64::()?; + let wire_mapping = read_map(&mut reader, sec_size, &header)?; + + Ok(R1CSFile { + version, + header, + constraints, + wire_mapping, + }) + } +} + +pub struct Header { + pub field_size: u32, + pub prime_size: Vec, + pub n_wires: u32, + pub n_pub_out: u32, + pub n_pub_in: u32, + pub n_prv_in: u32, + pub n_labels: u64, + pub n_constraints: u32, +} + +impl Header { + fn new(mut reader: R, size: u64) -> Result
{ + let field_size = reader.read_u32::()?; + if field_size != 32 { + return Err(Error::new( + ErrorKind::InvalidData, + "This parser only supports 32-byte fields", + )); + } + + if size != 32 + field_size as u64 { + return Err(Error::new( + ErrorKind::InvalidData, + "Invalid header section size", + )); + } + + let mut prime_size = vec![0u8; field_size as usize]; + reader.read_exact(&mut prime_size)?; + + if prime_size + != hex::decode("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") + .unwrap() + { + return Err(Error::new( + ErrorKind::InvalidData, + "This parser only supports bn256", + )); + } + + Ok(Header { + field_size, + prime_size, + n_wires: reader.read_u32::()?, + n_pub_out: reader.read_u32::()?, + n_pub_in: reader.read_u32::()?, + n_prv_in: reader.read_u32::()?, + n_labels: reader.read_u64::()?, + n_constraints: reader.read_u32::()?, + }) + } +} + +fn read_constraint_vec(mut reader: R) -> Result> { + let n_vec = reader.read_u32::()? as usize; + let mut vec = Vec::with_capacity(n_vec); + for _ in 0..n_vec { + vec.push(( + reader.read_u32::()? as usize, + E::Fr::read(&mut reader)?, + )); + } + Ok(vec) +} + +fn read_constraints( + mut reader: R, + header: &Header, +) -> Result>> { + // todo check section size + let mut vec = Vec::with_capacity(header.n_constraints as usize); + for _ in 0..header.n_constraints { + vec.push(( + read_constraint_vec::<&mut R, E>(&mut reader)?, + read_constraint_vec::<&mut R, E>(&mut reader)?, + read_constraint_vec::<&mut R, E>(&mut reader)?, + )); + } + Ok(vec) +} + +fn read_map(mut reader: R, size: u64, header: &Header) -> Result> { + if size != header.n_wires as u64 * 8 { + return Err(Error::new( + ErrorKind::InvalidData, + "Invalid map section size", + )); + } + let mut vec = Vec::with_capacity(header.n_wires as usize); + for _ in 0..header.n_wires { + vec.push(reader.read_u64::()?); + } + if vec[0] != 0 { + return Err(Error::new( + ErrorKind::InvalidData, + "Wire 0 should always be mapped to 0", + )); + } + Ok(vec) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bn254::{Bn254, Fr}; + + #[test] + fn sample() { + let data = hex_literal::hex!( + " + 72316373 + 01000000 + 03000000 + 01000000 40000000 00000000 + 20000000 + 010000f0 93f5e143 9170b979 48e83328 5d588181 b64550b8 29a031e1 724e6430 + 07000000 + 01000000 + 02000000 + 03000000 + e8030000 00000000 + 03000000 + 02000000 88020000 00000000 + 02000000 + 05000000 03000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 06000000 08000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 + 00000000 02000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 14000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 0C000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 + 00000000 05000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 07000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 + 01000000 04000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 04000000 08000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 05000000 03000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 + 03000000 2C000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 06000000 06000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 00000000 + 01000000 + 06000000 04000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 + 00000000 06000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 0B000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 05000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 01000000 + 06000000 58020000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 38000000 00000000 + 00000000 00000000 + 03000000 00000000 + 0a000000 00000000 + 0b000000 00000000 + 0c000000 00000000 + 0f000000 00000000 + 44010000 00000000 + " + ); + + let file = R1CSFile::::new(&data[..]).unwrap(); + assert_eq!(file.version, 1); + + assert_eq!(file.header.field_size, 32); + assert_eq!( + file.header.prime_size, + hex::decode("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") + .unwrap(), + ); + assert_eq!(file.header.n_wires, 7); + assert_eq!(file.header.n_pub_out, 1); + assert_eq!(file.header.n_pub_in, 2); + assert_eq!(file.header.n_prv_in, 3); + assert_eq!(file.header.n_labels, 0x03e8); + assert_eq!(file.header.n_constraints, 3); + + assert_eq!(file.constraints.len(), 3); + assert_eq!(file.constraints[0].0.len(), 2); + assert_eq!(file.constraints[0].0[0].0, 5); + assert_eq!(file.constraints[0].0[0].1, Fr::from(3)); + assert_eq!(file.constraints[2].1[0].0, 0); + assert_eq!(file.constraints[2].1[0].1, Fr::from(6)); + assert_eq!(file.constraints[1].2.len(), 0); + + assert_eq!(file.wire_mapping.len(), 7); + assert_eq!(file.wire_mapping[1], 3); + } +} diff --git a/src/ethereum.rs b/src/ethereum.rs new file mode 100644 index 0000000..e6f28a0 --- /dev/null +++ b/src/ethereum.rs @@ -0,0 +1,271 @@ +//! Helpers for converting Arkworks types to U256-tuples as expected by the +//! Solidity Groth16 Verifier smart contracts +use ark_ff::{BigInteger, FromBytes, PrimeField}; +use ethers::types::U256; + +use ark_bn254::{Bn254, Fq2, Fr, G1Affine, G2Affine}; + +pub struct Inputs(pub Vec); + +impl From<&[Fr]> for Inputs { + fn from(src: &[Fr]) -> Self { + let els = src.iter().map(|point| point_to_u256(*point)).collect(); + + Self(els) + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct G1 { + pub x: U256, + pub y: U256, +} + +impl From for G1Affine { + fn from(src: G1) -> G1Affine { + let x = u256_to_point(src.x); + let y = u256_to_point(src.y); + G1Affine::new(x, y, false) + } +} + +type G1Tup = (U256, U256); + +impl G1 { + pub fn as_tuple(&self) -> (U256, U256) { + (self.x, self.y) + } +} + +impl From<&G1Affine> for G1 { + fn from(p: &G1Affine) -> Self { + Self { + x: point_to_u256(p.x), + y: point_to_u256(p.y), + } + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct G2 { + pub x: [U256; 2], + pub y: [U256; 2], +} + +impl From for G2Affine { + fn from(src: G2) -> G2Affine { + let c0 = u256_to_point(src.x[0]); + let c1 = u256_to_point(src.x[1]); + let x = Fq2::new(c0, c1); + + let c0 = u256_to_point(src.y[0]); + let c1 = u256_to_point(src.y[1]); + let y = Fq2::new(c0, c1); + + G2Affine::new(x, y, false) + } +} + +type G2Tup = ([U256; 2], [U256; 2]); + +impl G2 { + // NB: Serialize the c1 limb first. + pub fn as_tuple(&self) -> G2Tup { + ([self.x[1], self.x[0]], [self.y[1], self.y[0]]) + } +} + +impl From<&G2Affine> for G2 { + fn from(p: &G2Affine) -> Self { + Self { + x: [point_to_u256(p.x.c0), point_to_u256(p.x.c1)], + y: [point_to_u256(p.y.c0), point_to_u256(p.y.c1)], + } + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Proof { + a: G1, + b: G2, + c: G1, +} + +impl Proof { + pub fn as_tuple(&self) -> (G1Tup, G2Tup, G1Tup) { + (self.a.as_tuple(), self.b.as_tuple(), self.c.as_tuple()) + } +} + +impl From> for Proof { + fn from(proof: ark_groth16::Proof) -> Self { + Self { + a: G1::from(&proof.a), + b: G2::from(&proof.b), + c: G1::from(&proof.c), + } + } +} + +impl From for ark_groth16::Proof { + fn from(src: Proof) -> ark_groth16::Proof { + ark_groth16::Proof { + a: src.a.into(), + b: src.b.into(), + c: src.c.into(), + } + } +} + +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct VerifyingKey { + pub alpha1: G1, + pub beta2: G2, + pub gamma2: G2, + pub delta2: G2, + pub ic: Vec, +} + +impl VerifyingKey { + pub fn as_tuple(&self) -> (G1Tup, G2Tup, G2Tup, G2Tup, Vec) { + ( + self.alpha1.as_tuple(), + self.beta2.as_tuple(), + self.gamma2.as_tuple(), + self.delta2.as_tuple(), + self.ic.iter().map(|i| i.as_tuple()).collect(), + ) + } +} + +impl From> for VerifyingKey { + fn from(vk: ark_groth16::VerifyingKey) -> Self { + Self { + alpha1: G1::from(&vk.alpha_g1), + beta2: G2::from(&vk.beta_g2), + gamma2: G2::from(&vk.gamma_g2), + delta2: G2::from(&vk.delta_g2), + ic: vk.gamma_abc_g1.iter().map(G1::from).collect(), + } + } +} + +impl From for ark_groth16::VerifyingKey { + fn from(src: VerifyingKey) -> ark_groth16::VerifyingKey { + ark_groth16::VerifyingKey { + alpha_g1: src.alpha1.into(), + beta_g2: src.beta2.into(), + gamma_g2: src.gamma2.into(), + delta_g2: src.delta2.into(), + gamma_abc_g1: src.ic.into_iter().map(Into::into).collect(), + } + } +} + +// Helper for converting a PrimeField to its U256 representation for Ethereum compatibility +fn u256_to_point(point: U256) -> F { + let mut buf = [0; 32]; + point.to_little_endian(&mut buf); + let bigint = F::BigInt::read(&buf[..]).expect("always works"); + F::from_repr(bigint).expect("alwasy works") +} + +// Helper for converting a PrimeField to its U256 representation for Ethereum compatibility +// (U256 reads data as big endian) +fn point_to_u256(point: F) -> U256 { + let point = point.into_repr(); + let point_bytes = point.to_bytes_be(); + U256::from(&point_bytes[..]) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bn254::Fq; + + fn fq() -> Fq { + Fq::from(2) + } + + fn fq2() -> Fq2 { + Fq2::from(2) + } + + fn fr() -> Fr { + Fr::from(2) + } + + fn g1() -> G1Affine { + G1Affine::new(fq(), fq(), false) + } + + fn g2() -> G2Affine { + G2Affine::new(fq2(), fq2(), false) + } + + #[test] + fn convert_fq() { + let el = fq(); + let el2 = point_to_u256(el); + let el3: Fq = u256_to_point(el2); + let el4 = point_to_u256(el3); + assert_eq!(el, el3); + assert_eq!(el2, el4); + } + + #[test] + fn convert_fr() { + let el = fr(); + let el2 = point_to_u256(el); + let el3: Fr = u256_to_point(el2); + let el4 = point_to_u256(el3); + assert_eq!(el, el3); + assert_eq!(el2, el4); + } + + #[test] + fn convert_g1() { + let el = g1(); + let el2 = G1::from(&el); + let el3: G1Affine = el2.into(); + let el4 = G1::from(&el3); + assert_eq!(el, el3); + assert_eq!(el2, el4); + } + + #[test] + fn convert_g2() { + let el = g2(); + let el2 = G2::from(&el); + let el3: G2Affine = el2.into(); + let el4 = G2::from(&el3); + assert_eq!(el, el3); + assert_eq!(el2, el4); + } + + #[test] + fn convert_vk() { + let vk = ark_groth16::VerifyingKey:: { + alpha_g1: g1(), + beta_g2: g2(), + gamma_g2: g2(), + delta_g2: g2(), + gamma_abc_g1: vec![g1(), g1(), g1()], + }; + let vk_ethers = VerifyingKey::from(vk.clone()); + let ark_vk: ark_groth16::VerifyingKey = vk_ethers.into(); + assert_eq!(ark_vk, vk); + } + + #[test] + fn convert_proof() { + let p = ark_groth16::Proof:: { + a: g1(), + b: g2(), + c: g1(), + }; + let p2 = Proof::from(p.clone()); + let p3 = ark_groth16::Proof::from(p2); + assert_eq!(p, p3); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c433988 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,79 @@ +//! Arkworks - Circom Compatibility layer +//! +//! Given a Circom WASM-compiled witness.wasm, it can read it and calculate the corresponding +//! +//! ## WASM Witness Generator +//! +//! ## Types +//! * ZKey +//! * WTNS +//! * R1CS +//! * WASM +//! * Sys? +//! +//! Inputs: +//! * circuit.wasm +//! * input.json +//! +//! Outputs: +//! * witness.wtns +//! +//! Given a circuit WASM and an input.json calculates the corresponding witness +//! +//! ## Proof calculator +//! +//! Inputs: +//! * witness.wtns / witness.json +//! * circuit.zkey +//! +//! Given a witness (and r1cs?) synthesizes the circom circuit +//! And then feeds it to the arkworks groth16 prover +//! +//! Outputs: +//! * public.json +//! * proof.json +//! +//! ## Smart Contract connector class +//! +//! Given an Arkworks proof, it's able to translate it to the Circom-verifier +//! expected arguments +//! +//! (No Dark Forest specific modifications included, these are part of df-snark) +//! +//! ## Binary +//! +//! CLIs for each of the above + logging to stdout +//! +//! witness for the specified inputs +//! +//! ## Commands +//! +//! Compile a circuit: +//! `circom circuit.circom --r1cs --wasm --sym` +//! +//! Phase2 over circuit + PoT +//! `snarkjs zkey new circuit.r1cs powersOfTau28_hez_final_10.ptau circuit_0000.zkey` +//! `snarkjs zkey contribute circuit_0000.zkey circuit_final.zkey` +//! `snarkjs zkey export verificationkey circuit_final.zkey verification_key.json` +//! +//! Witness calculation from inputs: +//! `snarkjs wtns calculate circuit.wasm input.json witness.wtns` +//! `snarkjs wtns export json witness.wtns witness.json` +//! +//! Groth16 proof calculation: +//! `snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json` +//! +//! Groth16 Proof verification: +//! `snarkjs groth16 verify verification_key.json public.json proof.json` + +mod circom_wasm; +pub use circom_wasm::WitnessCalculator; + +pub mod circuit; +pub use circuit::{CircomBuilder, CircomCircuit, CircuitConfig}; + +pub mod ethereum; + +pub mod zkey; + +pub mod circom_qap; diff --git a/src/zkey.rs b/src/zkey.rs new file mode 100644 index 0000000..d1a9679 --- /dev/null +++ b/src/zkey.rs @@ -0,0 +1,835 @@ +//! ZKey +//! +//! Each ZKey file is broken into sections: +//! Header(1) +//! Prover Type 1 Groth +//! HeaderGroth(2) +//! n8q +//! q +//! n8r +//! r +//! NVars +//! NPub +//! DomainSize (multiple of 2 +//! alpha1 +//! beta1 +//! delta1 +//! beta2 +//! gamma2 +//! delta2 +//! IC(3) +//! Coefs(4) +//! PointsA(5) +//! PointsB1(6) +//! PointsB2(7) +//! PointsC(8) +//! PointsH(9) +//! Contributions(10) +use ark_ff::{BigInteger256, FromBytes}; +use ark_serialize::{CanonicalDeserialize, SerializationError}; +use ark_std::log2; +use byteorder::{LittleEndian, ReadBytesExt}; + +use std::{ + collections::HashMap, + io::{Cursor, Read, Result as IoResult}, +}; + +use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G2Affine}; +use ark_groth16::{ProvingKey, VerifyingKey}; +use ark_serialize::CanonicalSerialize; +use num_traits::Zero; + +#[derive(Clone, Debug)] +pub struct Section { + position: u64, + size: usize, +} + +#[derive(Debug)] +pub struct BinFile<'a> { + ftype: String, + version: u32, + sections: HashMap>, + reader: &'a mut Cursor<&'a [u8]>, +} + +impl<'a> BinFile<'a> { + pub fn new(reader: &'a mut Cursor<&'a [u8]>) -> IoResult { + let mut magic = [0u8; 4]; + reader.read_exact(&mut magic)?; + + let version = reader.read_u32::()?; + + let num_sections = reader.read_u32::()?; + + let mut sections = HashMap::new(); + for _ in 0..num_sections { + let section_id = reader.read_u32::()?; + let section_length = reader.read_u64::()?; + + let section = sections.entry(section_id).or_insert_with(Vec::new); + section.push(Section { + position: reader.position(), + size: section_length as usize, + }); + + reader.set_position(reader.position() + section_length); + } + + Ok(Self { + ftype: std::str::from_utf8(&magic[..]).unwrap().to_string(), + version, + sections, + reader, + }) + } + + pub fn proving_key(&mut self) -> IoResult> { + let header = self.groth_header()?; + let ic = self.ic(header.n_public)?; + + let a_query = self.a_query(header.n_vars)?; + let b_g1_query = self.b_g1_query(header.n_vars)?; + let b_g2_query = self.b_g2_query(header.n_vars)?; + let l_query = self.l_query(header.n_vars - header.n_public - 1)?; + let h_query = self.h_query(header.domain_size as usize)?; + + let vk = VerifyingKey:: { + alpha_g1: header.verifying_key.alpha_g1, + beta_g2: header.verifying_key.beta_g2, + gamma_g2: header.verifying_key.gamma_g2, + delta_g2: header.verifying_key.delta_g2, + gamma_abc_g1: ic, + }; + + let pk = ProvingKey:: { + vk, + beta_g1: header.verifying_key.beta_g1, + delta_g1: header.verifying_key.delta_g1, + a_query, + b_g1_query, + b_g2_query, + h_query, + l_query, + }; + + Ok(pk) + } + + fn get_section(&self, id: u32) -> Section { + self.sections.get(&id).unwrap()[0].clone() + } + + pub fn groth_header(&mut self) -> IoResult { + let section = self.get_section(2); + let header = HeaderGroth::new(&mut self.reader, §ion)?; + Ok(header) + } + + pub fn ic(&mut self, n_public: usize) -> IoResult> { + // the range is non-inclusive so we do +1 to get all inputs + self.g1_section(n_public + 1, 3) + } + + // Section 4 is the coefficients, we ignore it + + pub fn a_query(&mut self, n_vars: usize) -> IoResult> { + self.g1_section(n_vars, 5) + } + + pub fn b_g1_query(&mut self, n_vars: usize) -> IoResult> { + self.g1_section(n_vars, 6) + } + + pub fn b_g2_query(&mut self, n_vars: usize) -> IoResult> { + self.g2_section(n_vars, 7) + } + + pub fn l_query(&mut self, n_vars: usize) -> IoResult> { + self.g1_section(n_vars, 8) + } + + pub fn h_query(&mut self, n_vars: usize) -> IoResult> { + self.g1_section(n_vars, 9) + } + + fn g1_section(&mut self, num: usize, section_id: usize) -> IoResult> { + let section = self.get_section(section_id as u32); + deserialize_g1_vec( + &self.reader.get_ref()[section.position as usize..], + num as u32, + ) + } + + fn g2_section(&mut self, num: usize, section_id: usize) -> IoResult> { + let section = self.get_section(section_id as u32); + deserialize_g2_vec( + &self.reader.get_ref()[section.position as usize..], + num as u32, + ) + } +} + +#[derive(Default, Clone, Debug, CanonicalDeserialize)] +pub struct ZVerifyingKey { + pub alpha_g1: G1Affine, + pub beta_g1: G1Affine, + pub beta_g2: G2Affine, + pub gamma_g2: G2Affine, + pub delta_g1: G1Affine, + pub delta_g2: G2Affine, +} + +impl ZVerifyingKey { + fn new(reader: &mut R) -> IoResult { + let alpha_g1 = deserialize_g1(reader)?; + let beta_g1 = deserialize_g1(reader)?; + let beta_g2 = deserialize_g2(reader)?; + let gamma_g2 = deserialize_g2(reader)?; + let delta_g1 = deserialize_g1(reader)?; + let delta_g2 = deserialize_g2(reader)?; + + Ok(Self { + alpha_g1, + beta_g1, + beta_g2, + gamma_g2, + delta_g1, + delta_g2, + }) + } +} + +#[derive(Clone, Debug)] +pub struct HeaderGroth { + pub n8q: u32, + pub q: BigInteger256, + + pub n8r: u32, + pub r: BigInteger256, + + pub n_vars: usize, + pub n_public: usize, + + pub domain_size: u32, + pub power: u32, + + pub verifying_key: ZVerifyingKey, +} + +impl HeaderGroth { + pub fn new(reader: &mut Cursor<&[u8]>, section: &Section) -> IoResult { + reader.set_position(section.position); + Self::read(reader) + } + + fn read(mut reader: &mut R) -> IoResult { + // TODO: Impl From in Arkworks + let n8q: u32 = FromBytes::read(&mut reader)?; + // group order r of Bn254 + let q = BigInteger256::read(&mut reader)?; + + let n8r: u32 = FromBytes::read(&mut reader)?; + // Prime field modulus + let r = BigInteger256::read(&mut reader)?; + + let n_vars = u32::read(&mut reader)? as usize; + let n_public = u32::read(&mut reader)? as usize; + + let domain_size: u32 = FromBytes::read(&mut reader)?; + let power = log2(domain_size as usize); + + let verifying_key = ZVerifyingKey::new(&mut reader)?; + + Ok(Self { + n8q, + q, + n8r, + r, + n_vars, + n_public, + domain_size, + power, + verifying_key, + }) + } +} + +// skips the multiplication by R because Circom points are already in Montgomery form +fn deserialize_field(reader: &mut R) -> IoResult { + let bigint = BigInteger256::read(reader)?; + // if you use ark_ff::PrimeField::from_repr it multiplies by R + Ok(Fq::new(bigint)) +} + +fn deserialize_g1(reader: &mut R) -> IoResult { + let x = deserialize_field(reader)?; + let y = deserialize_field(reader)?; + let infinity = x.is_zero() && y.is_zero(); + Ok(G1Affine::new(x, y, infinity)) +} + +fn deserialize_g2(reader: &mut R) -> IoResult { + let c0 = deserialize_field(reader)?; + let c1 = deserialize_field(reader)?; + let f1 = Fq2::new(c0, c1); + + let c0 = deserialize_field(reader)?; + let c1 = deserialize_field(reader)?; + let f2 = Fq2::new(c0, c1); + let infinity = f1.is_zero() && f2.is_zero(); + Ok(G2Affine::new(f1, f2, infinity)) +} + +fn deserialize_g1_vec(buf: &[u8], n_vars: u32) -> IoResult> { + let size = G1Affine::zero().uncompressed_size(); + let mut v = vec![]; + for i in 0..n_vars as usize { + let el = deserialize_g1(&mut &buf[i * size..(i + 1) * size])?; + v.push(el); + } + Ok(v) +} + +fn deserialize_g2_vec(buf: &[u8], n_vars: u32) -> IoResult> { + let size = G2Affine::zero().uncompressed_size(); + let mut v = vec![]; + for i in 0..n_vars as usize { + let el = deserialize_g2(&mut &buf[i * size..(i + 1) * size])?; + v.push(el); + } + Ok(v) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bn254::{G1Projective, G2Projective}; + use memmap::*; + use num_bigint::BigUint; + use serde_json::Value; + use std::fs::File; + + use crate::{circom_qap::R1CStoQAPCircom, CircomBuilder, CircuitConfig}; + use ark_groth16::{create_random_proof_with_qap as prove, prepare_verifying_key, verify_proof}; + use ark_std::rand::thread_rng; + use num_traits::{One, Zero}; + use std::str::FromStr; + + use std::convert::TryFrom; + + fn fq_from_str(s: &str) -> Fq { + BigInteger256::try_from(BigUint::from_str(s).unwrap()) + .unwrap() + .into() + } + + // Circom snarkjs code: + // console.log(curve.G1.F.one) + fn fq_buf() -> Vec { + vec![ + 157, 13, 143, 197, 141, 67, 93, 211, 61, 11, 199, 245, 40, 235, 120, 10, 44, 70, 121, + 120, 111, 163, 110, 102, 47, 223, 7, 154, 193, 119, 10, 14, + ] + } + + // Circom snarkjs code: + // const buff = new Uint8Array(curve.G1.F.n8*2); + // curve.G1.toRprLEM(buff, 0, curve.G1.one); + // console.dir( buff, { 'maxArrayLength': null }) + fn g1_buf() -> Vec { + vec![ + 157, 13, 143, 197, 141, 67, 93, 211, 61, 11, 199, 245, 40, 235, 120, 10, 44, 70, 121, + 120, 111, 163, 110, 102, 47, 223, 7, 154, 193, 119, 10, 14, 58, 27, 30, 139, 27, 135, + 186, 166, 123, 22, 142, 235, 81, 214, 241, 20, 88, 140, 242, 240, 222, 70, 221, 204, + 94, 190, 15, 52, 131, 239, 20, 28, + ] + } + + // Circom snarkjs code: + // const buff = new Uint8Array(curve.G2.F.n8*2); + // curve.G2.toRprLEM(buff, 0, curve.G2.one); + // console.dir( buff, { 'maxArrayLength': null }) + fn g2_buf() -> Vec { + vec![ + 38, 32, 188, 2, 209, 181, 131, 142, 114, 1, 123, 73, 53, 25, 235, 220, 223, 26, 129, + 151, 71, 38, 184, 251, 59, 80, 150, 175, 65, 56, 87, 25, 64, 97, 76, 168, 125, 115, + 180, 175, 196, 216, 2, 88, 90, 221, 67, 96, 134, 47, 160, 82, 252, 80, 233, 9, 107, + 123, 234, 58, 131, 240, 254, 20, 246, 233, 107, 136, 157, 250, 157, 97, 120, 155, 158, + 245, 151, 210, 127, 254, 254, 125, 27, 35, 98, 26, 158, 255, 6, 66, 158, 174, 235, 126, + 253, 40, 238, 86, 24, 199, 86, 91, 9, 100, 187, 60, 125, 50, 34, 249, 87, 220, 118, 16, + 53, 51, 190, 53, 249, 85, 130, 100, 253, 147, 230, 160, 164, 13, + ] + } + + // Circom logs in Projective coordinates: console.log(curve.G1.one) + fn g1_one() -> G1Affine { + let x = Fq::one(); + let y = Fq::one() + Fq::one(); + let z = Fq::one(); + G1Affine::from(G1Projective::new(x, y, z)) + } + + // Circom logs in Projective coordinates: console.log(curve.G2.one) + fn g2_one() -> G2Affine { + let x = Fq2::new( + fq_from_str( + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + ), + fq_from_str( + "11559732032986387107991004021392285783925812861821192530917403151452391805634", + ), + ); + + let y = Fq2::new( + fq_from_str( + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + ), + fq_from_str( + "4082367875863433681332203403145435568316851327593401208105741076214120093531", + ), + ); + let z = Fq2::new(Fq::one(), Fq::zero()); + G2Affine::from(G2Projective::new(x, y, z)) + } + + #[test] + fn can_deser_fq() { + let buf = fq_buf(); + let fq = deserialize_field(&mut &buf[..]).unwrap(); + assert_eq!(fq, Fq::one()); + } + + #[test] + fn can_deser_g1() { + let buf = g1_buf(); + assert_eq!(buf.len(), 64); + let g1 = deserialize_g1(&mut &buf[..]).unwrap(); + let expected = g1_one(); + assert_eq!(g1, expected); + } + + #[test] + fn can_deser_g1_vec() { + let n_vars = 10; + let buf = vec![g1_buf(); n_vars] + .iter() + .cloned() + .flatten() + .collect::>(); + let expected = vec![g1_one(); n_vars]; + + let de = deserialize_g1_vec(&buf[..], n_vars as u32).unwrap(); + assert_eq!(expected, de); + } + + #[test] + fn can_deser_g2() { + let buf = g2_buf(); + assert_eq!(buf.len(), 128); + let g2 = deserialize_g2(&mut &buf[..]).unwrap(); + + let expected = g2_one(); + assert_eq!(g2, expected); + } + + #[test] + fn can_deser_g2_vec() { + let n_vars = 10; + let buf = vec![g2_buf(); n_vars] + .iter() + .cloned() + .flatten() + .collect::>(); + let expected = vec![g2_one(); n_vars]; + + let de = deserialize_g2_vec(&buf[..], n_vars as u32).unwrap(); + assert_eq!(expected, de); + } + + #[test] + fn header() { + // `circom --r1cs` using the below file: + // + // template Multiplier() { + // signal private input a; + // signal private input b; + // signal output c; + // + // c <== a*b; + // } + // + // component main = Multiplier(); + // + // Then: + // `snarkjs zkey new circuit.r1cs powersOfTau28_hez_final_10.ptau test.zkey` + let path = "./test-vectors/test.zkey"; + let file = File::open(path).unwrap(); + let map = unsafe { + MmapOptions::new() + .map(&file) + .expect("unable to create a memory map") + }; + let mut reader = Cursor::new(map.as_ref()); + let mut binfile = BinFile::new(&mut reader).unwrap(); + let header = binfile.groth_header().unwrap(); + assert_eq!(header.n_vars, 4); + assert_eq!(header.n_public, 1); + assert_eq!(header.domain_size, 4); + assert_eq!(header.power, 2); + } + + #[test] + fn deser_key() { + let path = "./test-vectors/test.zkey"; + let file = File::open(path).unwrap(); + let map = unsafe { + MmapOptions::new() + .map(&file) + .expect("unable to create a memory map") + }; + let mut reader = Cursor::new(map.as_ref()); + let mut binfile = BinFile::new(&mut reader).unwrap(); + let params = binfile.proving_key().unwrap(); + + // Check IC + let expected = vec![ + deserialize_g1( + &mut &[ + 11, 205, 205, 176, 2, 105, 129, 243, 153, 58, 137, 89, 61, 95, 99, 161, 133, + 201, 153, 192, 119, 19, 113, 136, 43, 105, 47, 206, 166, 55, 81, 22, 154, 77, + 58, 119, 28, 230, 160, 206, 134, 98, 4, 115, 112, 184, 46, 117, 61, 180, 103, + 138, 141, 202, 110, 252, 199, 252, 141, 211, 5, 46, 244, 10, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 118, 135, 198, 156, 63, 190, 210, 98, 194, 59, 169, 168, 204, 168, 76, 208, + 109, 170, 24, 193, 57, 31, 184, 88, 234, 218, 118, 58, 107, 129, 90, 36, 230, + 98, 62, 243, 3, 55, 68, 227, 117, 64, 188, 81, 81, 247, 161, 68, 68, 210, 142, + 191, 174, 43, 110, 194, 253, 128, 217, 4, 54, 196, 111, 43, + ][..], + ) + .unwrap(), + ]; + assert_eq!(expected, params.vk.gamma_abc_g1); + + // Check A Query + let expected = vec![ + deserialize_g1( + &mut &[ + 240, 165, 110, 187, 72, 39, 218, 59, 128, 85, 50, 174, 229, 1, 86, 58, 125, + 244, 145, 205, 248, 253, 120, 2, 165, 140, 154, 55, 220, 253, 14, 19, 212, 106, + 59, 19, 125, 198, 202, 4, 59, 74, 14, 62, 20, 248, 219, 47, 234, 205, 54, 183, + 33, 119, 165, 84, 46, 75, 39, 17, 229, 42, 192, 2, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 93, 53, 177, 82, 50, 5, 123, 116, 91, 35, 14, 196, 43, 180, 54, 15, 88, 144, + 197, 105, 57, 167, 54, 5, 188, 109, 17, 89, 9, 223, 80, 1, 39, 193, 211, 168, + 203, 119, 169, 105, 17, 156, 53, 106, 11, 102, 44, 92, 123, 220, 158, 240, 97, + 253, 30, 121, 4, 236, 171, 23, 100, 34, 133, 11, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 177, 47, 21, 237, 244, 73, 76, 98, 80, 10, 10, 142, 80, 145, 40, 254, 100, 214, + 103, 33, 38, 84, 238, 248, 252, 181, 75, 32, 109, 16, 93, 23, 135, 157, 206, + 122, 107, 105, 202, 164, 197, 124, 242, 100, 70, 108, 9, 180, 224, 102, 250, + 149, 130, 14, 133, 185, 132, 189, 193, 230, 180, 143, 156, 30, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 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, 0, 0, + ][..], + ) + .unwrap(), + ]; + assert_eq!(expected, params.a_query); + + // B G1 Query + let expected = vec![ + deserialize_g1( + &mut &[ + 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, 0, 0, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 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, 0, 0, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 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, 0, 0, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 177, 47, 21, 237, 244, 73, 76, 98, 80, 10, 10, 142, 80, 145, 40, 254, 100, 214, + 103, 33, 38, 84, 238, 248, 252, 181, 75, 32, 109, 16, 93, 23, 192, 95, 174, 93, + 171, 34, 86, 151, 199, 77, 127, 3, 75, 254, 119, 227, 124, 241, 134, 235, 51, + 55, 203, 254, 164, 226, 111, 250, 189, 190, 199, 17, + ][..], + ) + .unwrap(), + ]; + assert_eq!(expected, params.b_g1_query); + + // B G2 Query + let expected = vec![ + deserialize_g2( + &mut &[ + 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, 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, 0, 0, 0, 0, + ][..], + ) + .unwrap(), + deserialize_g2( + &mut &[ + 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, 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, 0, 0, 0, 0, + ][..], + ) + .unwrap(), + deserialize_g2( + &mut &[ + 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, 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, 0, 0, 0, 0, + ][..], + ) + .unwrap(), + deserialize_g2( + &mut &[ + 240, 25, 157, 232, 164, 49, 152, 204, 244, 190, 178, 178, 29, 133, 205, 175, + 172, 28, 12, 123, 139, 202, 196, 13, 67, 165, 204, 42, 74, 40, 6, 36, 112, 104, + 61, 67, 107, 112, 72, 41, 213, 210, 249, 75, 89, 144, 144, 34, 177, 228, 18, + 70, 80, 195, 124, 82, 40, 122, 91, 21, 198, 100, 154, 1, 16, 235, 41, 4, 176, + 106, 9, 113, 141, 251, 100, 233, 188, 128, 194, 173, 0, 100, 206, 110, 53, 223, + 163, 47, 166, 235, 25, 12, 151, 238, 45, 0, 78, 210, 56, 53, 57, 212, 67, 189, + 253, 132, 62, 62, 116, 20, 235, 15, 245, 113, 30, 182, 33, 127, 203, 231, 124, + 149, 74, 223, 39, 190, 217, 41, + ][..], + ) + .unwrap(), + ]; + assert_eq!(expected, params.b_g2_query); + + // Check L Query + let expected = vec![ + deserialize_g1( + &mut &[ + 146, 142, 29, 235, 9, 162, 84, 255, 6, 119, 86, 214, 154, 18, 12, 190, 202, 19, + 168, 45, 29, 76, 174, 130, 6, 59, 146, 15, 229, 82, 81, 40, 50, 25, 124, 247, + 129, 12, 147, 35, 108, 119, 178, 116, 238, 145, 33, 184, 74, 201, 128, 41, 151, + 6, 60, 84, 156, 225, 200, 14, 240, 171, 128, 20, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 26, 32, 112, 226, 161, 84, 188, 236, 141, 226, 119, 169, 235, 218, 253, 176, + 157, 184, 108, 243, 73, 122, 239, 217, 39, 190, 239, 105, 147, 190, 80, 47, + 211, 68, 155, 212, 139, 173, 229, 160, 123, 117, 243, 110, 162, 188, 217, 206, + 102, 19, 36, 189, 87, 183, 113, 8, 164, 133, 43, 142, 138, 109, 66, 33, + ][..], + ) + .unwrap(), + ]; + assert_eq!(expected, params.l_query); + + // Check H Query + let expected = vec![ + deserialize_g1( + &mut &[ + 21, 76, 104, 34, 28, 236, 135, 204, 218, 16, 160, 115, 185, 44, 19, 62, 43, 24, + 57, 99, 207, 105, 10, 139, 195, 60, 17, 57, 85, 244, 167, 10, 166, 166, 165, + 55, 38, 75, 116, 116, 182, 87, 217, 112, 28, 237, 239, 123, 231, 180, 122, 109, + 77, 116, 88, 67, 102, 48, 80, 214, 137, 47, 94, 30, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 144, 175, 205, 119, 119, 192, 11, 10, 148, 224, 87, 161, 157, 231, 101, 208, + 55, 15, 13, 16, 24, 59, 9, 22, 63, 215, 255, 30, 77, 188, 71, 37, 84, 227, 59, + 29, 159, 116, 101, 93, 212, 220, 159, 141, 204, 107, 131, 87, 174, 149, 175, + 72, 199, 109, 64, 109, 180, 150, 160, 249, 246, 33, 212, 29, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 129, 169, 52, 179, 66, 88, 123, 199, 222, 69, 24, 17, 219, 235, 118, 195, 156, + 210, 14, 21, 76, 155, 178, 210, 223, 4, 233, 5, 8, 18, 156, 24, 82, 68, 183, + 186, 7, 126, 2, 201, 207, 207, 74, 45, 44, 199, 16, 165, 25, 65, 157, 199, 90, + 159, 12, 150, 250, 17, 177, 193, 244, 93, 230, 41, + ][..], + ) + .unwrap(), + deserialize_g1( + &mut &[ + 207, 61, 229, 214, 21, 61, 103, 165, 93, 145, 54, 138, 143, 214, 5, 83, 183, + 22, 174, 87, 108, 59, 99, 96, 19, 20, 25, 139, 114, 238, 198, 40, 182, 88, 1, + 255, 206, 132, 156, 165, 178, 171, 0, 226, 179, 30, 192, 4, 79, 198, 69, 43, + 145, 133, 116, 86, 36, 144, 190, 119, 79, 241, 76, 16, + ][..], + ) + .unwrap(), + ]; + assert_eq!(expected, params.h_query); + } + + #[test] + fn deser_vk() { + let path = "./test-vectors/test.zkey"; + let file = File::open(path).unwrap(); + let map = unsafe { + MmapOptions::new() + .map(&file) + .expect("unable to create a memory map") + }; + let mut reader = Cursor::new(map.as_ref()); + let mut binfile = BinFile::new(&mut reader).unwrap(); + + let params = binfile.proving_key().unwrap(); + + let json = std::fs::read_to_string("./test-vectors/verification_key.json").unwrap(); + let json: Value = serde_json::from_str(&json).unwrap(); + + assert_eq!(json_to_g1(&json, "vk_alpha_1"), params.vk.alpha_g1); + assert_eq!(json_to_g2(&json, "vk_beta_2"), params.vk.beta_g2); + assert_eq!(json_to_g2(&json, "vk_gamma_2"), params.vk.gamma_g2); + assert_eq!(json_to_g2(&json, "vk_delta_2"), params.vk.delta_g2); + assert_eq!(json_to_g1_vec(&json, "IC"), params.vk.gamma_abc_g1); + } + + fn json_to_g1(json: &Value, key: &str) -> G1Affine { + let els: Vec = json + .get(key) + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|i| i.as_str().unwrap().to_string()) + .collect(); + G1Affine::from(G1Projective::new( + fq_from_str(&els[0]), + fq_from_str(&els[1]), + fq_from_str(&els[2]), + )) + } + + fn json_to_g1_vec(json: &Value, key: &str) -> Vec { + let els: Vec> = json + .get(key) + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|i| { + i.as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect::>() + }) + .collect(); + + els.iter() + .map(|coords| { + G1Affine::from(G1Projective::new( + fq_from_str(&coords[0]), + fq_from_str(&coords[1]), + fq_from_str(&coords[2]), + )) + }) + .collect() + } + + fn json_to_g2(json: &Value, key: &str) -> G2Affine { + let els: Vec> = json + .get(key) + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|i| { + i.as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect::>() + }) + .collect(); + + let x = Fq2::new(fq_from_str(&els[0][0]), fq_from_str(&els[0][1])); + let y = Fq2::new(fq_from_str(&els[1][0]), fq_from_str(&els[1][1])); + let z = Fq2::new(fq_from_str(&els[2][0]), fq_from_str(&els[2][1])); + G2Affine::from(G2Projective::new(x, y, z)) + } + + #[test] + fn verify_proof_with_zkey() { + let path = "./test-vectors/test.zkey"; + let file = File::open(path).unwrap(); + let map = unsafe { + MmapOptions::new() + .map(&file) + .expect("unable to create a memory map") + }; + let mut reader = Cursor::new(map.as_ref()); + let mut binfile = BinFile::new(&mut reader).unwrap(); + + let params = binfile.proving_key().unwrap(); + + let cfg = CircuitConfig::::new( + "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit.r1cs", + ) + .unwrap(); + let mut builder = CircomBuilder::new(cfg); + builder.push_input("a", 3); + builder.push_input("b", 11); + + let circom = builder.build().unwrap(); + + let inputs = circom.get_public_inputs().unwrap(); + + let mut rng = thread_rng(); + let proof = prove::<_, _, _, R1CStoQAPCircom>(circom, ¶ms, &mut rng).unwrap(); + + let pvk = prepare_verifying_key(¶ms.vk); + + let verified = verify_proof(&pvk, &proof, &inputs).unwrap(); + + assert!(verified); + } +} diff --git a/test-vectors/calculatewitness.js b/test-vectors/calculatewitness.js new file mode 100755 index 0000000..d562d27 --- /dev/null +++ b/test-vectors/calculatewitness.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const {stringifyBigInts, unstringifyBigInts} = require("snarkjs"); +const WitnessCalculatorBuilder = require("./witness_calculator.js"); + +// const wasmName = "smtverifier10.wasm" +// const inputName = "smtverifier10-input.json" + +const wasmName = "nconstraints.wasm" +const inputName = "nconstraints-input.json" + +async function run () { + const wasm = await fs.promises.readFile(wasmName); + const input = unstringifyBigInts(JSON.parse(await fs.promises.readFile(inputName, "utf8"))); + + console.log("input:", input); + let options; + const wc = await WitnessCalculatorBuilder(wasm, options); + + const w = await wc.calculateWitness(input); + + console.log("witness:\n", JSON.stringify(stringifyBigInts(w))); + + // const wb = await wc.calculateBinWitness(input); + + // console.log("witnessBin:", Buffer.from(wb).toString('hex')); + + // await fs.promises.writeFile(witnessName, JSON.stringify(stringifyBigInts(w), null, 1)); + +} + +run().then(() => { + process.exit(); +}); diff --git a/test-vectors/circuit2.circom b/test-vectors/circuit2.circom new file mode 100644 index 0000000..1cd0da2 --- /dev/null +++ b/test-vectors/circuit2.circom @@ -0,0 +1,39 @@ +template CheckBits(n) { + signal input in; + signal bits[n]; + var lc1=0; + + var e2=1; + for (var i = 0; i> i) & 1; + bits[i] * (bits[i] -1 ) === 0; + lc1 += bits[i] * e2; + e2 = e2+e2; + } + + lc1 === in; +} + +template Multiplier(n) { + signal private input a; + signal private input b; + signal output c; + signal inva; + signal invb; + + component chackA = CheckBits(n); + component chackB = CheckBits(n); + + chackA.in <== a; + chackB.in <== b; + + inva <-- 1/(a-1); + (a-1)*inva === 1; + + invb <-- 1/(b-1); + (b-1)*invb === 1; + + c <== a*b; +} + +component main = Multiplier(64); diff --git a/test-vectors/circuit2.wasm b/test-vectors/circuit2.wasm new file mode 100644 index 0000000..3495132 Binary files /dev/null and b/test-vectors/circuit2.wasm differ diff --git a/test-vectors/input.json b/test-vectors/input.json new file mode 100644 index 0000000..294fec2 --- /dev/null +++ b/test-vectors/input.json @@ -0,0 +1 @@ +{ "a": 3, "b": 11 } diff --git a/test-vectors/mycircuit-input1.json b/test-vectors/mycircuit-input1.json new file mode 100644 index 0000000..3b53ebe --- /dev/null +++ b/test-vectors/mycircuit-input1.json @@ -0,0 +1,2 @@ +{"a": 3, "b": 11} + diff --git a/test-vectors/mycircuit-input2.json b/test-vectors/mycircuit-input2.json new file mode 100644 index 0000000..86288c0 --- /dev/null +++ b/test-vectors/mycircuit-input2.json @@ -0,0 +1,2 @@ +{"a": "21888242871839275222246405745257275088548364400416034343698204186575796149939", "b": 11} + diff --git a/test-vectors/mycircuit-input3.json b/test-vectors/mycircuit-input3.json new file mode 100644 index 0000000..bd8dbf1 --- /dev/null +++ b/test-vectors/mycircuit-input3.json @@ -0,0 +1,2 @@ +{"a": "10944121435919637611123202872628637544274182200208017171849102093287904246808", "b": 2} + diff --git a/test-vectors/mycircuit-witness.json b/test-vectors/mycircuit-witness.json new file mode 100644 index 0000000..81becc6 --- /dev/null +++ b/test-vectors/mycircuit-witness.json @@ -0,0 +1,6 @@ +[ + "1", + "33", + "3", + "11" +] \ No newline at end of file diff --git a/test-vectors/mycircuit.circom b/test-vectors/mycircuit.circom new file mode 100644 index 0000000..1ca4dae --- /dev/null +++ b/test-vectors/mycircuit.circom @@ -0,0 +1,10 @@ +template Multiplier() { + signal private input a; + signal private input b; + signal output c; + + c <== a*b; +} + +component main = Multiplier(); + diff --git a/test-vectors/mycircuit.r1cs b/test-vectors/mycircuit.r1cs new file mode 100644 index 0000000..0aebe91 Binary files /dev/null and b/test-vectors/mycircuit.r1cs differ diff --git a/test-vectors/mycircuit.sym b/test-vectors/mycircuit.sym new file mode 100644 index 0000000..af23eeb --- /dev/null +++ b/test-vectors/mycircuit.sym @@ -0,0 +1,3 @@ +1,2,0,main.a +2,3,0,main.b +3,1,0,main.c diff --git a/test-vectors/mycircuit.wasm b/test-vectors/mycircuit.wasm new file mode 100644 index 0000000..4a28f0c Binary files /dev/null and b/test-vectors/mycircuit.wasm differ diff --git a/test-vectors/nconstraints.circom b/test-vectors/nconstraints.circom new file mode 100644 index 0000000..b32786c --- /dev/null +++ b/test-vectors/nconstraints.circom @@ -0,0 +1,14 @@ +template A(n) { + signal input in; + signal output out; + + signal intermediate[n]; + + intermediate[0] <== in; + for (var i=1; i0; i++) bytes.push(i8[p+i]); + + return String.fromCharCode.apply(null, bytes); + } +}; + +class WitnessCalculator { + constructor(memory, instance, sanityCheck) { + this.memory = memory; + this.i32 = new Uint32Array(memory.buffer); + this.instance = instance; + + this.n32 = (this.instance.exports.getFrLen() >> 2) - 2; + const pRawPrime = this.instance.exports.getPRawPrime(); + console.log("pRawPrime:", pRawPrime); + + // console.log("0:", this.i32[(pRawPrime >> 2)]); + this.prime = bigInt(0); + for (let i=this.n32-1; i>=0; i--) { + this.prime = this.prime.shiftLeft(32); + this.prime = this.prime.add(bigInt(this.i32[(pRawPrime >> 2) + i])); + } + console.log("prime:", this.prime); + + this.mask32 = bigInt("FFFFFFFF", 16); + console.log("mask32:", this.mask32); + this.NVars = this.instance.exports.getNVars(); + console.log("NVars:", this.NVars); + this.n64 = Math.floor((this.prime.bitLength() - 1) / 64)+1; + console.log("n64:", this.n64); + this.R = bigInt.one.shiftLeft(this.n64*64); + console.log("R:", this.R); + this.RInv = this.R.modInv(this.prime); + console.log("RInv:", this.RInv); + this.sanityCheck = sanityCheck; + + } + + async _doCalculateWitness(input, sanityCheck) { + this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0); + const pSigOffset = this.allocInt(); + console.log("pSigOffset:", pSigOffset); + const pFr = this.allocFr(); + console.log("pFr:", pFr); + for (let k in input) { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + console.log("h(", k, ") =", h, " = ", hMSB, hLSB); + this.instance.exports.getSignalOffset32(pSigOffset, 0, hMSB, hLSB); + const sigOffset = this.getInt(pSigOffset); + console.log("sigOffset:", sigOffset); + const fArr = flatArray(input[k]); + for (let i=0; i>2]; + } + + setInt(p, v) { + this.i32[p>>2] = v; + } + + getFr(p) { + const self = this; + const idx = (p>>2); + + if (self.i32[idx + 1] & 0x80000000) { + let res= bigInt(0); + for (let i=self.n32-1; i>=0; i--) { + res = res.shiftLeft(32); + res = res.add(bigInt(self.i32[idx+2+i])); + } + if (self.i32[idx + 1] & 0x40000000) { + return fromMontgomery(res); + } else { + return res; + } + + } else { + if (self.i32[idx] & 0x80000000) { + return self.prime.add( bigInt(self.i32[idx]).minus(bigInt(0x100000000)) ); + } else { + return bigInt(self.i32[idx]); + } + } + + function fromMontgomery(n) { + return n.times(self.RInv).mod(self.prime); + } + + } + + + setFr(p, v) { + const self = this; + v = bigInt(v); + + if (v.lt(bigInt("80000000", 16)) ) { + return setShortPositive(v); + } + if (v.geq(self.prime.minus(bigInt("80000000", 16))) ) { + return setShortNegative(v); + } + return setLongNormal(v); + + function setShortPositive(a) { + self.i32[(p >> 2)] = parseInt(a); + self.i32[(p >> 2) + 1] = 0; + } + + function setShortNegative(a) { + const b = bigInt("80000000", 16 ).add(a.minus( self.prime.minus(bigInt("80000000", 16 )))); + self.i32[(p >> 2)] = parseInt(b); + self.i32[(p >> 2) + 1] = 0; + } + + function setLongNormal(a) { + self.i32[(p >> 2)] = 0; + self.i32[(p >> 2) + 1] = 0x80000000; + for (let i=0; i> 2) + 2 + i] = a.shiftRight(i*32).and(self.mask32); + } + console.log(">>>", self.i32[(p >> 2)] , self.i32[(p >> 2) + 1]); + console.log(">>>", self.i32.slice((p >> 2) + 2, (p >> 2) + 2 + self.n32)); + } + } +} diff --git a/tests/groth16.rs b/tests/groth16.rs new file mode 100644 index 0000000..dbec85d --- /dev/null +++ b/tests/groth16.rs @@ -0,0 +1,39 @@ +use ark_circom::{CircomBuilder, CircuitConfig}; +use ark_std::rand::thread_rng; +use color_eyre::Result; + +use ark_bn254::Bn254; +use ark_groth16::{ + create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof, +}; + +#[test] +fn groth16_proof() -> Result<()> { + let cfg = CircuitConfig::::new( + "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit.r1cs", + )?; + let mut builder = CircomBuilder::new(cfg); + builder.push_input("a", 3); + builder.push_input("b", 11); + + // create an empty instance for setting it up + let circom = builder.setup(); + + let mut rng = thread_rng(); + let params = generate_random_parameters::(circom, &mut rng)?; + + let circom = builder.build()?; + + let inputs = circom.get_public_inputs().unwrap(); + + let proof = prove(circom, ¶ms, &mut rng)?; + + let pvk = prepare_verifying_key(¶ms.vk); + + let verified = verify_proof(&pvk, &proof, &inputs)?; + + assert!(verified); + + Ok(()) +} diff --git a/tests/solidity.rs b/tests/solidity.rs new file mode 100644 index 0000000..2742ee2 --- /dev/null +++ b/tests/solidity.rs @@ -0,0 +1,95 @@ +use ark_circom::{ + ethereum::{Inputs, Proof, VerifyingKey}, + CircomBuilder, CircuitConfig, +}; +use ark_std::rand::thread_rng; +use color_eyre::Result; + +use ark_bn254::Bn254; +use ark_groth16::{create_random_proof as prove, generate_random_parameters}; + +use ethers::{ + contract::{abigen, ContractError, ContractFactory}, + providers::{Http, Middleware, Provider}, + utils::{compile_and_launch_ganache, Ganache, Solc}, +}; + +use std::{convert::TryFrom, sync::Arc}; + +#[tokio::test] +async fn solidity_verifier() -> Result<()> { + let cfg = CircuitConfig::::new( + "./test-vectors/mycircuit.wasm", + "./test-vectors/mycircuit.r1cs", + )?; + let mut builder = CircomBuilder::new(cfg); + builder.push_input("a", 3); + builder.push_input("b", 11); + + // create an empty instance for setting it up + let circom = builder.setup(); + + let mut rng = thread_rng(); + let params = generate_random_parameters::(circom, &mut rng)?; + + let circom = builder.build()?; + let inputs = circom.get_public_inputs().unwrap(); + + let proof = prove(circom, ¶ms, &mut rng)?; + + // launch the network & compile the verifier + let (compiled, ganache) = + compile_and_launch_ganache(Solc::new("./tests/verifier.sol"), Ganache::new()).await?; + let acc = ganache.addresses()[0]; + let provider = Provider::::try_from(ganache.endpoint())?; + let provider = provider.with_sender(acc); + let provider = Arc::new(provider); + + // deploy the verifier + let contract = { + let contract = compiled + .get("TestVerifier") + .expect("could not find contract"); + + let factory = ContractFactory::new( + contract.abi.clone(), + contract.bytecode.clone(), + provider.clone(), + ); + let contract = factory.deploy(())?.send().await?; + let addr = contract.address(); + Groth16Verifier::new(addr, provider) + }; + + // check the proof + let verified = contract + .check_proof(proof, params.vk, inputs.as_slice()) + .await?; + assert!(verified); + + Ok(()) +} + +abigen!( + Groth16Verifier, + "./tests/verifier_abi.json" +); + +impl Groth16Verifier { + async fn check_proof, P: Into, VK: Into>( + &self, + proof: P, + vk: VK, + inputs: I, + ) -> Result> { + // convert into the expected format by the contract + let proof = proof.into().as_tuple(); + let vk = vk.into().as_tuple(); + let inputs = inputs.into().0; + + // query the contract + let res = self.verify(inputs, proof, vk).call().await?; + + Ok(res) + } +} diff --git a/tests/verifier.sol b/tests/verifier.sol new file mode 100644 index 0000000..79eb1eb --- /dev/null +++ b/tests/verifier.sol @@ -0,0 +1,304 @@ +// THIS FILE IS GENERATED BY HARDHAT-CIRCOM. DO NOT EDIT THIS FILE. + +// +// Copyright 2017 Christian Reitwiessner +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// 2019 OKIMS +// ported to solidity 0.5 +// fixed linter warnings +// added requiere error messages +// +pragma solidity ^0.7.6; +pragma abicoder v2; + +contract TestVerifier{ + constructor() {} + + function verify( + uint256[] memory input, + Verifier.Proof memory proof, + Verifier.VerifyingKey memory vk + ) public view returns (bool) { + uint256 err = Verifier.verify( + input, + proof, + vk + ); + + if (err == 0) { + return true; + } else { + return false; + } + } +} + +library Verifier { + using Pairing for *; + struct VerifyingKey { + Pairing.G1Point alfa1; + Pairing.G2Point beta2; + Pairing.G2Point gamma2; + Pairing.G2Point delta2; + Pairing.G1Point[] IC; + } + struct Proof { + Pairing.G1Point A; + Pairing.G2Point B; + Pairing.G1Point C; + } + + function verify( + uint256[] memory input, + Proof memory proof, + VerifyingKey memory vk + ) internal view returns (uint256) { + uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + require(input.length + 1 == vk.IC.length, "verifier-bad-input"); + // Compute the linear combination vk_x + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint256 i = 0; i < input.length; i++) { + require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field"); + vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); + } + vk_x = Pairing.addition(vk_x, vk.IC[0]); + if ( + !Pairing.pairingProd4( + Pairing.negate(proof.A), + proof.B, + vk.alfa1, + vk.beta2, + vk_x, + vk.gamma2, + proof.C, + vk.delta2 + ) + ) return 1; + return 0; + } + + function verifyProof( + uint256[2] memory a, + uint256[2][2] memory b, + uint256[2] memory c, + uint256[] memory input, + VerifyingKey memory vk + ) internal view returns (bool) { + Proof memory proof; + proof.A = Pairing.G1Point(a[0], a[1]); + proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); + proof.C = Pairing.G1Point(c[0], c[1]); + if (verify(input, proof, vk) == 0) { + return true; + } else { + return false; + } + } +} + +library Pairing { + struct G1Point { + uint256 X; + uint256 Y; + } + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + /// @return the generator of G1 + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + /// @return the generator of G2 + function P2() internal pure returns (G2Point memory) { + // Original code point + return + G2Point( + [ + 11559732032986387107991004021392285783925812861821192530917403151452391805634, + 10857046999023057135944570762232829481370756359578518086990519993285655852781 + ], + [ + 4082367875863433681332203403145435568316851327593401208105741076214120093531, + 8495653923123431417604973247489272438418190587263600148770280649306958101930 + ] + ); + + /* + // Changed by Jordi point + return G2Point( + [10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634], + [8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531] + ); +*/ + } + + /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero. + function negate(G1Point memory p) internal pure returns (G1Point memory r) { + // The prime q in the base field F_q for G1 + uint256 q + = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function addition(G1Point memory p1, G1Point memory p2) + internal + view + returns (G1Point memory r) + { + uint256[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-add-failed"); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. + function scalar_mul(G1Point memory p, uint256 s) + internal + view + returns (G1Point memory r) + { + uint256[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-mul-failed"); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) + internal + view + returns (bool) + { + require(p1.length == p2.length, "pairing-lengths-failed"); + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + for (uint256 i = 0; i < elements; i++) { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint256[1] memory out; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall( + sub(gas(), 2000), + 8, + add(input, 0x20), + mul(inputSize, 0x20), + out, + 0x20 + ) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-opcode-failed"); + return out[0] != 0; + } + + /// Convenience method for a pairing check for two pairs. + function pairingProd2( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for three pairs. + function pairingProd3( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for four pairs. + function pairingProd4( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} + diff --git a/tests/verifier_abi.json b/tests/verifier_abi.json new file mode 100644 index 0000000..5e05e4d --- /dev/null +++ b/tests/verifier_abi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256[]","name":"input","type":"uint256[]"},{"components":[{"components":[{"internalType":"uint256","name":"X","type":"uint256"},{"internalType":"uint256","name":"Y","type":"uint256"}],"internalType":"struct Pairing.G1Point","name":"A","type":"tuple"},{"components":[{"internalType":"uint256[2]","name":"X","type":"uint256[2]"},{"internalType":"uint256[2]","name":"Y","type":"uint256[2]"}],"internalType":"struct Pairing.G2Point","name":"B","type":"tuple"},{"components":[{"internalType":"uint256","name":"X","type":"uint256"},{"internalType":"uint256","name":"Y","type":"uint256"}],"internalType":"struct Pairing.G1Point","name":"C","type":"tuple"}],"internalType":"struct Verifier.Proof","name":"proof","type":"tuple"},{"components":[{"components":[{"internalType":"uint256","name":"X","type":"uint256"},{"internalType":"uint256","name":"Y","type":"uint256"}],"internalType":"struct Pairing.G1Point","name":"alfa1","type":"tuple"},{"components":[{"internalType":"uint256[2]","name":"X","type":"uint256[2]"},{"internalType":"uint256[2]","name":"Y","type":"uint256[2]"}],"internalType":"struct Pairing.G2Point","name":"beta2","type":"tuple"},{"components":[{"internalType":"uint256[2]","name":"X","type":"uint256[2]"},{"internalType":"uint256[2]","name":"Y","type":"uint256[2]"}],"internalType":"struct Pairing.G2Point","name":"gamma2","type":"tuple"},{"components":[{"internalType":"uint256[2]","name":"X","type":"uint256[2]"},{"internalType":"uint256[2]","name":"Y","type":"uint256[2]"}],"internalType":"struct Pairing.G2Point","name":"delta2","type":"tuple"},{"components":[{"internalType":"uint256","name":"X","type":"uint256"},{"internalType":"uint256","name":"Y","type":"uint256"}],"internalType":"struct Pairing.G1Point[]","name":"IC","type":"tuple[]"}],"internalType":"struct Verifier.VerifyingKey","name":"vk","type":"tuple"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]