Offchain Verification
Rust no_std
Verification
You can verify SP1 Groth16 and Plonk proofs in no_std
environments with sp1-verifier
.
sp1-verifier
is also patched to verify Groth16 and Plonk proofs within the SP1 zkVM, using
bn254 precompiles. For an example of this, see
the Groth16 Example.
Installation
Import the following dependency in your Cargo.toml
. Note that the sp1-verifier
crate was added in version 3.2.1
.
sp1-verifier = {version = "3.2.1", default-features = false}
Usage
sp1-verifier
's interface is very similar to the solidity verifier's. It exposes two public functions:
Groth16Verifier::verify_proof
and PlonkVerifier::verify_proof
.
sp1-verifier
also exposes the Groth16 and Plonk verifying keys as constants, GROTH16_VK_BYTES
and PLONK_VK_BYTES
. These
keys correspond to the current SP1 version's official Groth16 and Plonk verifying keys, which are used for verifying proofs generated
using docker or the prover network.
First, generate your groth16/plonk proof with the SP1 SDK. See here
for more information -- sp1-verifier
and the solidity verifier expect inputs in the same format.
Next, verify the proof with sp1-verifier
. The following snippet is from the Groth16 example program, which verifies a Groth16 proof within SP1 using sp1-verifier
.
//! A program that verifies a Groth16 proof in SP1.
#![no_main]
sp1_zkvm::entrypoint!(main);
use sp1_verifier::Groth16Verifier;
pub fn main() {
// Read the proof, public values, and vkey hash from the input stream.
let proof = sp1_zkvm::io::read_vec();
let sp1_public_values = sp1_zkvm::io::read_vec();
let sp1_vkey_hash: String = sp1_zkvm::io::read();
// Verify the groth16 proof.
let groth16_vk = *sp1_verifier::GROTH16_VK_BYTES;
println!("cycle-tracker-start: verify");
let result = Groth16Verifier::verify(&proof, &sp1_public_values, &sp1_vkey_hash, groth16_vk);
println!("cycle-tracker-end: verify");
match result {
Ok(()) => {
println!("Proof is valid");
}
Err(e) => {
println!("Error verifying proof: {:?}", e);
}
}
}
Here, the proof, public inputs, and vkey hash are read from stdin. See the following snippet to see how these values are generated.
//! A script that generates a Groth16 proof for the Fibonacci program, and verifies the
//! Groth16 proof in SP1.
use sp1_sdk::{include_elf, utils, HashableKey, ProverClient, SP1Stdin};
/// The ELF for the Groth16 verifier program.
const GROTH16_ELF: &[u8] = include_elf!("groth16-verifier-program");
/// The ELF for the Fibonacci program.
const FIBONACCI_ELF: &[u8] = include_elf!("fibonacci-program");
/// Generates the proof, public values, and vkey hash for the Fibonacci program in a format that
/// can be read by `sp1-verifier`.
///
/// Returns the proof bytes, public values, and vkey hash.
fn generate_fibonacci_proof() -> (Vec<u8>, Vec<u8>, String) {
// Create an input stream and write '20' to it.
let n = 20u32;
// The input stream that the program will read from using `sp1_zkvm::io::read`. Note that the
// types of the elements in the input stream must match the types being read in the program.
let mut stdin = SP1Stdin::new();
stdin.write(&n);
// Create a `ProverClient`.
let client = ProverClient::new();
// Generate the groth16 proof for the Fibonacci program.
let (pk, vk) = client.setup(FIBONACCI_ELF);
println!("vk: {:?}", vk.bytes32());
let proof = client.prove(&pk, stdin).groth16().run().unwrap();
(proof.bytes(), proof.public_values.to_vec(), vk.bytes32())
}
fn main() {
// Setup logging.
utils::setup_logger();
// Generate the Fibonacci proof, public values, and vkey hash.
let (fibonacci_proof, fibonacci_public_values, vk) = generate_fibonacci_proof();
// Write the proof, public values, and vkey hash to the input stream.
let mut stdin = SP1Stdin::new();
stdin.write_vec(fibonacci_proof);
stdin.write_vec(fibonacci_public_values);
stdin.write(&vk);
// Create a `ProverClient`.
let client = ProverClient::new();
// Execute the program using the `ProverClient.execute` method, without generating a proof.
let (_, report) = client.execute(GROTH16_ELF, stdin.clone()).run().unwrap();
println!("executed groth16 program with {} cycles", report.total_instruction_count());
println!("{}", report);
}
Note that the SP1 SDK itself is not
no_std
compatible.
Advanced: verify_gnark_proof
sp1-verifier
also exposes Groth16Verifier::verify_gnark_proof
and PlonkVerifier::verify_gnark_proof
,
which verifies any Groth16 or Plonk proof from Gnark. This is especially useful for verifying custom Groth16 and Plonk proofs
efficiently in the SP1 zkVM.
The following snippet demonstrates how you might serialize a Gnark proof in a way that sp1-verifier
can use.
// Write the verifier key.
vkFile, err := os.Create("vk.bin")
if err != nil {
panic(err)
}
defer vkFile.Close()
// Here, `vk` is a `groth16_bn254.VerifyingKey` or `plonk_bn254.VerifyingKey`.
_, err = vk.WriteTo(vkFile)
if err != nil {
panic(err)
}
// Write the proof.
proofFile, err := os.Create("proof.bin")
if err != nil {
panic(err)
}
defer proofFile.Close()
// Here, `proof` is a `groth16_bn254.Proof` or `plonk_bn254.Proof`.
_, err = proof.WriteTo(proofFile)
if err != nil {
panic(err)
}
Public values are serialized as big-endian Fr
values. The default Gnark serialization will work
out of the box.
Wasm Verification
The example-sp1-wasm-verifier
demonstrates how to
verify SP1 proofs in wasm. For a more detailed explanation of the process, please see the README.