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 <dryajov@gmail.com> 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
This commit is contained in:
parent
a0ba322cfd
commit
afcaf4a600
|
@ -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
|
|
@ -1 +1,18 @@
|
|||
node_modules/
|
||||
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
|
||||
|
|
|
@ -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))
|
35
README.md
35
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
|
||||
```
|
|
@ -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<Bn254>,
|
||||
params: ProvingKey<Bn254>,
|
||||
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);
|
|
@ -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/urandom
|
||||
snarkjs powersoftau contribute ../scripts/pot17_bn128_0001.ptau ../scripts/pot17_bn128_0002.ptau >/dev/null 2>&1 </dev/urandom
|
||||
snarkjs powersoftau verify ../scripts/pot17_bn128_0002.ptau
|
||||
snarkjs powersoftau beacon ../scripts/pot17_bn128_0002.ptau ../scripts/pot17_bn128_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon"
|
||||
|
||||
echo "Preparing phase 2"
|
||||
snarkjs powersoftau prepare phase2 ../scripts/pot17_bn128_beacon.ptau ../scripts/pot17_bn128_final.ptau
|
||||
snarkjs powersoftau verify ../scripts/pot17_bn128_final.ptau
|
||||
|
||||
echo "Calculating witness"
|
||||
node ./storer_test_js/generate_witness.js ./storer_test_js/storer_test.wasm ${WITNESS} ./witness.wtns
|
||||
snarkjs wtns check ./storer_test.r1cs ./witness.wtns
|
||||
|
||||
# Benchmark Groth16
|
||||
echo "Benchmarking Groth16..."
|
||||
snarkjs groth16 setup ./storer_test.r1cs ../scripts/pot17_bn128_final.ptau circuit_0000.zkey
|
||||
snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st contributor" >/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
|
||||
|
|
@ -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);
|
|
@ -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/urandom
|
||||
snarkjs powersoftau contribute ../scripts/pot17_bn128_0001.ptau ../scripts/pot17_bn128_0002.ptau >/dev/null 2>&1 </dev/urandom
|
||||
snarkjs powersoftau verify ../scripts/pot17_bn128_0002.ptau
|
||||
snarkjs powersoftau beacon ../scripts/pot17_bn128_0002.ptau ../scripts/pot17_bn128_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon"
|
||||
|
||||
echo "Preparing phase 2"
|
||||
snarkjs powersoftau prepare phase2 ../scripts/pot17_bn128_beacon.ptau ../scripts/pot17_bn128_final.ptau
|
||||
snarkjs powersoftau verify ../scripts/pot17_bn128_final.ptau
|
||||
|
||||
echo "Calculating witness"
|
||||
node ./storer_test_js/generate_witness.js ./storer_test_js/storer_test.wasm ${WITNESS} ./witness.wtns
|
||||
snarkjs wtns check ./storer_test.r1cs ./witness.wtns
|
||||
|
||||
# Benchmark Groth16
|
||||
echo "Benchmarking Groth16..."
|
||||
snarkjs groth16 setup ./storer_test.r1cs ../scripts/pot17_bn128_final.ptau circuit_0000.zkey
|
||||
snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st contributor" >/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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
pragma circom 2.1.0;
|
||||
|
||||
include "./storer.circom";
|
||||
|
||||
component main { public [root, salt] } = StorageProver(256, 80, 32, 16);
|
|
@ -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
|
|
@ -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"
|
||||
|
|
@ -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
|
|
@ -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<Bn254>,
|
||||
params: ProvingKey<Bn254>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl CircuitsTests {
|
||||
pub fn new(wtns: String, r1cs: String) -> CircuitsTests {
|
||||
let mut rng = ThreadRng::default();
|
||||
let builder = CircomBuilder::new(CircomConfig::<Bn254>::new(wtns, r1cs).unwrap());
|
||||
let params =
|
||||
generate_random_parameters::<Bn254, _, _>(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<U256> = (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<U256> = rng
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(256)
|
||||
.map(|c| U256::from(c))
|
||||
.collect();
|
||||
let hash = digest(&preimages, Some(16));
|
||||
(preimages, hash)
|
||||
})
|
||||
.collect::<Vec<(Vec<U256>, U256)>>();
|
||||
|
||||
let chunks: Vec<U256> = data.iter().flat_map(|c| c.0.to_vec()).collect();
|
||||
let hashes: Vec<U256> = 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());
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
|
@ -0,0 +1,5 @@
|
|||
pragma circom 2.1.0;
|
||||
|
||||
include "../../circuits/storer.circom";
|
||||
|
||||
component main { public [root, salt] } = StorageProver(256, 4, 2, 16);
|
|
@ -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<usize>) -> 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<U256> = 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<U256> = 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]
|
||||
}
|
|
@ -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::<Vec<U256>>()
|
||||
};
|
||||
|
||||
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::<Vec<U256>>()
|
||||
};
|
||||
|
||||
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::<Vec<U256>>()
|
||||
};
|
||||
|
||||
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<U256> = rng
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(256)
|
||||
.map(|c| U256::from(c))
|
||||
.collect();
|
||||
let hash = digest(&preimages, Some(16));
|
||||
(preimages, hash)
|
||||
})
|
||||
.collect::<Vec<(Vec<U256>, U256)>>();
|
||||
|
||||
let chunks: Vec<u8> = data
|
||||
.iter()
|
||||
.map(|c| {
|
||||
c.0.iter()
|
||||
.map(|c| c.to_le_bytes_vec())
|
||||
.flatten()
|
||||
.collect::<Vec<u8>>()
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let hashes: Vec<U256> = data.iter().map(|c| c.1).collect();
|
||||
let hashes_slice: Vec<u8> = 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<u8> = 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod ffi;
|
||||
pub mod storage_proofs;
|
||||
mod circuit_tests;
|
|
@ -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<Bn254>,
|
||||
params: ProvingKey<Bn254>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl StorageProofs {
|
||||
// TODO: add rng
|
||||
pub fn new(
|
||||
wtns: String,
|
||||
r1cs: String,
|
||||
zkey: Option<String>, /* , rng: Option<ThreadRng> */
|
||||
) -> Self {
|
||||
let mut rng = ThreadRng::default();
|
||||
let builder = CircomBuilder::new(CircomConfig::<Bn254>::new(wtns, r1cs).unwrap());
|
||||
let params: ProvingKey<Bn254> = match zkey {
|
||||
Some(zkey) => {
|
||||
let mut file = File::open(zkey).unwrap();
|
||||
read_zkey(&mut file).unwrap().0
|
||||
}
|
||||
None => generate_random_parameters::<Bn254, _, _>(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<u8>,
|
||||
public_inputs_bytes: &mut Vec<u8>,
|
||||
) -> 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<RR: Read>(
|
||||
&mut self,
|
||||
proof_bytes: RR,
|
||||
mut public_inputs: RR,
|
||||
) -> Result<(), String> {
|
||||
let inputs: Vec<Fr> =
|
||||
CanonicalDeserialize::deserialize(&mut public_inputs).map_err(|e| e.to_string())?;
|
||||
let proof = Proof::<Bn254>::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(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
pragma circom 2.1.0;
|
||||
|
||||
include "../../circuits/storer.circom";
|
||||
|
||||
component main { public [root, salt] } = StorageProver(32, 4, 2, 5);
|
|
@ -1,5 +0,0 @@
|
|||
pragma circom 2.1.0;
|
||||
|
||||
include "../../circuits/storer.circom";
|
||||
|
||||
component main = StorageProver(32, 4, 2);
|
123
test/storer.js
123
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);
|
||||
// });
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue