Skip to main content

Overview

The verification functions allow you to verify that a zero-knowledge proof is valid. The #[jolt::provable] macro generates two verification-related functions:
  1. build_verifier_<function_name> - Creates a reusable verifier closure (recommended)
  2. Internal verification logic used by the verifier closure

Function Signature

build_verifier

pub fn build_verifier_<function_name>(
    preprocessing: jolt::JoltVerifierPreprocessing<jolt::F, jolt::PCS>,
) -> impl Fn(
    // ... public inputs
    output: ReturnType,
    panic: bool,
    // Optional: trusted_advice_commitment (if function has trusted advice)
    proof: jolt::RV64IMACProof,
) -> bool + Sync + Send

Parameters

Preprocessing:
  • preprocessing - Verifier preprocessing data (obtained from preprocess_verifier_<function_name>)
Verifier Closure Parameters:
  • Public inputs - The same public inputs provided to the prover
  • output - The claimed output value
  • panic - Whether the program panicked during execution
  • trusted_advice_commitment (optional) - Commitment to trusted advice, if present
  • proof - The zero-knowledge proof to verify

Return Value

Returns true if the proof is valid, false otherwise.

Verification Process

The verifier:
  1. Reconstructs the expected I/O device state from public inputs and claimed outputs
  2. Verifies the proof against the preprocessing data
  3. Checks that all constraints are satisfied
  4. Returns a boolean indicating validity

Usage Examples

Basic Verification

use std::time::Instant;

// Setup: compile and preprocess (typically done once)
let target_dir = "/tmp/jolt-guest-targets";
let mut program = guest::compile_fib(target_dir);
let shared_preprocessing = guest::preprocess_shared_fib(&mut program);
let prover_preprocessing = guest::preprocess_prover_fib(shared_preprocessing.clone());
let verifier_setup = prover_preprocessing.generators.to_verifier_setup();
let verifier_preprocessing = guest::preprocess_verifier_fib(
    shared_preprocessing,
    verifier_setup,
);

// Build verifier and prover
let prove_fib = guest::build_prover_fib(program, prover_preprocessing);
let verify_fib = guest::build_verifier_fib(verifier_preprocessing);

// Prove
let input = 50;
let (output, proof, io_device) = prove_fib(input);

// Verify
let is_valid = verify_fib(input, output, io_device.panic, proof);
println!("Proof is valid: {}", is_valid);
assert!(is_valid);

With Multiple Inputs

let a = vec![1usize, 2, 3, 4, 5];
let b = vec![0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let n = 221u8;

// Generate proof
let (output, proof, io_device) = prove_advice_demo(n, a.clone(), b.clone());

// Verify with the same inputs
let is_valid = verify_advice_demo(
    n,
    a,
    b,
    output,
    io_device.panic,
    proof,
);
assert!(is_valid);

With Trusted Advice

let public_input = 42;
let trusted_data = vec![1, 2, 3, 4, 5];

// Prover commits to trusted advice
let (commitment, hint) = guest::commit_trusted_advice_my_function(
    trusted_data.clone(),
    &prover_preprocessing,
);

// Generate proof
let (output, proof, io_device) = prove_my_function(
    program,
    prover_preprocessing,
    public_input,
    trusted_data,
    commitment,
    hint,
);

// Verifier only needs the commitment (not the trusted data)
let is_valid = verify_my_function(
    public_input,
    output,
    io_device.panic,
    commitment,
    proof,
);
assert!(is_valid);

Preprocessing Conversion

You can obtain verifier preprocessing from prover preprocessing:
// From prover preprocessing
let verifier_preprocessing = guest::verifier_preprocessing_from_prover_fib(
    &prover_preprocessing
);

// Or from shared preprocessing + verifier setup
let verifier_setup = prover_preprocessing.generators.to_verifier_setup();
let verifier_preprocessing = guest::preprocess_verifier_fib(
    shared_preprocessing,
    verifier_setup,
);

Generated From

For a function annotated with #[jolt::provable]:
#[jolt::provable]
fn fib(n: u32) -> u32 {
    // implementation
}
The macro generates build_verifier_fib that creates verification closures.

Important Notes

  • The verifier is deterministic - the same inputs and proof always produce the same result
  • The verifier is thread-safe (Sync + Send) and can be used concurrently
  • Verification is typically much faster than proof generation
  • The verifier does not need access to trusted advice data, only its commitment
  • The panic flag from io_device.panic must be passed to the verifier
  • prove - Generates proofs that can be verified
  • preprocess - Generates preprocessing data for verification
  • analyze - Analyzes execution without generating a proof

Build docs developers (and LLMs) love