This repo demonstrates how to verify "compressed," Groth16, and Plonk proofs in a browser. We wrap the sp1-verifier
crate in Wasm bindings, and invoke it from JavaScript (Node.js).
In SP1, proofs are produced in multiple stages:
- SP1 splits a single execution of a RISC-V program into several "shards" and proves each of them independently.
- SP1 combines these proofs through a process called "recursion" into a single proof, which we call a "compressed" proof.
- Finally, SP1 wraps the compressed proof in a non-interactive ZK (NIZK) proof, which may either be Groth16 or Plonk.
The sp1-verifier
crate supports verifying proofs from either of the last two stages, which are precisely the kind of proofs that describe the entirety of a RISC-V program's execution.
⚠️ NOTE: At the moment,sp1-verifier
does not support single-shard compressed proofs. This is only of concern for short program executions, since long program executions inevitably require multiple shards. Furthermore,sp1-verifier
provides an informative error when it detects a single-shard compressed proof.
verifier
: A thin wrapper aroundsp1-verifier
used to generate a Wasm module and bindings.example/fibonacci-program
: A simple fibonacci SP1 program to verify.example/fibonacci-script
: A simple script to generate proofs in a JSON format.example/wasm_example
: A short JavaScript example that verifies proofs in Wasm.
Use the Just recipes in the justfile
. Given a fresh clone of this repository, the init
recipe performs all tasks needed to setup and run the example.
To manually perform these tasks, follow the instructions below.
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], sp1_vk_hash: &str) -> bool {
Groth16Verifier::verify(proof, public_inputs, sp1_vk_hash, *GROTH16_VK_BYTES).is_ok()
}
To generate and save proofs for the first time, in the example/script
directory, run cargo run --release -- --mode <mode> --prove
, where <mode>
is one of groth16, plonk, compressed
. This will do two things:
- Using SP1, generate and save proofs to the
example/binaries
directory. - Encode the saved proofs to JSON and write them to
example/json
.
After the proofs are saved to example/binaries
for the first time, one may omit the --prove
flag to skip the first step.
For the proof generation, saving, and serialization logic, see example/script/src/main.rs
.
To verify proofs in Wasm, run the following commands from the example/wasm_example
directory:
# Install Node.js dependencies.
pnpm install
# Run the Node.js test program.
pnpm run test
This runs main.js
, which first reads the proof data from the artifacts in example/json
into a Wasm-friendly format. Then, for each proof, it calls the appropriate verification function via the generated Wasm bindings. Finally, it prints verification durations and (if they are easily available) the public values.