Skip to main content

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.

RISC Zero can convert STARK proofs to Groth16 SNARKs, enabling efficient verification on blockchains like Ethereum. Groth16 proofs are constant-size (around 200-300 bytes) and can be verified in a single smart contract transaction.

Overview

The STARK-to-SNARK conversion process (also called “shrink wrapping”) transforms a RISC Zero STARK proof into a Groth16 proof:
  • Input: SuccinctReceipt (STARK proof, ~200kB)
  • Output: Groth16Receipt (SNARK proof, ~300 bytes)
  • Verification: On-chain using the RISC Zero Verifier Contract
Platform Requirements: Groth16 proving currently only works on x86 architecture. Apple Silicon (M-series) is not supported, even via Docker. See #1520 and #1749.

Generating Groth16 Proofs

The simplest way to generate a Groth16 proof:
use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts};
use methods::{METHOD_ELF, METHOD_ID};

fn main() {
    // Build execution environment
    let env = ExecutorEnv::builder()
        .write(&input_data).unwrap()
        .build().unwrap();
    
    // Generate Groth16 proof directly
    let opts = ProverOpts::groth16();
    let receipt = default_prover()
        .prove_with_opts(env, METHOD_ELF, &opts)
        .unwrap()
        .receipt;
    
    // Verify the Groth16 receipt
    receipt.verify(METHOD_ID).unwrap();
    
    // Receipt is now ready for on-chain verification
    println!("Groth16 proof generated successfully!");
}

Using Bonsai SDK

Generate Groth16 proofs remotely via Bonsai:
use std::time::Duration;
use bonsai_sdk::blocking::Client;
use risc0_zkvm::{compute_image_id, Receipt};

fn prove_groth16_on_bonsai(input_data: Vec<u8>) -> Result<Receipt> {
    let client = Client::from_env(risc0_zkvm::VERSION)?;
    
    // Upload image and input (same as regular proving)
    let image_id = hex::encode(compute_image_id(METHOD_ELF)?);
    client.upload_img(&image_id, METHOD_ELF.to_vec())?;
    let input_id = client.upload_input(input_data)?;
    
    // Create proving session
    let session = client.create_session(
        image_id,
        input_id,
        vec![],  // assumptions
        false    // execute_only
    )?;
    
    // Wait for STARK proof to complete
    let stark_session_id = loop {
        let status = session.status(&client)?;
        match status.status.as_str() {
            "SUCCEEDED" => break session.uuid.clone(),
            "RUNNING" => {
                std::thread::sleep(Duration::from_secs(15));
                continue;
            }
            _ => panic!("Session failed: {:?}", status.error_msg),
        }
    };
    
    // Convert STARK to SNARK (Groth16)
    let snark_session = client.create_snark(stark_session_id)?;
    
    // Wait for Groth16 proof
    loop {
        let status = snark_session.status(&client)?;
        match status.status.as_str() {
            "SUCCEEDED" => {
                let snark_url = status.output.unwrap();
                let snark_buf = client.download(&snark_url)?;
                let receipt: Receipt = bincode::deserialize(&snark_buf)?;
                return Ok(receipt);
            }
            "RUNNING" => {
                eprintln!("SNARK proving in progress...");
                std::thread::sleep(Duration::from_secs(15));
            }
            _ => panic!("SNARK failed: {:?}", status.error_msg),
        }
    }
}
See Bonsai SDK for more details on remote proving.

Proving Pipeline

The complete proving pipeline for generating a Groth16 proof:
1
Execute the Program
2
Run the zkVM to generate execution segments:
3
let session = ExecutorImpl::from_elf(env, elf)?.run()?;
4
Prove Segments
5
Generate STARK proofs for each execution segment:
6
for segment in session.segments() {
    let segment_receipt = prove_segment(&segment)?;
    segments.push(segment_receipt);
}
7
Lift to Recursion Circuit
8
Convert each segment proof to use the recursion circuit:
9
let succinct_receipt = lift(segment_receipt)?;
10
Join Segments
11
Recursively join all segment proofs into a single proof:
12
let mut current = succinct_receipt_1;
for receipt in remaining_receipts {
    current = join(current, receipt)?;
}
13
Identity P254
14
Prepare the proof for Groth16 conversion:
15
let identity_receipt = identity_p254(final_succinct_receipt)?;
16
Compress to Groth16
17
Generate the final Groth16 SNARK:
18
let groth16_receipt = compress(identity_receipt)?;
When using ProverOpts::groth16(), all these steps are handled automatically.

Receipt Types Comparison

PropertyCompositeSuccinctGroth16
SizeLinear (~100kB-MB)Constant (~200kB)Constant (~300 bytes)
Generation TimeFastMediumSlow
Verification TimeLinearConstant (off-chain)Constant (on-chain)
Blockchain ReadyNoNoYes
Supports CompositionYesYesYes
Use CaseDevelopmentOff-chain verificationOn-chain verification

Low-Level API

For advanced use cases, you can use the low-level risc0-groth16 crate:

Shrink Wrap Function

use risc0_groth16::prove::shrink_wrap;

// Convert an identity_p254 seal to Groth16
let identity_p254_seal_bytes = /* ... */;
let groth16_seal = shrink_wrap(identity_p254_seal_bytes)?;

Verification

use risc0_groth16::{Verifier, ProofJson, PublicInputsJson, VerifyingKeyJson};

// Load verification data
let verifying_key: VerifyingKeyJson = serde_json::from_str(vk_json)?;
let proof: ProofJson = serde_json::from_str(proof_json)?;
let public_inputs = PublicInputsJson {
    values: serde_json::from_str(inputs_json)?,
};

// Verify the Groth16 proof
let verifier = Verifier::from_json(proof, public_inputs, verifying_key)?;
verifier.verify()?;

Docker vs CUDA

The Groth16 prover can use either Docker or CUDA:
# Uses Docker for Groth16 proving
cargo run --release
CUDA-based Groth16 proving requires:
  • NVIDIA GPU with CUDA support
  • CUDA toolkit installed
  • cuda feature flag enabled

On-Chain Verification

Once you have a Groth16 receipt, you can verify it on Ethereum:
// Solidity contract using RiscZeroVerifier
import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol";

contract MyContract {
    IRiscZeroVerifier public verifier;
    bytes32 public imageId;
    
    function verifyProof(
        bytes calldata seal,
        bytes32 journalHash
    ) public view returns (bool) {
        // Verify the Groth16 proof on-chain
        verifier.verify(seal, imageId, journalHash);
        return true;
    }
}
See the RISC Zero Verifier Contract documentation for integration details.

Performance Considerations

Proving Time

  • Composite → Succinct: Depends on execution length (recursion overhead)
  • Succinct → Groth16: Fixed time (~5-10 minutes on GPU, longer on CPU)

When to Use Groth16

Use Groth16 when:
  • Verifying proofs on Ethereum or other EVM chains
  • Minimizing verification cost is critical
  • Proof size must be minimized (e.g., for storage)
Avoid Groth16 when:
  • Only verifying off-chain (use Succinct instead)
  • Proving time is critical (use Composite for development)
  • Running on non-x86 architecture

Troubleshooting

Docker Issues

# Ensure Docker is running
sudo systemctl start docker

# Verify Docker installation
docker --version

CUDA Issues

# Check CUDA installation
nvcc --version

# Verify GPU is detected
nvidia-smi

Platform Compatibility

If you encounter “platform not supported” errors on Apple Silicon, you must use an x86 machine for Groth16 proving. Consider using Bonsai remote proving instead.

Examples

Groth16 Proof Directory

Installation and setup instructions

Blockchain Integration

Deploy and verify on Ethereum

risc0-groth16 Crate

Low-level Groth16 API documentation

Next Steps

Bonsai SDK

Generate Groth16 proofs remotely

Proof Composition

Compose multiple proofs efficiently

Local Proving

Set up local proving infrastructure