Skip to main content
Version: 3.4.0

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.