Skip to main content
Preprocessing is a one-time setup phase that prepares a program for efficient proof generation and verification. Jolt uses a structured preprocessing pipeline that separates shared, prover-specific, and verifier-specific data.

Why Preprocessing?

Preprocessing amortizes expensive computations across multiple proofs:
  • Program analysis - Decode bytecode, build lookup tables, compute memory layout
  • Commitment setup - Generate cryptographic commitment parameters
  • Memory initialization - Preprocess initial RAM state from ELF
  • Zero-knowledge setup - Generate Pedersen generators for ZK mode
After preprocessing, the same preprocessed data can be reused for unlimited proofs of the same program.

Preprocessing Phases

Jolt preprocessing happens in three phases:

Phase 1: Shared Preprocessing

Created by:
Components:
  • BytecodePreprocessing - Program instructions, PC mappings, lookup tables
  • RAMPreprocessing - Initial memory state, bytecode placement addresses
  • MemoryLayout - Address ranges for stack, heap, I/O, advice regions
  • ZK generators - Pedersen commitment generators (ZK mode only)
Usage:

Phase 2A: Prover Preprocessing

Created by:
Components:
  • ProverSetup - Cryptographic commitment keys for Dory/HyperKZG
  • Shared preprocessing - Reference to shared data
Generates commitment generators based on:
  • Maximum trace length
  • Number of committed polynomials
  • Advice region sizes

Phase 2B: Verifier Preprocessing

Created by:
Components:
  • VerifierSetup - Verification keys (smaller than prover setup)
  • Shared preprocessing - Reference to shared data

Memory Layout

The MemoryLayout computed during preprocessing determines guest memory organization:
Memory map:

Bytecode Preprocessing

The bytecode preprocessing phase analyzes the RISC-V binary:
Operations:
  1. Decode ELF - Parse RISC-V instructions from .text section
  2. Build PC mapping - Map program counter values to instruction indices
  3. Compute instruction flags - One-hot encoding for each instruction type
  4. Decompose operands - Split instructions into opcode, rd, rs1, rs2, immediate

RAM Preprocessing

The RAM preprocessing phase handles initial memory state:
Process:
  1. Extract non-zero memory from ELF sections (.data, .rodata, .bss)
  2. Determine program code placement address
  3. Pack initialized data into 64-bit words
  4. Store for efficient polynomial evaluation
Initial RAM includes:
  • Program .text (code)
  • .rodata (read-only data)
  • .data (initialized data)
  • Everything else starts at zero

Serialization

Preprocessing data can be saved and loaded:
Typical sizes:
  • Shared preprocessing: ~1-10 MB (depends on program size)
  • Prover preprocessing: ~10-100 MB (depends on trace length)
  • Verifier preprocessing: ~1-10 MB (much smaller than prover)

ZK Mode Preprocessing

When compiled with --features zk, preprocessing includes additional setup:
ZK generators are used for:
  • Committing to sumcheck round polynomials
  • BlindFold R1CS witness commitments
  • Hiding polynomial evaluations
See the ZK feature flag documentation for details on zero-knowledge mode.

Generated Preprocessing Functions

The #[jolt::provable] macro generates preprocessing helpers:

preprocess_shared_*

Decodes the ELF and creates JoltSharedPreprocessing with the configured memory layout.

preprocess_prover_*

Generates prover commitment parameters.

preprocess_verifier_*

Creates verifier preprocessing from shared preprocessing and generators.

verifier_preprocessing_from_prover_*

Convenience function to derive verifier preprocessing from prover preprocessing.

Best Practices

Preprocessing is expensive (seconds to minutes). Reuse the same preprocessing for all proofs of a program.
Save preprocessing to disk. Loading from disk is much faster than regenerating.
The actual execution trace must fit within max_trace_length. Pad trace lengths to powers of 2 for efficiency.
Verifiers only need JoltVerifierPreprocessing, which is much smaller than prover preprocessing.

Complete Example

Performance Characteristics

Preprocessing time:
  • Small programs (less than 1K instructions): ~1-5 seconds
  • Medium programs (~10K instructions): ~10-30 seconds
  • Large programs (~100K instructions): ~1-5 minutes
Proving time (after preprocessing):
  • Simple proofs (less than 10K cycles): ~1-10 seconds
  • Complex proofs (~100K cycles): ~10-60 seconds
  • Very large proofs (~1M cycles): ~1-10 minutes
Preprocessing time is amortized across all proofs. The more proofs you generate, the less preprocessing overhead matters.