Verify Groth16 proofs generated by Circom/SnarkJS inside the RISC Zero zkVM, enabling proof recursion and cross-system verification.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/risc0/risc0/llms.txt
Use this file to discover all available pages before exploring further.
What You’ll Learn
- Verifying Groth16 proofs in the zkVM
- Working with Circom-generated proofs
- Proof system composition
- Performance optimization with DEV_MODE
This is an experimental example demonstrating proof system interoperability.
Overview
This example verifies a Groth16 proof (from another zkSNARK system) inside the RISC Zero zkVM, producing a RISC Zero receipt that proves the Groth16 proof is valid.Why Verify Proofs in zkVM?
- Proof Recursion: Compress multiple proofs into one
- Cross-System Verification: Bridge different proof systems
- Flexible Verification: Add custom logic around proof checking
- Standardization: Use RISC Zero for all verifications
Example Use Case
The example uses a pre-generated Groth16 proof of a Sudoku solution from zkSudoku.How It Works
use risc0_groth16::{ProofJson, PublicInputsJson, Verifier, VerifyingKeyJson};
use risc0_zkvm::{guest::env, sha::Digestible};
pub fn main() {
// Read proof components
let (proof_json, public_inputs_json, verifying_key_json): (
ProofJson,
PublicInputsJson,
VerifyingKeyJson,
) = env::read();
// Verify the Groth16 proof
Verifier::from_json(
proof_json,
public_inputs_json.clone(),
verifying_key_json.clone(),
)
.unwrap()
.verify()
.unwrap();
// Commit hashes to journal
let verifying_key_digest = verifying_key_json
.verifying_key()
.unwrap()
.digest();
let public_inputs_digest = public_inputs_json
.to_scalar()
.unwrap()
.digest();
env::commit(&(verifying_key_digest, public_inputs_digest));
}
use groth16_verifier_methods::{GROTH16_VERIFIER_ELF, GROTH16_VERIFIER_ID};
use risc0_groth16::{load_proof_from_file, load_public_inputs, load_verifying_key};
use risc0_zkvm::{ExecutorEnv, default_prover};
fn main() {
// Load Groth16 proof artifacts
let proof_json = load_proof_from_file("proof.json").unwrap();
let public_inputs_json = load_public_inputs("public.json").unwrap();
let verifying_key_json = load_verifying_key("verification_key.json").unwrap();
// Create zkVM environment
let env = ExecutorEnv::builder()
.write(&(proof_json, public_inputs_json, verifying_key_json))
.unwrap()
.build()
.unwrap();
// Generate RISC Zero proof
let receipt = default_prover()
.prove(env, GROTH16_VERIFIER_ELF)
.unwrap()
.receipt;
// Verify the receipt
receipt.verify(GROTH16_VERIFIER_ID).unwrap();
println!("Successfully verified Groth16 proof in zkVM!");
}
Running the Example
This example requires ~500M cycles and will take significant time to prove locally. Use DEV_MODE for testing.
With DEV_MODE (Fast)
Full Proving (Slow)
Using Bonsai (Recommended)
For production, use Bonsai for remote proving:What Gets Proven?
The RISC Zero receipt proves:- Valid Groth16 Proof: The Groth16 proof verifies correctly
- Correct Circuit: Verification key hash identifies the circuit
- Public Inputs: Public input hash commits to the statement
- Verification Integrity: All checks passed in the zkVM
Groth16 Background
What is Groth16?
Groth16 is a zkSNARK proof system with:- Constant Size: ~128 bytes regardless of circuit
- Fast Verification: Milliseconds onchain
- Trusted Setup: Requires ceremony per circuit
- Pairing-Based: Uses elliptic curve pairings
Verification Equation
Groth16 verifies by checking:e: Pairing function on BN254 curveA, C: Proof elements in G1B: Proof element in G2α, β, γ, δ: Verification key parametersL: Linear combination of public inputs
Working with Circom
Circom Circuit
Example Sudoku circuit:Generate Groth16 Proof
Using Circom and SnarkJS:Proof Artifacts
proof.json
Contains the Groth16 proof:verification_key.json
Contains the verification key:public.json
Contains public inputs:Performance
| Metric | Value |
|---|---|
| Cycles | ~500M |
| Proving time (local) | 10-30 minutes |
| Proving time (Bonsai) | 1-2 minutes |
| Memory usage | ~8 GB |
| Receipt size | ~128 KB |
Optimization Tips
- Use DEV_MODE: For development and testing
- Bonsai: For production proving
- Batch Verification: Verify multiple proofs in one zkVM session
- Precomputation: Cache verification keys
Use Cases
Proof Aggregation
Combine multiple Groth16 proofs:- Constant verification cost onchain
- Batch processing efficiency
- Simplified integration
Cross-Chain Bridges
Verify proofs from other chains:Privacy-Preserving Audits
Verify compliance proofs:ZK Rollup Verification
Verify rollup state transitions:Comparison to Native Verification
| Approach | Gas Cost (ETH) | Latency | Flexibility |
|---|---|---|---|
| Native Groth16 | ~300K gas | Instant | Fixed |
| zkVM Verification | ~300K gas* | Async | Customizable |
When to Use zkVM Verification?
Use zkVM when:- Aggregating multiple proofs
- Adding custom verification logic
- Bridging to chains without Groth16 precompiles
- Standardizing on RISC Zero infrastructure
- Single proof verification
- Minimizing latency
- Ethereum mainnet (has precompiles)
Integration with Other Examples
This example uses BN254 pairings (see BN254 example):Trying Other Circuits
You can verify any Circom circuit:ECDSA Circuit
From 0xPARC circom-ecdsa:Custom Circuits
- Write your Circom circuit
- Generate proof with SnarkJS
- Load proof artifacts in this example
- Verify in zkVM
Security Considerations
Trusted Setup
Groth16 requires a trusted setup ceremony:- Circuit-specific: Each circuit needs its own setup
- Toxic waste: Setup parameters must be destroyed
- Powers of Tau: Can reuse universal setup then specialize
Verification Key Binding
The example commitsverifying_key_digest to prevent:
- Proof substitution attacks
- Circuit confusion
- Parameter tampering
Next Steps
- Study BN254 pairing operations
- Learn Circom
- Explore SnarkJS
- Try 0xPARC circuits
- Build on Bonsai