This repo demonstrates how to verify Groth16 and Plonk proofs in browser. We wrap the ziren-verifier
crate in wasm bindings, and invoke it from javascript.
verifier
: The rust Ziren verifier crate with wasm bindings.example/eth_wasm
: A short javascript example that verifies an ETH proof in wasm.example/guest
: A simple fibonacci Ziren guest to verify.example/host
: A simple host to generate proofs in a json format.example/wasm_example
: A short javascript example that verifies proofs in wasm.
First, generate the wasm library for the verifier. From the verifier
directory, run
wasm-pack build --target nodejs --dev
This will generate wasm bindings for the rust functions in verifier/src/lib.rs
.
Note: generating wasm bindings in dev mode will result in drastically slower verification times. Generate bindings in release mode by replacing
--dev
with--release
.
As an example, the following snippet provides wasm bindings for the verify_groth16
function:
#[wasm_bindgen]
pub fn verify_groth16(proof: &[u8], public_inputs: &[u8], zkm_vk_hash: &str) -> bool {
Groth16Verifier::verify(proof, public_inputs, zkm_vk_hash, *GROTH16_VK_BYTES).is_ok()
}
Next, run the host to generate fibonacci_groth16_proof.json
and fibonacci_plonk_proof.json
. From the example/host
directory, run:
cargo run --release -- --mode stark
cargo run --release -- --mode groth16
cargo run --release -- --mode plonk
By default, this will not generate fresh proofs from the program in example/guest
. To generate fresh proofs, run:
cargo run --release -- --mode stark --prove
cargo run --release -- --mode groth16 --prove
cargo run --release -- --mode plonk --prove
Here, stark, groth16 and plonk proofs are generated using client.prove(&pk, stdin).compressed().run()
, client.prove(&pk, stdin).groth16().run()
and client.prove(&pk, stdin).plonk().run()
, respectively.
See the Ziren docs for more details.
From a ZKMProofWithPublicValues
,
we extract the proof and public inputs, and serialize the appropriate fields. See the following snippet for details:
// Load the proof and extract the proof and public inputs.
let proof = ZKMProofWithPublicValues::load(&proof_path).expect("Failed to load proof");
let fixture = ProofData {
proof: hex::encode(proof.bytes()),
public_inputs: hex::encode(proof.public_values),
vkey_hash: vk.bytes32(),
vkey,
zkm_version: proof.zkm_version,
mode: args.mode,
};
// Serialize the proof data to a JSON file.
let json_proof = serde_json::to_string(&fixture).expect("Failed to serialize proof");
std::fs::write(json_path, json_proof).expect("Failed to write JSON proof");
To verify proofs in wasm, run the following command from the example/wasm_example
directory:
pnpm install
pnpm run test
This runs main.js
, which verifies the proofs in example/json
.
The proofs are decoded from hex strings and verified using the wasm bindings. In addition, the public inputs
are deserialized into 32-bit integers and printed. See the following snippet for details:
// Read and parse the JSON content of the file
const fileContent = fs.readFileSync(path.join("../json", file), 'utf8');
const proof_json = JSON.parse(fileContent);
// Determine the ZKP type (Groth16 or Plonk) based on the filename
const zkpType = file_name.includes('groth16') ? 'groth16' : file_name.includes('plonk')? 'plonk' : 'stark';
const proof = fromHexString(proof_json.proof);
const public_inputs = fromHexString(proof_json.public_inputs);
const vkey_hash = proof_json.vkey_hash;
// Get the values using DataView.
const view = new DataView(public_inputs.buffer);
// Read each 32-bit (4 byte) integer as little-endian
const n = view.getUint32(0, true);
const a = view.getUint32(4, true);
const b = view.getUint32(8, true);
console.log(`n: ${n}`);
console.log(`a: ${a}`);
console.log(`b: ${b}`);
if (zkpType == 'stark') {
const vkey = fromHexString(proof_json.vkey);
const startTime = performance.now();
const result = wasm.verify_stark(proof, public_inputs, vkey);
const endTime = performance.now();
console.log(`${zkpType} verification took ${endTime - startTime}ms`);
assert(result);
console.log(`Proof in ${file} is valid.`);
} else {
// Select the appropriate verification function and verification key based on ZKP type
const verifyFunction = zkpType === 'groth16' ? wasm.verify_groth16 : wasm.verify_plonk;
const startTime = performance.now();
const result = verifyFunction(proof, public_inputs, vkey_hash);
const endTime = performance.now();
console.log(`${zkpType} verification took ${endTime - startTime}ms`);
assert(result);
console.log(`Proof in ${file} is valid.`);
}
To verify an ETH proof in wasm, run the following command from the example/eth_wasm
directory:
pnpm install
pnpm run test
This runs main.js
, which verifies an ETH proof in example/binaries
.
The proof is downloaded from https://ethproofs.org. And the vk is downloaded from Ziren prover network.
See the following snippet for details:
import * as wasm from "../../verifier/pkg/zkm_wasm_verifier.js"
import fs from 'node:fs'
const vkey = fs.readFileSync('../binaries/eth_vk.bin');
// Download the proof from https://ethproofs.org/blocks/23174100 > ZKM
const proof = fs.readFileSync('../binaries/23174100_ZKM_167157.txt');
const startTime = performance.now();
const result = wasm.verify_stark_proof(proof, vkey);
const endTime = performance.now();
console.log(`stark verification took ${endTime - startTime}ms`);
console.assert(result, "result:", result, "proof should be valid");
console.log(`ETH proof is valid.`);
The wasm STARK verifier is used by @ethproofs/ziren-wasm-stark-verifier