From afcaf4a600c4c034fc17444564c84c131db7829f Mon Sep 17 00:00:00 2001 From: Mikerah Date: Tue, 13 Jun 2023 15:24:39 -0400 Subject: [PATCH] Completed benchmarking Basic benchmarking script Added benchmarks for groth16 base of benches.rs Update README.md (#2) * Update README.md * fix tests running example --------- Co-authored-by: Dmitriy Ryajov adding CI build (#4) adding initial CI build for circuits rework with poseidon (#3) * rework with poseidon * adding main template * adding todo * remove mimc Ark circom and rust ffi (#5) * wip rust ffi * proper test component instantiation * adding quick&dirty poseidon implementation * update gitignode * gitignore * adding rust circuit tests * gitignore * rename * add storer tests * move utils under circuit_tests * fix storage proofs * wip: ffi * instantiate storer * enable ark-serialize * delete js tests * update CI to run cargo tests * keep the artifacts dir * update .gitignore * build circuits * remove package json * place built circuits in correct dirs * update gitignore * remove node * fix ci * updating readme * storageproofs.rs to storage_proofs.rs * flatten tests chunks by default * add ffi * fix digest * minor fixes for ffi * fix storer test * use random data for chunks * debug optimizations to speed witness generation * clippy & other lint stuff * add back missing unsafe blocks * release mode disables constraint checks * fix ffi * fix hashes serialization * make naming more consistent * add missing pragma * use correct circuits * add todo * add clarification to readme * silence unused warning * include constants file into exec * remove unused imports extract poseidon to it's own package (#8) * extract poseidon to it's own package * move license to the bottom --- .github/workflows/ci.yml | 34 ++ .gitignore | 19 +- Cargo.toml | 49 +++ README.md | 35 +- bench/benches.rs | 75 +++++ bench/snarkjs_bench.sh | 65 ++++ benches/benches.rs | 66 ++++ benches/snarkjs_bench.sh | 65 ++++ circuits/poseidon-digest.circom | 55 ++++ circuits/storer.circom | 32 +- circuits/storer_main_256_80_32_16.circom | 5 + scripts/circuit-prep.sh | 5 + scripts/circuit_prep.sh | 28 -- scripts/install-circom.sh | 11 + src/circuit_tests/artifacts/.keep | 0 src/circuit_tests/mod.rs | 153 +++++++++ src/circuit_tests/poseidon-digest-test.circom | 20 ++ src/circuit_tests/poseidon-hash-test.circom | 17 + src/circuit_tests/storer-test.circom | 5 + src/circuit_tests/utils.rs | 49 +++ src/ffi.rs | 302 ++++++++++++++++++ src/lib.rs | 3 + src/storage_proofs.rs | 98 ++++++ test/circuits/storer-test.circom | 5 + test/circuits/storer_test.circom | 5 - test/storer.js | 123 +++++-- 26 files changed, 1222 insertions(+), 102 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Cargo.toml create mode 100644 bench/benches.rs create mode 100755 bench/snarkjs_bench.sh create mode 100644 benches/benches.rs create mode 100755 benches/snarkjs_bench.sh create mode 100644 circuits/poseidon-digest.circom create mode 100644 circuits/storer_main_256_80_32_16.circom create mode 100755 scripts/circuit-prep.sh delete mode 100755 scripts/circuit_prep.sh create mode 100755 scripts/install-circom.sh create mode 100644 src/circuit_tests/artifacts/.keep create mode 100644 src/circuit_tests/mod.rs create mode 100644 src/circuit_tests/poseidon-digest-test.circom create mode 100644 src/circuit_tests/poseidon-hash-test.circom create mode 100644 src/circuit_tests/storer-test.circom create mode 100644 src/circuit_tests/utils.rs create mode 100644 src/ffi.rs create mode 100644 src/lib.rs create mode 100644 src/storage_proofs.rs create mode 100644 test/circuits/storer-test.circom delete mode 100644 test/circuits/storer_test.circom diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ed5f2ab --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +# This workflow runs the tests for the circuits. +name: Test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + - name: Cache circom + id: cache-circom + uses: actions/cache@v3 + with: + path: ~/.cargo/bin/circom + # Since the version of circom is specified in `scripts/install-circom.sh`, + # as long as the file doesn't change we can reuse the circom binary. + key: ${{ runner.os }}-circom-${{ hashFiles('./scripts/install-circom.sh') }} + - name: Install circom if not cached + run: sh ./scripts/install-circom.sh + - run: npm ci + - name: Build circuits + run: sh ./scripts/circuit-prep.sh + - name: Run the tests + run: RUST_BACKTRACE=full cargo test diff --git a/.gitignore b/.gitignore index 40b878d..d3ad3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,18 @@ -node_modules/ \ No newline at end of file +node_modules/ + +# Added by cargo + +/target + + +# Added by cargo +# +# already existing elements were commented out + +#/target +/Cargo.lock +.vscode +test/circuits/artifacts +out.log +src/circuit_tests/artifacts/* +!src/circuit_tests/artifacts/.keep diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4ba1fd4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "codex-storage-proofs" +version = "0.1.0" +edition = "2021" + +[profile.dev] +opt-level = 3 + +[lib] +crate-type = [ + "staticlib", # Ensure it gets compiled as a (static) C library + # "cdylib", # If you want a shared/dynamic C library (advanced) + "lib", # For downstream Rust dependents: `examples/`, `tests/` etc. +] + +[dependencies] +ark-bn254 = { version = "0.3.0" } +ark-ec = { version = "0.3.0", default-features = false, features = [ + "parallel", +] } +ark-groth16 = { git = "https://github.com/arkworks-rs/groth16", rev = "765817f", features = [ + "parallel", +] } +ark-std = { version = "0.3.0", default-features = false, features = [ + "parallel", +] } +ark-serialize = { version = "0.3.0", default-features = false } +num-bigint = { version = "0.4", default-features = false, features = ["rand"] } +ark-circom = { git = "https://github.com/gakonst/ark-circom.git", rev = "35ce5a9", features = [ + "circom-2", +] } +ark-ff = { version = "0.3.0", features = ["std"] } +ruint = { version = "1.7.0", features = ["serde", "num-bigint", "ark-ff"] } +once_cell = "1.17.1" +serde = "1.0.156" +serde_json = "1.0.94" +num-traits = "0.2.15" +ark-relations = { version = "0.4.0", features = ["std", "tracing-subscriber"] } +<<<<<<< HEAD +<<<<<<< HEAD +rs-poseidon = {git = "https://github.com/status-im/rs-poseidon" } + +[dev-dependencies] +criterion = "0.3" +======= +>>>>>>> ebef300 (Ark circom and rust ffi (#5)) +======= +rs-poseidon = {git = "https://github.com/status-im/rs-poseidon" } +>>>>>>> f8e4b3e (extract poseidon to it's own package (#8)) diff --git a/README.md b/README.md index ad29c15..f383bf6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,29 @@ WIP Zero Knowledge tooling for the Codex project +## Usage + +First, clone the repo and install the circom components: + +```sh +git clone git@github.com:status-im/codex-storage-proofs.git +cd codex-storage-proofs +npm i +cd circuits +``` + +Nex, compile circuits: + +```sh +../scripts/circuit_prep.sh +``` + +Running the tests: + +```sh +cargo test # don't run in release more as it dissables circuit assets +``` + ## License Licensed and distributed under either of @@ -16,15 +39,3 @@ or * Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0) at your option. These files may not be copied, modified, or distributed except according to those terms. - -## Usage - -Preparing test key material (only suitable for testing) -``` -../scripts/circuit_prep.sh storer 13 -``` - -Running part of the code -``` -npm test test/merkletree.js -``` \ No newline at end of file diff --git a/bench/benches.rs b/bench/benches.rs new file mode 100644 index 0000000..b7faf6f --- /dev/null +++ b/bench/benches.rs @@ -0,0 +1,75 @@ +use std::fs::File; + +use ark_bn254::{Bn254, Fr}; +use ark_circom::{read_zkey, CircomBuilder, CircomConfig}; +use ark_groth16::{ + create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof, + Proof, ProvingKey, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read}; +use ark_std::rand::rngs::ThreadRng; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use ruint::aliases::U256; + +#[derive(Debug, Clone)] +pub struct StorageProofs { + builder: CircomBuilder, + params: ProvingKey, + rng: ThreadRng, +} + +// ...StorageProofs implementation here... + +// Functions for benchmarking +fn bench_prove(c: &mut Criterion) { + // You need to fill in these variables properly + let wtns = "..."; + let r1cs = "..."; + let zkey = Some("..."); + let mut sp = StorageProofs::new(wtns, r1cs, zkey); + let chunks: &[U256] = &[]; + let siblings: &[U256] = &[]; + let hashes: &[U256] = &[]; + let path: &[i32] = &[]; + let root = U256::default(); + let salt = U256::default(); + let mut proof_bytes = Vec::new(); + let mut public_inputs_bytes = Vec::new(); + + c.bench_function("StorageProofs prove", |b| { + b.iter(|| { + black_box( + sp.prove( + chunks, + siblings, + hashes, + path, + root, + salt, + &mut proof_bytes, + &mut public_inputs_bytes, + ) + .unwrap(), + ) + }) + }); +} + +fn bench_verify(c: &mut Criterion) { + // You need to fill in these variables properly + let wtns = "..."; + let r1cs = "..."; + let zkey = Some("..."); + let mut sp = StorageProofs::new(wtns, r1cs, zkey); + let proof_bytes: &[u8] = &[]; + let public_inputs: &[u8] = &[]; + + c.bench_function("StorageProofs verify", |b| { + b.iter(|| { + black_box(sp.verify(proof_bytes, public_inputs).unwrap()); + }) + }); +} + +criterion_group!(benches, bench_prove, bench_verify); +criterion_main!(benches); diff --git a/bench/snarkjs_bench.sh b/bench/snarkjs_bench.sh new file mode 100755 index 0000000..ee04303 --- /dev/null +++ b/bench/snarkjs_bench.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Set up the benchmarking parameters +ITERATIONS=10 +CIRCUIT=../test/circuits/storer_test.circom +WITNESS=./input.json + +# Define the SnarkJS commands for each system +GROTH16_CMD="snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json" +PLONK_CMD="snarkjs plonk prove circuit_final.zkey witness.wtns proof.json public.json" + +# Set up the powers of tau ceremony +echo "Set up powers of tau ceremony" +snarkjs powersoftau new bn128 17 ../scripts/pot17_bn128_0000.ptau -v + +# Generate circuit files +circom ${CIRCUIT} --r1cs --wasm --sym +snarkjs r1cs export json ./storer_test.r1cs ./storer_test.r1cs.json + +# Generate the proving and verifying keys for Groth16 +echo "Preparing phase 1" +snarkjs powersoftau contribute ../scripts/pot17_bn128_0000.ptau ../scripts/pot17_bn128_0001.ptau >/dev/null 2>&1 /dev/null 2>&1 /dev/null 2>&1 +snarkjs zkey contribute circuit_0001.zkey circuit_0002.zkey --name="2nd contributor" >/dev/null 2>&1 +snarkjs zkey verify ./storer_test.r1cs ../scripts/pot17_bn128_final.ptau circuit_0002.zkey +snarkjs zkey beacon circuit_0002.zkey circuit_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +snarkjs zkey verify ./storer_test.r1cs ../scripts/pot17_bn128_final.ptau circuit_final.zkey +snarkjs zkey export verificationkey circuit_final.zkey verification_key.json +for i in $(seq 1 $ITERATIONS); do + echo "Proving..." + /usr/bin/time -f "%e seconds" $GROTH16_CMD >/dev/null 2>&1 + echo "Verifying.." + /usr/bin/time -f "%e seconds " snarkjs groth16 verify verification_key.json public.json proof.json +done + + +# Generate the proving and verifying keys for PLONK +echo "Generating PLONK keys..." +snarkjs powersoftau contribute ./contributions_2 pot12_0000_final_challenge >/dev/null 2>&1 +snarkjs powersoftau verify ./contributions_2 >/dev/null 2>&1 +snarkjs powersoftau prepare phase2 ./contributions_2 pot12_0000_final_challenge --srs_monomial_form ./srs.monomial >/dev/null 2>&1 +snarkjs plonk setup --srs_monomial_form ./srs.monomial >/dev/null 2>&1 + + +# Benchmark PLONK +echo "Benchmarking PLONK..." +for i in $(seq 1 $ITERATIONS); do + /usr/bin/time -f "%e seconds" $PLONK_CMD >/dev/null 2>&1 +done + diff --git a/benches/benches.rs b/benches/benches.rs new file mode 100644 index 0000000..c960c44 --- /dev/null +++ b/benches/benches.rs @@ -0,0 +1,66 @@ +use std::fs::File; + +use ark_bn254::{Bn254, Fr}; +use ark_circom::{read_zkey, CircomBuilder, CircomConfig}; +use ark_groth16::{ + create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof, + Proof, ProvingKey, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read}; +use ark_std::rand::rngs::ThreadRng; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use ruint::aliases::U256; +use codex_storage_proofs::storage_proofs::{StorageProofs}; + + +// Functions for benchmarking +fn bench_prove(c: &mut Criterion) { + let wtns = "./witness.wtns"; + let r1cs = "./storer_test.r1cs"; + let zkey = Some("./circuit_0001.zkey".to_string()); + let mut sp = StorageProofs::new(wtns.to_string(), r1cs.to_string(), zkey); + let chunks: &[U256] = &[]; + let siblings: &[U256] = &[]; + let hashes: &[U256] = &[]; + let path: &[i32] = &[]; + let root = U256::default(); + let salt = U256::default(); + let mut proof_bytes = Vec::new(); + let mut public_inputs_bytes = Vec::new(); + + c.bench_function("StorageProofs prove", |b| { + b.iter(|| { + black_box( + sp.prove( + chunks, + siblings, + hashes, + path, + root, + salt, + &mut proof_bytes, + &mut public_inputs_bytes, + ) + .unwrap(), + ) + }) + }); +} + +fn bench_verify(c: &mut Criterion) { + let wtns = "./witness.wtns"; + let r1cs = "./storer_test.r1cs"; + let zkey = Some("./circuit_0001.zkey".to_string()); + let mut sp = StorageProofs::new(wtns.to_string(), r1cs.to_string(), zkey); + let proof_bytes: &[u8] = &[]; + let public_inputs: &[u8] = &[]; + + c.bench_function("StorageProofs verify", |b| { + b.iter(|| { + black_box(sp.verify(proof_bytes, public_inputs).unwrap()); + }) + }); +} + +criterion_group!(benches, bench_prove, bench_verify); +criterion_main!(benches); diff --git a/benches/snarkjs_bench.sh b/benches/snarkjs_bench.sh new file mode 100755 index 0000000..ee04303 --- /dev/null +++ b/benches/snarkjs_bench.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Set up the benchmarking parameters +ITERATIONS=10 +CIRCUIT=../test/circuits/storer_test.circom +WITNESS=./input.json + +# Define the SnarkJS commands for each system +GROTH16_CMD="snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json" +PLONK_CMD="snarkjs plonk prove circuit_final.zkey witness.wtns proof.json public.json" + +# Set up the powers of tau ceremony +echo "Set up powers of tau ceremony" +snarkjs powersoftau new bn128 17 ../scripts/pot17_bn128_0000.ptau -v + +# Generate circuit files +circom ${CIRCUIT} --r1cs --wasm --sym +snarkjs r1cs export json ./storer_test.r1cs ./storer_test.r1cs.json + +# Generate the proving and verifying keys for Groth16 +echo "Preparing phase 1" +snarkjs powersoftau contribute ../scripts/pot17_bn128_0000.ptau ../scripts/pot17_bn128_0001.ptau >/dev/null 2>&1 /dev/null 2>&1 /dev/null 2>&1 +snarkjs zkey contribute circuit_0001.zkey circuit_0002.zkey --name="2nd contributor" >/dev/null 2>&1 +snarkjs zkey verify ./storer_test.r1cs ../scripts/pot17_bn128_final.ptau circuit_0002.zkey +snarkjs zkey beacon circuit_0002.zkey circuit_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +snarkjs zkey verify ./storer_test.r1cs ../scripts/pot17_bn128_final.ptau circuit_final.zkey +snarkjs zkey export verificationkey circuit_final.zkey verification_key.json +for i in $(seq 1 $ITERATIONS); do + echo "Proving..." + /usr/bin/time -f "%e seconds" $GROTH16_CMD >/dev/null 2>&1 + echo "Verifying.." + /usr/bin/time -f "%e seconds " snarkjs groth16 verify verification_key.json public.json proof.json +done + + +# Generate the proving and verifying keys for PLONK +echo "Generating PLONK keys..." +snarkjs powersoftau contribute ./contributions_2 pot12_0000_final_challenge >/dev/null 2>&1 +snarkjs powersoftau verify ./contributions_2 >/dev/null 2>&1 +snarkjs powersoftau prepare phase2 ./contributions_2 pot12_0000_final_challenge --srs_monomial_form ./srs.monomial >/dev/null 2>&1 +snarkjs plonk setup --srs_monomial_form ./srs.monomial >/dev/null 2>&1 + + +# Benchmark PLONK +echo "Benchmarking PLONK..." +for i in $(seq 1 $ITERATIONS); do + /usr/bin/time -f "%e seconds" $PLONK_CMD >/dev/null 2>&1 +done + diff --git a/circuits/poseidon-digest.circom b/circuits/poseidon-digest.circom new file mode 100644 index 0000000..c3590b7 --- /dev/null +++ b/circuits/poseidon-digest.circom @@ -0,0 +1,55 @@ +pragma circom 2.1.0; + +include "../node_modules/circomlib/circuits/poseidon.circom"; + +function roundUpDiv(x, n) { + var last = x % n; // get the last digit + var div = x \ n; // get the division + + if (last > 0) { + return div + 1; + } + + return div; +} + +template parallel PoseidonDigest(BLOCK_SIZE, DIGEST_CHUNK) { + // BLOCK_SIZE - size of the input block array + // DIGEST_CHUNK - number of elements to hash at once + signal input block[BLOCK_SIZE]; // Input block array + signal output hash; // Output hash + + // Split array into chunks of size DIGEST_CHUNK, usually 2 + var NUM_CHUNKS = roundUpDiv(BLOCK_SIZE, DIGEST_CHUNK); + + // Initialize an array to store hashes of each block + component hashes[NUM_CHUNKS]; + + // Loop over chunks and hash them using Poseidon() + for (var i = 0; i < NUM_CHUNKS; i++) { + hashes[i] = Poseidon(DIGEST_CHUNK); + + var start = i * DIGEST_CHUNK; + var end = start + DIGEST_CHUNK; + for (var j = start; j < end; j++) { + if (j >= BLOCK_SIZE) { + hashes[i].inputs[j - start] <== 0; + } else { + hashes[i].inputs[j - start] <== block[j]; + } + } + } + + // Concatenate hashes into a single block + var concat[NUM_CHUNKS]; + for (var i = 0; i < NUM_CHUNKS; i++) { + concat[i] = hashes[i].out; + } + + // Hash concatenated array using Poseidon() again + component h = Poseidon(NUM_CHUNKS); + h.inputs <== concat; + + // Assign output to hash signal + hash <== h.out; +} diff --git a/circuits/storer.circom b/circuits/storer.circom index cc98c9c..c0e500a 100644 --- a/circuits/storer.circom +++ b/circuits/storer.circom @@ -1,11 +1,11 @@ pragma circom 2.1.0; -// include "../node_modules/circomlib/circuits/poseidon.circom"; -include "../node_modules/circomlib/circuits/mimc.circom"; -// include "../node_modules/circomlib/circuits/mimcsponge.circom"; +include "../node_modules/circomlib/circuits/poseidon.circom"; include "../node_modules/circomlib/circuits/switcher.circom"; include "../node_modules/circomlib/circuits/bitify.circom"; +include "./poseidon-digest.circom"; + template parallel MerkleProof(LEVELS) { signal input leaf; signal input pathElements[LEVELS]; @@ -26,31 +26,19 @@ template parallel MerkleProof(LEVELS) { switcher[i].R <== pathElements[i]; switcher[i].sel <== indexBits.out[i]; - // hasher[i] = Poseidon(2); - hasher[i] = MultiMiMC7(2, 91); - hasher[i].k <== 2; - hasher[i].in[0] <== switcher[i].outL; - hasher[i].in[1] <== switcher[i].outR; + hasher[i] = Poseidon(2); + hasher[i].inputs[0] <== switcher[i].outL; + hasher[i].inputs[1] <== switcher[i].outR; } root <== hasher[LEVELS - 1].out; } -template parallel HashCheck(BLOCK_SIZE) { - signal input block[BLOCK_SIZE]; - signal input blockHash; - - component hash = MultiMiMC7(BLOCK_SIZE, 91); - hash.in <== block; - hash.k <== 2; - - blockHash === hash.out; // assert that block matches hash -} - -template StorageProver(BLOCK_SIZE, QUERY_LEN, LEVELS) { +template StorageProver(BLOCK_SIZE, QUERY_LEN, LEVELS, DIGEST_CHUNK) { // BLOCK_SIZE: size of block in symbols // QUERY_LEN: query length, i.e. number if indices to be proven // LEVELS: size of Merkle Tree in the manifest + // DIGEST_CHUNK: number of symbols to hash in one go signal input chunks[QUERY_LEN][BLOCK_SIZE]; // chunks to be proven signal input siblings[QUERY_LEN][LEVELS]; // siblings hashes of chunks to be proven signal input path[QUERY_LEN]; // path of chunks to be proven @@ -62,9 +50,9 @@ template StorageProver(BLOCK_SIZE, QUERY_LEN, LEVELS) { component hashers[QUERY_LEN]; for (var i = 0; i < QUERY_LEN; i++) { - hashers[i] = HashCheck(BLOCK_SIZE); + hashers[i] = PoseidonDigest(BLOCK_SIZE, DIGEST_CHUNK); hashers[i].block <== chunks[i]; - hashers[i].blockHash <== hashes[i]; + hashers[i].hash === hashes[i]; } component merkelizer[QUERY_LEN]; diff --git a/circuits/storer_main_256_80_32_16.circom b/circuits/storer_main_256_80_32_16.circom new file mode 100644 index 0000000..41efe31 --- /dev/null +++ b/circuits/storer_main_256_80_32_16.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.0; + +include "./storer.circom"; + +component main { public [root, salt] } = StorageProver(256, 80, 32, 16); diff --git a/scripts/circuit-prep.sh b/scripts/circuit-prep.sh new file mode 100755 index 0000000..34a1842 --- /dev/null +++ b/scripts/circuit-prep.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +circom src/circuit_tests/poseidon-digest-test.circom --r1cs --wasm -o src/circuit_tests/artifacts +circom src/circuit_tests/poseidon-hash-test.circom --r1cs --wasm -o src/circuit_tests/artifacts +circom src/circuit_tests/storer-test.circom --r1cs --wasm -o src/circuit_tests/artifacts diff --git a/scripts/circuit_prep.sh b/scripts/circuit_prep.sh deleted file mode 100755 index fbdaa3c..0000000 --- a/scripts/circuit_prep.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -set -e -set -x - -CIRCUIT=`basename $1` -POWER="${2:-12}" -CURVE="${3:-bn128}" - -POTPREFIX=pot${POWER}_${CURVE} - -if [ ! -f ${POTPREFIX}_final.ptau ] -then - snarkjs powersoftau new $CURVE $POWER ${POTPREFIX}_0000.ptau -v - snarkjs powersoftau contribute ${POTPREFIX}_0000.ptau ${POTPREFIX}_0001.ptau --name="First contribution" -v -e="random text" - snarkjs powersoftau verify ${POTPREFIX}_0001.ptau - snarkjs powersoftau beacon ${POTPREFIX}_0001.ptau ${POTPREFIX}_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon" - snarkjs powersoftau prepare phase2 ${POTPREFIX}_beacon.ptau ${POTPREFIX}_final.ptau -v - snarkjs powersoftau verify ${POTPREFIX}_final.ptau -fi - -# phase 2 -circom $1.circom --r1cs --wasm - -snarkjs groth16 setup ${CIRCUIT}.r1cs ${POTPREFIX}_final.ptau ${CIRCUIT}_0000.zkey -snarkjs zkey contribute ${CIRCUIT}_0000.zkey ${CIRCUIT}_0001.zkey --name="1st Contributor Name" -v -e="another random text" -snarkjs zkey verify ${CIRCUIT}.r1cs ${POTPREFIX}_final.ptau ${CIRCUIT}_0001.zkey -snarkjs zkey beacon ${CIRCUIT}_0001.zkey ${CIRCUIT}_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon phase2" - diff --git a/scripts/install-circom.sh b/scripts/install-circom.sh new file mode 100755 index 0000000..d4aea3b --- /dev/null +++ b/scripts/install-circom.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +circom_version=v2.1.4 + +if ! [ -x "$(command -v circom)" ]; then + git clone https://github.com/iden3/circom.git + cd circom + git checkout $circom_version + cargo build --release + cargo install --path circom +fi diff --git a/src/circuit_tests/artifacts/.keep b/src/circuit_tests/artifacts/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/circuit_tests/mod.rs b/src/circuit_tests/mod.rs new file mode 100644 index 0000000..d520612 --- /dev/null +++ b/src/circuit_tests/mod.rs @@ -0,0 +1,153 @@ +pub mod utils; + +#[cfg(test)] +mod test { + use ark_bn254::Bn254; + use ark_circom::{CircomBuilder, CircomConfig}; + use ark_groth16::{ + create_random_proof as prove, generate_random_parameters, prepare_inputs, + prepare_verifying_key, verify_proof_with_prepared_inputs, ProvingKey, + }; + use ark_std::rand::{distributions::Alphanumeric, rngs::ThreadRng, Rng}; + use rs_poseidon::poseidon::hash; + use ruint::aliases::U256; + + use crate::{ + circuit_tests::utils::{digest, treehash}, + storage_proofs::StorageProofs, + }; + + pub struct CircuitsTests { + builder: CircomBuilder, + params: ProvingKey, + rng: ThreadRng, + } + + impl CircuitsTests { + pub fn new(wtns: String, r1cs: String) -> CircuitsTests { + let mut rng = ThreadRng::default(); + let builder = CircomBuilder::new(CircomConfig::::new(wtns, r1cs).unwrap()); + let params = + generate_random_parameters::(builder.setup(), &mut rng).unwrap(); + + CircuitsTests { + builder, + params, + rng, + } + } + + pub fn poseidon_hash(&mut self, elements: &[U256], hash: U256) -> bool { + let mut builder = self.builder.clone(); + + elements.iter().for_each(|c| builder.push_input("in", *c)); + builder.push_input("hash", hash); + + let circuit = builder.build().unwrap(); + let inputs = circuit.get_public_inputs().unwrap(); + let proof = prove(circuit, &self.params, &mut self.rng).unwrap(); + let vk = prepare_verifying_key(&self.params.vk); + let public_inputs = prepare_inputs(&vk, &inputs).unwrap(); + verify_proof_with_prepared_inputs(&vk, &proof, &public_inputs).is_ok() + } + + pub fn poseidon_digest(&mut self, elements: &[U256], hash: U256) -> bool { + let mut builder = self.builder.clone(); + + elements + .iter() + .for_each(|c| builder.push_input("block", *c)); + builder.push_input("hash", hash); + + let circuit = builder.build().unwrap(); + let inputs = circuit.get_public_inputs().unwrap(); + + let proof = prove(circuit, &self.params, &mut self.rng).unwrap(); + let vk = prepare_verifying_key(&self.params.vk); + let public_inputs = prepare_inputs(&vk, &inputs).unwrap(); + + verify_proof_with_prepared_inputs(&vk, &proof, &public_inputs).is_ok() + } + } + + #[test] + fn test_poseidon_hash() { + let r1cs = "./src/circuit_tests/artifacts/poseidon-hash-test.r1cs"; + let wasm = "./src/circuit_tests/artifacts/poseidon-hash-test_js/poseidon-hash-test.wasm"; + + let mut hasher = CircuitsTests::new(wasm.to_string(), r1cs.to_string()); + assert!(hasher.poseidon_hash(&[U256::from(1)], hash(&[U256::from(1)]))); + } + + #[test] + fn test_poseidon_digest() { + let r1cs = "./src/circuit_tests/artifacts/poseidon-digest-test.r1cs"; + let wasm = + "./src/circuit_tests/artifacts/poseidon-digest-test_js/poseidon-digest-test.wasm"; + + let mut hasher = CircuitsTests::new(wasm.to_string(), r1cs.to_string()); + let input: Vec = (0..256).map(|c| U256::from(c)).collect(); + assert!(hasher.poseidon_digest(&input, digest(&input, Some(16)))); + } + + #[test] + fn test_storer() { + let r1cs = "./src/circuit_tests/artifacts/storer-test.r1cs"; + let wasm = "./src/circuit_tests/artifacts/storer-test_js/storer-test.wasm"; + let mut prover = StorageProofs::new(wasm.to_string(), r1cs.to_string(), None); + + // generate a tuple of (preimages, hash), where preimages is a vector of 256 U256s + // and hash is the hash of each vector generated using the digest function + let data = (0..4) + .map(|_| { + let rng = ThreadRng::default(); + let preimages: Vec = rng + .sample_iter(Alphanumeric) + .take(256) + .map(|c| U256::from(c)) + .collect(); + let hash = digest(&preimages, Some(16)); + (preimages, hash) + }) + .collect::, U256)>>(); + + let chunks: Vec = data.iter().flat_map(|c| c.0.to_vec()).collect(); + let hashes: Vec = data.iter().map(|c| c.1).collect(); + let path = [0, 1, 2, 3].to_vec(); + + let parent_hash_l = hash(&[hashes[0], hashes[1]]); + let parent_hash_r = hash(&[hashes[2], hashes[3]]); + + let siblings = &[ + hashes[1], + parent_hash_r, + hashes[0], + parent_hash_r, + hashes[3], + parent_hash_l, + hashes[2], + parent_hash_l, + ]; + + let root = treehash(hashes.as_slice()); + let proof_bytes = &mut Vec::new(); + let public_inputs_bytes = &mut Vec::new(); + + prover + .prove( + chunks.as_slice(), + siblings, + hashes.as_slice(), + path.as_slice(), + root, + root, // random salt - block hash + proof_bytes, + public_inputs_bytes, + ) + .unwrap(); + + assert!(prover + .verify(proof_bytes.as_slice(), public_inputs_bytes.as_slice()) + .is_ok()); + } +} diff --git a/src/circuit_tests/poseidon-digest-test.circom b/src/circuit_tests/poseidon-digest-test.circom new file mode 100644 index 0000000..8bb957c --- /dev/null +++ b/src/circuit_tests/poseidon-digest-test.circom @@ -0,0 +1,20 @@ +pragma circom 2.1.0; + +include "../../circuits/poseidon-digest.circom"; + +template PoseidonDigestTest(BLOCK_SIZE, CHUNK_SIZE) { + signal input block[BLOCK_SIZE]; + signal input hash; + signal output hash2; + + component digest = PoseidonDigest(BLOCK_SIZE, CHUNK_SIZE); + for (var i = 0; i < BLOCK_SIZE; i++) { + digest.block[i] <== block[i]; + } + + digest.hash === hash; // verify that the hash is correct + + hash2 <== digest.hash; +} + +component main { public [hash] } = PoseidonDigestTest(256, 16); diff --git a/src/circuit_tests/poseidon-hash-test.circom b/src/circuit_tests/poseidon-hash-test.circom new file mode 100644 index 0000000..01b965e --- /dev/null +++ b/src/circuit_tests/poseidon-hash-test.circom @@ -0,0 +1,17 @@ +pragma circom 2.1.0; + +include "../../node_modules/circomlib/circuits/poseidon.circom"; + +template PoseidonHash(SIZE) { + signal input in[SIZE]; + signal input hash; + + component hasher = Poseidon(SIZE); + for(var i = 0; i < SIZE; i++) { + hasher.inputs[i] <== in[i]; + } + + hasher.out === hash; +} + +component main { public [hash] } = PoseidonHash(1); diff --git a/src/circuit_tests/storer-test.circom b/src/circuit_tests/storer-test.circom new file mode 100644 index 0000000..9d97cc6 --- /dev/null +++ b/src/circuit_tests/storer-test.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.0; + +include "../../circuits/storer.circom"; + +component main { public [root, salt] } = StorageProver(256, 4, 2, 16); diff --git a/src/circuit_tests/utils.rs b/src/circuit_tests/utils.rs new file mode 100644 index 0000000..8053adb --- /dev/null +++ b/src/circuit_tests/utils.rs @@ -0,0 +1,49 @@ +#![allow(dead_code)] + +use rs_poseidon::poseidon::hash; +use ruint::{aliases::U256, uint}; + +pub fn digest(input: &[U256], chunk_size: Option) -> U256 { + let chunk_size = chunk_size.unwrap_or(4); + let chunks = ((input.len() as f32) / (chunk_size as f32)).ceil() as usize; + let mut concat: Vec = vec![]; + + for i in 0..chunks { + let range = (i * chunk_size)..std::cmp::min((i + 1) * chunk_size, input.len()); + let mut chunk = input[range].to_vec(); + if chunk.len() < chunk_size { + chunk.resize(chunk_size, uint!(0_U256)); + } + + concat.push(hash(chunk.as_slice())); + } + + if concat.len() > 1 { + return hash(concat.as_slice()); + } + + concat[0] +} + +pub fn treehash(leafs: &[U256]) -> U256 { + // simple merkle root (treehash) generator + // unbalanced trees will have the last leaf duplicated + let mut merkle: Vec = leafs.to_vec(); + + while merkle.len() > 1 { + let mut new_merkle = Vec::new(); + let mut i = 0; + while i < merkle.len() { + new_merkle.push(hash(&[merkle[i], merkle[i + 1]])); + i += 2; + } + + if merkle.len() % 2 == 1 { + new_merkle.push(hash(&[merkle[merkle.len() - 2], merkle[merkle.len() - 2]])); + } + + merkle = new_merkle; + } + + merkle[0] +} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..1a779d7 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,302 @@ +use ruint::aliases::U256; + +use crate::storage_proofs::StorageProofs; +use std::str; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct Buffer { + pub data: *const u8, + pub len: usize, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ProofCtx { + pub proof: Buffer, + pub public_inputs: Buffer, +} + +impl ProofCtx { + pub fn new(proof: &[u8], public_inputs: &[u8]) -> Self { + Self { + proof: Buffer { + data: proof.as_ptr(), + len: proof.len(), + }, + public_inputs: Buffer { + data: public_inputs.as_ptr(), + len: public_inputs.len(), + }, + } + } +} + +/// # Safety +/// +/// Construct a StorageProofs object +#[no_mangle] +pub unsafe extern "C" fn init( + r1cs: *const &Buffer, + wasm: *const &Buffer, + zkey: *const &Buffer, +) -> *mut StorageProofs { + let r1cs = { + if r1cs.is_null() { + return std::ptr::null_mut(); + } + + let slice = std::slice::from_raw_parts((*r1cs).data, (*r1cs).len); + str::from_utf8(slice).unwrap().to_string() + }; + + let wasm = { + if wasm.is_null() { + return std::ptr::null_mut(); + } + + let slice = std::slice::from_raw_parts((*wasm).data, (*wasm).len); + str::from_utf8(slice).unwrap().to_string() + }; + + let zkey = { + if !zkey.is_null() { + let slice = std::slice::from_raw_parts((*zkey).data, (*zkey).len); + Some(str::from_utf8(slice).unwrap().to_string()) + } else { + None + } + }; + + Box::into_raw(Box::new(StorageProofs::new(wasm, r1cs, zkey))) +} + +/// # Safety +/// +/// Use after constructing a StorageProofs object with init +#[no_mangle] +pub unsafe extern "C" fn prove( + prover_ptr: *mut StorageProofs, + chunks: *const Buffer, + siblings: *const Buffer, + hashes: *const Buffer, + path: *const i32, + path_len: usize, + pubkey: *const Buffer, + root: *const Buffer, + salt: *const Buffer, +) -> *mut ProofCtx { + let chunks = { + let slice = std::slice::from_raw_parts((*chunks).data, (*chunks).len); + slice + .chunks(U256::BYTES) + .map(|c| U256::try_from_le_slice(c).unwrap()) + .collect::>() + }; + + let siblings = { + let slice = std::slice::from_raw_parts((*siblings).data, (*siblings).len); + slice + .chunks(U256::BYTES) + .map(|c| U256::try_from_le_slice(c).unwrap()) + .collect::>() + }; + + let hashes = { + let slice = std::slice::from_raw_parts((*hashes).data, (*hashes).len); + slice + .chunks(U256::BYTES) + .map(|c| U256::try_from_le_slice(c).unwrap()) + .collect::>() + }; + + let path = { + let slice = std::slice::from_raw_parts(path, path_len); + slice.to_vec() + }; + + let pubkey = + U256::try_from_le_slice(std::slice::from_raw_parts((*pubkey).data, (*pubkey).len)).unwrap(); + + let root = + U256::try_from_le_slice(std::slice::from_raw_parts((*root).data, (*root).len)).unwrap(); + + let salt = + U256::try_from_le_slice(std::slice::from_raw_parts((*salt).data, (*salt).len)).unwrap(); + + let proof_bytes = &mut Vec::new(); + let public_inputs_bytes = &mut Vec::new(); + + let mut _prover = &mut *prover_ptr; + _prover + .prove( + chunks.as_slice(), + siblings.as_slice(), + hashes.as_slice(), + path.as_slice(), + root, + salt, + proof_bytes, + public_inputs_bytes, + ) + .unwrap(); + + Box::into_raw(Box::new(ProofCtx::new(proof_bytes, public_inputs_bytes))) +} + +#[no_mangle] +/// # Safety +/// +/// Should be called on a valid proof and public inputs previously generated by prove +pub unsafe extern "C" fn verify( + prover_ptr: *mut StorageProofs, + proof: *const Buffer, + public_inputs: *const Buffer, +) -> bool { + let proof = std::slice::from_raw_parts((*proof).data, (*proof).len); + let public_inputs = std::slice::from_raw_parts((*public_inputs).data, (*public_inputs).len); + let mut _prover = &mut *prover_ptr; + _prover.verify(proof, public_inputs).is_ok() +} + +/// # Safety +/// +/// Use on a valid pointer to StorageProofs or panics +#[no_mangle] +pub unsafe extern "C" fn free_prover(prover: *mut StorageProofs) { + if prover.is_null() { + return; + } + + unsafe { drop(Box::from_raw(prover)) } +} + +/// # Safety +/// +/// Use on a valid pointer to ProofCtx or panics +#[no_mangle] +pub unsafe extern "C" fn free_proof_ctx(ctx: *mut ProofCtx) { + if ctx.is_null() { + return; + } + + drop(Box::from_raw(ctx)) +} + +#[cfg(test)] +mod tests { + use ark_std::rand::{distributions::Alphanumeric, rngs::ThreadRng, Rng}; + use rs_poseidon::poseidon::hash; + use ruint::aliases::U256; + + use crate::{ + circuit_tests::utils::{digest, treehash}, + }; + + use super::{init, prove, Buffer}; + + #[test] + fn test_storer_ffi() { + // generate a tuple of (preimages, hash), where preimages is a vector of 256 U256s + // and hash is the hash of each vector generated using the digest function + let data = (0..4) + .map(|_| { + let rng = ThreadRng::default(); + let preimages: Vec = rng + .sample_iter(Alphanumeric) + .take(256) + .map(|c| U256::from(c)) + .collect(); + let hash = digest(&preimages, Some(16)); + (preimages, hash) + }) + .collect::, U256)>>(); + + let chunks: Vec = data + .iter() + .map(|c| { + c.0.iter() + .map(|c| c.to_le_bytes_vec()) + .flatten() + .collect::>() + }) + .flatten() + .collect(); + + let hashes: Vec = data.iter().map(|c| c.1).collect(); + let hashes_slice: Vec = hashes.iter().map(|c| c.to_le_bytes_vec()).flatten().collect(); + + let path = [0, 1, 2, 3]; + let parent_hash_l = hash(&[hashes[0], hashes[1]]); + let parent_hash_r = hash(&[hashes[2], hashes[3]]); + + let sibling_hashes = &[ + hashes[1], + parent_hash_r, + hashes[0], + parent_hash_r, + hashes[3], + parent_hash_l, + hashes[2], + parent_hash_l, + ]; + + let siblings: Vec = sibling_hashes + .iter() + .map(|c| c.to_le_bytes_vec()) + .flatten() + .collect(); + + let root = treehash(hashes.as_slice()); + let chunks_buff = Buffer { + data: chunks.as_ptr() as *const u8, + len: chunks.len(), + }; + + let siblings_buff = Buffer { + data: siblings.as_ptr() as *const u8, + len: siblings.len(), + }; + + let hashes_buff = Buffer { + data: hashes_slice.as_ptr() as *const u8, + len: hashes_slice.len(), + }; + + let root_bytes: [u8; U256::BYTES] = root.to_le_bytes(); + let root_buff = Buffer { + data: root_bytes.as_ptr() as *const u8, + len: root_bytes.len(), + }; + + let r1cs_path = "src/circuit_tests/artifacts/storer-test.r1cs"; + let wasm_path = "src/circuit_tests/artifacts/storer-test_js/storer-test.wasm"; + + let r1cs = &Buffer { + data: r1cs_path.as_ptr(), + len: r1cs_path.len(), + }; + + let wasm = &Buffer { + data: wasm_path.as_ptr(), + len: wasm_path.len(), + }; + + let prover_ptr = unsafe { init(&r1cs, &wasm, std::ptr::null()) }; + let prove_ctx = unsafe { + prove( + prover_ptr, + &chunks_buff as *const Buffer, + &siblings_buff as *const Buffer, + &hashes_buff as *const Buffer, + &path as *const i32, + path.len(), + &root_buff as *const Buffer, // root + &root_buff as *const Buffer, // pubkey + &root_buff as *const Buffer, // salt/block hash + ) + }; + + assert!(prove_ctx.is_null() == false); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b2797c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod ffi; +pub mod storage_proofs; +mod circuit_tests; diff --git a/src/storage_proofs.rs b/src/storage_proofs.rs new file mode 100644 index 0000000..09bd570 --- /dev/null +++ b/src/storage_proofs.rs @@ -0,0 +1,98 @@ +use std::fs::File; + +use ark_bn254::{Bn254, Fr}; +use ark_circom::{read_zkey, CircomBuilder, CircomConfig}; +use ark_groth16::{ + create_random_proof as prove, generate_random_parameters, prepare_verifying_key, verify_proof, + Proof, ProvingKey, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read}; +use ark_std::rand::rngs::ThreadRng; +use ruint::aliases::U256; + +#[derive(Debug, Clone)] +pub struct StorageProofs { + builder: CircomBuilder, + params: ProvingKey, + rng: ThreadRng, +} + +impl StorageProofs { + // TODO: add rng + pub fn new( + wtns: String, + r1cs: String, + zkey: Option, /* , rng: Option */ + ) -> Self { + let mut rng = ThreadRng::default(); + let builder = CircomBuilder::new(CircomConfig::::new(wtns, r1cs).unwrap()); + let params: ProvingKey = match zkey { + Some(zkey) => { + let mut file = File::open(zkey).unwrap(); + read_zkey(&mut file).unwrap().0 + } + None => generate_random_parameters::(builder.setup(), &mut rng).unwrap(), + }; + + Self { + builder, + params, + rng, + } + } + + pub fn prove( + &mut self, + chunks: &[U256], + siblings: &[U256], + hashes: &[U256], + path: &[i32], + root: U256, + salt: U256, + proof_bytes: &mut Vec, + public_inputs_bytes: &mut Vec, + ) -> Result<(), String> { + let mut builder = self.builder.clone(); + + // vec of vecs is flattened, since wasm expects a contiguous array in memory + chunks.iter().for_each(|c| builder.push_input("chunks", *c)); + + siblings + .iter() + .for_each(|c| builder.push_input("siblings", *c)); + + hashes.iter().for_each(|c| builder.push_input("hashes", *c)); + path.iter().for_each(|c| builder.push_input("path", *c)); + + builder.push_input("root", root); + builder.push_input("salt", salt); + + let circuit = builder.build().map_err(|e| e.to_string())?; + let inputs = circuit + .get_public_inputs() + .ok_or("Unable to get public inputs!")?; + let proof = prove(circuit, &self.params, &mut self.rng).map_err(|e| e.to_string())?; + + proof.serialize(proof_bytes).map_err(|e| e.to_string())?; + inputs + .serialize(public_inputs_bytes) + .map_err(|e| e.to_string())?; + + Ok(()) + } + + pub fn verify( + &mut self, + proof_bytes: RR, + mut public_inputs: RR, + ) -> Result<(), String> { + let inputs: Vec = + CanonicalDeserialize::deserialize(&mut public_inputs).map_err(|e| e.to_string())?; + let proof = Proof::::deserialize(proof_bytes).map_err(|e| e.to_string())?; + let vk = prepare_verifying_key(&self.params.vk); + + verify_proof(&vk, &proof, inputs.as_slice()).map_err(|e| e.to_string())?; + + Ok(()) + } +} diff --git a/test/circuits/storer-test.circom b/test/circuits/storer-test.circom new file mode 100644 index 0000000..2d7a8e7 --- /dev/null +++ b/test/circuits/storer-test.circom @@ -0,0 +1,5 @@ +pragma circom 2.1.0; + +include "../../circuits/storer.circom"; + +component main { public [root, salt] } = StorageProver(32, 4, 2, 5); diff --git a/test/circuits/storer_test.circom b/test/circuits/storer_test.circom deleted file mode 100644 index 7ca569c..0000000 --- a/test/circuits/storer_test.circom +++ /dev/null @@ -1,5 +0,0 @@ -pragma circom 2.1.0; - -include "../../circuits/storer.circom"; - -component main = StorageProver(32, 4, 2); diff --git a/test/storer.js b/test/storer.js index 7ead36b..94ff637 100644 --- a/test/storer.js +++ b/test/storer.js @@ -3,25 +3,39 @@ const path = require("path"); const crypto = require("crypto"); const F1Field = require("ffjavascript").F1Field; const Scalar = require("ffjavascript").Scalar; -const mimc7 = require("circomlibjs").mimc7; -const mimcsponge = require("circomlibjs").mimcsponge; -const { MerkleTree } = require("merkletreejs"); const {c} = require("circom_tester"); const chaiAsPromised = require('chai-as-promised'); +const poseidon = require("circomlibjs/src/poseidon"); +const wasm_tester = require("circom_tester").wasm; +// const snarkjs = require("snarkjs"); +// const fs = require("fs"); chai.use(chaiAsPromised); -exports.p = Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"); -const Fr = new F1Field(exports.p); +const p = Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"); +const Fr = new F1Field(p); const assert = chai.assert; const expect = chai.expect; -const wasm_tester = require("circom_tester").wasm; -const key = BigInt(2); +function digest(input, chunkSize = 5) { + let chunks = Math.ceil(input.length / chunkSize); + let concat = []; -const digest = (buf) => mimc7.hash(buf, key); -const digestMulti = (buf) => mimc7.multiHash(buf, key); + for (let i = 0; i < chunks; i++) { + let chunk = input.slice(i * chunkSize, (i + 1) * chunkSize); + if (chunk.length < chunkSize) { + chunk = chunk.concat(Array(chunkSize - chunk.length).fill(0)); + } + concat.push(poseidon(chunk)); + } + + if (concat.length > 1) { + return poseidon(concat); + } + + return concat[0] +} function merkelize(leafs) { // simple merkle root (treehash) generator @@ -33,12 +47,12 @@ function merkelize(leafs) { var i = 0; while (i < merkle.length) { - newMerkle.push(digestMulti([merkle[i], merkle[i + 1]])); + newMerkle.push(digest([merkle[i], merkle[i + 1]], 2)); i += 2; } if (merkle.length % 2 == 1) { - newMerkle.add(digestMulti([merkle[merkle.length - 2], merkle[merkle.length - 2]])); + newMerkle.add(digest([merkle[merkle.length - 2], merkle[merkle.length - 2]], 2)); } merkle = newMerkle; @@ -47,57 +61,71 @@ function merkelize(leafs) { return merkle[0]; } +// TODO: should be removed at some point, as the rust test should be sufficient, but left here for now to aid debugging + describe("Storer test", function () { this.timeout(100000); - const a = Array.from(crypto.randomBytes(32).values()).map((v) => BigInt(v)); - const aHash = digestMulti(a); - const b = Array.from(crypto.randomBytes(32).values()).map((v) => BigInt(v)); - const bHash = digestMulti(b); - const c = Array.from(crypto.randomBytes(32).values()).map((v) => BigInt(v)); - const cHash = digestMulti(c); - const d = Array.from(crypto.randomBytes(32).values()).map((v) => BigInt(v)); - const dHash = digestMulti(d); - const salt = Array.from(crypto.randomBytes(32).values()).map((v) => BigInt(v)); - const saltHash = digestMulti(salt); + const a = Array.from(crypto.randomBytes(256).values()).map((v) => BigInt(v)); + const aHash = digest(a, 16); + const b = Array.from(crypto.randomBytes(256).values()).map((v) => BigInt(v)); + const bHash = digest(b, 16); + const c = Array.from(crypto.randomBytes(256).values()).map((v) => BigInt(v)); + const cHash = digest(c, 16); + const d = Array.from(crypto.randomBytes(256).values()).map((v) => BigInt(v)); + const dHash = digest(d, 16); + const salt = Array.from(crypto.randomBytes(256).values()).map((v) => BigInt(v)); + const saltHash = digest(salt, 16); it("Should merkelize", async () => { let root = merkelize([aHash, bHash]); - let hash = digestMulti([aHash, bHash]); + let hash = digest([aHash, bHash], 2); assert.equal(hash, root); }); it("Should verify chunk is correct and part of dataset", async () => { - const cir = await wasm_tester(path.join(__dirname, "./circuits", "storer_test.circom")); + const cir = await wasm_tester("src/circuit_tests/storer-test.circom"); const root = merkelize([aHash, bHash, cHash, dHash]); - const parentHashL = digestMulti([aHash, bHash]); - const parentHashR = digestMulti([cHash, dHash]); + const parentHashL = digest([aHash, bHash], 2); + const parentHashR = digest([cHash, dHash], 2); await cir.calculateWitness({ "chunks": [[a], [b], [c], [d]], - "siblings": [[bHash, parentHashR], [aHash, parentHashR], [dHash, parentHashL], [cHash, parentHashL]], + "siblings": [ + [bHash, parentHashR], + [aHash, parentHashR], + [dHash, parentHashL], + [cHash, parentHashL]], "hashes": [aHash, bHash, cHash, dHash], "path": [0, 1, 2, 3], "root": root, "salt": saltHash, }, true); - }).timeout(100000); + }); - it("Should verify chunk is correct and part of dataset", async () => { - const cir = await wasm_tester(path.join(__dirname, "./circuits", "storer_test.circom")); + it("Should verify chunk is not correct and part of dataset", async () => { + const cir = await wasm_tester("src/circuit_tests/storer-test.circom"); const root = merkelize([aHash, bHash, cHash, dHash]); - const parentHashL = digestMulti([aHash, bHash]); - const parentHashR = digestMulti([cHash, dHash]); + const parentHashL = digest([aHash, bHash], 2); + const parentHashR = digest([cHash, dHash], 2); const fn = async () => { return await cir.calculateWitness({ - "chunks": [[salt], [b], [c], [d]], - "siblings": [[bHash, parentHashR], [aHash, parentHashR], [dHash, parentHashL], [cHash, parentHashL]], + "chunks": [ + [salt], // wrong chunk + [b], + [c], + [d]], + "siblings": [ + [bHash, parentHashR], + [aHash, parentHashR], + [dHash, parentHashL], + [cHash, parentHashL]], "hashes": [saltHash, bHash, cHash, dHash], "path": [0, 1, 2, 3], "root": root, @@ -108,6 +136,33 @@ describe("Storer test", function () { assert.isRejected( fn(), Error, /Error: Error: Assert Failed.\nError in template StorageProver_7 line: 75/); + }); - }).timeout(100000); + function range(start, end) { + return Array(end - start + 1).fill().map((_, idx) => start + idx) + } + + it("Should test poseidon digest", async () => { + const cir = await wasm_tester("src/circuit_tests/poseidon-digest-test.circom"); + let input = range(0, 255).map((c) => BigInt(c)); + await cir.calculateWitness({ + "block": input, + "hash": digest(input, 16), + }); + }); + + // it("Should prove digest with zkey file", async () => { + // let input = range(0, 255).map((c) => BigInt(c)); + // const {proof, publicSignals} = await snarkjs.groth16.fullProve( + // { + // "block": input, + // "hash": digest(input, 16), + // }, + // "src/circuit_tests/artifacts/poseidon-digest-test_js/poseidon-digest-test.wasm", + // "circuit_0000.zkey"); + + // const vKey = JSON.parse(fs.readFileSync("verification_key.json")); + // const res = await snarkjs.groth16.verify(vKey, publicSignals, proof); + // assert(res); + // }); });