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
Preprocessing Phases
Jolt preprocessing happens in three phases:Phase 1: Shared Preprocessing
- 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)
Phase 2A: Prover Preprocessing
- ProverSetup - Cryptographic commitment keys for Dory/HyperKZG
- Shared preprocessing - Reference to shared data
- Maximum trace length
- Number of committed polynomials
- Advice region sizes
Phase 2B: Verifier Preprocessing
- VerifierSetup - Verification keys (smaller than prover setup)
- Shared preprocessing - Reference to shared data
Memory Layout
TheMemoryLayout computed during preprocessing determines guest memory organization:
Bytecode Preprocessing
The bytecode preprocessing phase analyzes the RISC-V binary:- Decode ELF - Parse RISC-V instructions from
.textsection - Build PC mapping - Map program counter values to instruction indices
- Compute instruction flags - One-hot encoding for each instruction type
- Decompose operands - Split instructions into opcode, rd, rs1, rs2, immediate
RAM Preprocessing
The RAM preprocessing phase handles initial memory state:- Extract non-zero memory from ELF sections (
.data,.rodata,.bss) - Determine program code placement address
- Pack initialized data into 64-bit words
- Store for efficient polynomial evaluation
- Program
.text(code) .rodata(read-only data).data(initialized data)- Everything else starts at zero
Serialization
Preprocessing data can be saved and loaded:- 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:
- Committing to sumcheck round polynomials
- BlindFold R1CS witness commitments
- Hiding polynomial evaluations
Generated Preprocessing Functions
The#[jolt::provable] macro generates preprocessing helpers:
preprocess_shared_*
JoltSharedPreprocessing with the configured memory layout.
preprocess_prover_*
preprocess_verifier_*
verifier_preprocessing_from_prover_*
Best Practices
Preprocess once, prove many times
Preprocess once, prove many times
Preprocessing is expensive (seconds to minutes). Reuse the same preprocessing for all proofs of a program.
Serialize preprocessing
Serialize preprocessing
Save preprocessing to disk. Loading from disk is much faster than regenerating.
Match trace lengths
Match trace lengths
The actual execution trace must fit within
max_trace_length. Pad trace lengths to powers of 2 for efficiency.Verifier preprocessing is portable
Verifier preprocessing is portable
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
- 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.
Related Concepts
- Provable Macro - Configure preprocessing parameters
- Guest and Host Architecture - Understanding the proving pipeline