Skip to content

Conversation

@quangvdao
Copy link
Contributor

Overview

This PR introduces Committed Program Mode, extending the bytecode commitment work to also commit to the initial RAM (program image). This enables fully succinct verification independent of both bytecode size and program image size.

This PR supersedes #1213 (bytecode commitment only) by:

  1. Adding program image commitment alongside bytecode commitment
  2. Unifying BytecodePreprocessing and ProgramImagePreprocessing into a single ProgramPreprocessing type
  3. Creating unified TrustedProgramCommitments for both bytecode and program image commitments
  4. Removing verifier's need to materialize initial RAM state

Key Additions (Beyond Bytecode Commitment)

1. Program Image Commitment

New file: jolt-core/src/zkvm/claim_reductions/program_image.rs

Claim reduction sumcheck that binds the program-image contribution to Val_init(r_address) claims from Stage 4 to a trusted commitment:

  • ProgramImageClaimReductionParams: Stores gamma, opening points, padded length
  • ProgramImageClaimReductionProver/Verifier: Runs the claim reduction sumcheck
  • Caches a CommittedPolynomial::ProgramImageInit opening for Stage 8 batching

RAM module additions (jolt-core/src/zkvm/ram/mod.rs):

  • prover_accumulate_program_image(): Computes and stages program-image contribution claims
  • verifier_accumulate_program_image(): Stages opening points without materializing RAM

2. Unified Program Preprocessing

New file: jolt-core/src/zkvm/program.rs

Consolidated types for all static program data:

pub struct ProgramPreprocessing {
    // Bytecode (instructions)
    pub instructions: Vec<Instruction>,
    pub pc_map: BytecodePCMapper,
    // Program image (RAM init)
    pub min_bytecode_address: u64,
    pub program_image_words: Vec<u64>,
}

pub struct ProgramMetadata {
    pub min_bytecode_address: u64,
    pub program_image_len_words: usize,
    pub bytecode_len: usize,
}

pub struct TrustedProgramCommitments<PCS> {
    // Bytecode commitments
    pub bytecode_commitments: Vec<PCS::Commitment>,
    pub bytecode_num_columns: usize,
    pub log_k_chunk: u8,
    pub bytecode_len: usize,
    // Program image commitment
    pub program_image_commitment: PCS::Commitment,
    pub program_image_num_columns: usize,
    pub program_image_num_words: usize,
}

pub enum VerifierProgram<PCS> {
    Full(Arc<ProgramPreprocessing>),
    Committed(TrustedProgramCommitments<PCS>),
}

Removed file: jolt-core/src/zkvm/program_image.rs (folded into program.rs)

3. Verifier No Longer Materializes Initial RAM

In committed mode, the verifier:

  • Does NOT call build_initial_ram_state() (avoids O(program_size) work)
  • Uses verifier_accumulate_program_image() to stage claims
  • Runs ProgramImageClaimReductionVerifier to bind claims to commitment
  • Verifies batched Stage 8 opening against program_image_commitment

4. Stage 8 Program Image Integration

File: jolt-core/src/poly/rlc_polynomial.rs

  • Added ProgramImageInit handling in StreamingRLCContext
  • Program image polynomial embedded with trace-dense layout (missing lane variables)
  • Supports both CycleMajor and AddressMajor layouts

Preprocessing API Changes

Before (separate types)

let bytecode = BytecodePreprocessing::preprocess(instructions);
let program_image = ProgramImagePreprocessing::preprocess(memory_init);
let shared = JoltSharedPreprocessing::new(&bytecode, layout, program_image.meta(), trace_len);
let prover = JoltProverPreprocessing::new(shared, bytecode, program_image);

After (unified type)

let program = Arc::new(ProgramPreprocessing::preprocess(instructions, memory_init));
let shared = JoltSharedPreprocessing::new(program.meta(), layout, trace_len);
let prover = JoltProverPreprocessing::new(shared, program);
// or for committed mode:
let prover = JoltProverPreprocessing::new_committed(shared, program);

Key Difficulties & Solutions

1. Program Image Padding for Main Sigma Compatibility

Problem: Small program images may have fewer words than Main's num_columns, making the matrix degenerate.

Solution: In committed mode, pad program image to at least main_num_columns:

let padded_len_words = unpadded_len_words
    .next_power_of_two()
    .max(main_num_columns);

2. Trace-Dense Embedding for Program Image

Problem: Program image lacks lane variables, so it must be embedded specially in Stage 8.

Solution: Use contiguous block embedding (CycleMajor) or stride-by-K embedding (AddressMajor), mirroring advice polynomial handling.

3. Stage 4 Claim Staging

Problem: Stage 4's Val_init(r_address) computation requires O(program_size) work for the program-image contribution.

Solution: In committed mode, prover stages scalar claims via prover_accumulate_program_image(), which are later bound to the commitment in Stage 6b via claim reduction.


Files Changed (Summary)

New Files

  • jolt-core/src/zkvm/program.rs - Unified program preprocessing
  • jolt-core/src/zkvm/claim_reductions/program_image.rs - Program image claim reduction

Deleted Files

  • jolt-core/src/zkvm/program_image.rs - Superseded by program.rs

Major Changes

  • jolt-core/src/zkvm/prover.rs - Unified preprocessing, program image accumulation
  • jolt-core/src/zkvm/verifier.rs - Program image verification, claim reduction
  • jolt-core/src/zkvm/ram/mod.rs - prover/verifier_accumulate_program_image functions
  • jolt-core/src/poly/rlc_polynomial.rs - Stage 8 program image handling
  • jolt-sdk/macros/src/lib.rs - Updated preprocessing API

64 files changed, 6832 insertions(+), 1627 deletions(-)


Testing

All existing tests pass, including:

  • fib_e2e_committed_bytecode (CycleMajor + AddressMajor)
  • bytecode_mode_detection_full/committed
  • All 20 zkvm e2e tests

Usage

// Full mode (default) - verifier has all program data
let prover_prep = JoltProverPreprocessing::new(shared, program);

// Committed mode - verifier only has commitments
let prover_prep = JoltProverPreprocessing::new_committed(shared, program);

// CLI flag
cargo run --release --example fibonacci -- --committed-bytecode

- Add BytecodeReadRafAddressSumcheckProver/Verifier and BytecodeReadRafCycleSumcheckProver/Verifier
- Add BooleanityAddressSumcheckProver/Verifier and BooleanityCycleSumcheckProver/Verifier
- Add SumcheckId variants: BytecodeReadRafAddressPhase, BooleanityAddressPhase, BytecodeClaimReductionCyclePhase, BytecodeClaimReduction
- Add VirtualPolynomial variants: BytecodeValStage, BytecodeReadRafAddrClaim, BooleanityAddrClaim, BytecodeClaimReductionIntermediate
- Update prover: prove_stage6a() and prove_stage6b()
- Update verifier: verify_stage6a() and verify_stage6b()
- Update JoltProof: stage6a_sumcheck_proof and stage6b_sumcheck_proof
- Add bytecode-commitment-progress.md planning doc
- BooleanityAddressSumcheckProver: now has its own state (B, G, F, gamma_powers)
- BooleanityCycleSumcheckProver: now has its own state (D, H, eq_r_r, gamma_powers)
- BytecodeReadRafAddressSumcheckProver: now has its own state (F, val_polys, int_poly)
- BytecodeReadRafCycleSumcheckProver: now has its own state (ra, gruen_eq_polys, bound_val_evals)

The into_cycle_prover() method now transfers only the necessary state rather than
wrapping an inner shared struct. This makes the separation cleaner and prepares
for potential future changes where the two phases might diverge further.
…d modes

- BytecodePreprocessing::preprocess() now returns Self (caller wraps in Arc)
- JoltSharedPreprocessing::new() takes &BytecodePreprocessing, stores bytecode_size
- JoltProverPreprocessing stores Arc<BytecodePreprocessing> + optional commitments
- JoltVerifierPreprocessing uses VerifierBytecode<PCS> enum (Full or Committed)
- Added TrustedBytecodeCommitments<PCS> for type-safe commitment handling
- Updated SDK macros to return (shared, bytecode) tuple
- Updated all tests, guest/*, and benchmarks

This refactor enables Committed mode where verifier only receives bytecode
commitments instead of full O(K) bytecode data. Actual commitment computation
is TODO for a future PR.
- Create jolt-core/src/zkvm/tests.rs with E2ETestConfig infrastructure
- Port all 15 e2e tests from prover.rs to unified test runner
- Add committed bytecode mode tests (ignored until verifier ready)
- Wire verifier Stage 6a to branch on BytecodeMode (committed path)
- Update read_raf_checking for optional bytecode preprocessing
- Update bytecode-commitment-progress.md with status
- Add macro-generated preprocess_<func> and keep verifier preprocessing derived from prover
- Update examples and host template to the new 2-call workflow
- Fold bytecode preprocessing refactor notes into bytecode-commitment-progress.md (single authoritative doc)
- Fix bigint inline assembly gating to avoid host build failures
Expose compute_bytecode_vmp_contribution for external callers (e.g., GPU prover)
and remove #[cfg(test)] restriction from set_layout.
Delegate to compute_bytecode_vmp_contribution to eliminate code duplication.
…lding

- Initialize bytecode Dory context using main matrix dimensions to support embedding in Stage 8.
- Update VMP contribution logic to use correct column count.
- Handle trailing dummy rounds in BytecodeClaimReductionProver for batched sumcheck alignment.
- Pass max_trace_len to TrustedBytecodeCommitments derivation.
Add committed program-image support in committed bytecode mode via staged scalar claims + a degree-2 claim reduction, and avoid materializing initial RAM in verifier Stage 4.
Fix Stage 8 joint opening by aligning Main context dimensions with trusted commitment dimensions.
…sing into ProgramPreprocessing

Introduce unified types for program data handling:
- ProgramPreprocessing: merges bytecode instructions and program image words
- ProgramMetadata: lightweight metadata replacing RAMPreprocessing
- TrustedProgramCommitments: unified commitments for bytecode + program image
- VerifierProgram: enum for Full/Committed verification modes

Remove zkvm/program_image.rs (superseded by program.rs) and update all callsites.
Rename BytecodeMode → ProgramMode since it now controls both bytecode
and program image commitment (not just bytecode).

Changes:
- BytecodeMode enum → ProgramMode in config.rs
- bytecode_mode field → program_mode in proof/prover structs
- gen_from_elf_with_bytecode_mode → gen_from_elf_with_program_mode
- Test names: *_committed_bytecode → *_committed_program

Add 14 new committed program mode tests covering:
- Small/large traces, different Dory layouts
- Various programs (sha2, sha3, merkle-tree, memory-ops, muldiv)
- Advice + committed program interaction

Two tests ignored pending investigation:
- fib_e2e_committed_large_trace (CycleMajor at 2^17)
- btreemap_e2e_committed_program (Stage 8 failure)

Delete program-image-commitment-progress.md (all TODOs complete).
For CycleMajor layout, bytecode polynomial coefficient indexing uses
`lane * T + cycle`. When T differs between commitment time (max_trace_len)
and proving time (padded_trace_len), row indices don't align, causing
Stage 8 verification to fail.

The fix:
- Store bytecode_T in TrustedProgramCommitments for VMP indexing
- For CycleMajor, commit bytecode with main-matrix dimensions
- Ensure padded_trace_len >= bytecode_T in committed mode prover
- Pass bytecode_T through streaming RLC context to VMP computation

Re-enables previously failing tests:
- fib_e2e_committed_large_trace
- btreemap_e2e_committed_program
Replace the dense `ProgramIOPolynomial` with sparse evaluation that
avoids allocating a large vector for the entire IO region.

- Add `eval_io_mle` for sparse evaluation of IO polynomial (inputs,
  outputs, panic, termination bits) with proper domain embedding
- Add `sparse_eval_u64_block` helper for evaluating u64 blocks at offset
- Rename `evaluate_public_initial_ram_evaluation` to `eval_initial_ram_mle`
- Remove `program_io_polynomial.rs` module

This optimization is important when the IO region is large (large
max_input_size or max_output_size), as the previous implementation
allocated a dense vector of size `io_region.next_power_of_two()`.
Brings in the IO polynomial optimization:
- Add eval_io_mle for sparse evaluation of IO polynomial
- Rename functions to eval_initial_ram_mle, eval_inputs_mle, sparse_eval_u64_block
- Remove program_io_polynomial.rs module
Add missing trait implementation that was causing zklean-extractor
compilation to fail in CI.
quangvdao and others added 16 commits January 23, 2026 15:53
- Replace fully qualified paths with imports in prover.rs and verifier.rs
- Consolidate scattered imports into main use crate::{...} blocks
- Store bytecode_read_raf_params and booleanity_params in prover state
  instead of passing between prove_stage6a and prove_stage6b
ProgramImageClaimReduction prover now parallelizes per-round evaluation across lanes.
BytecodeClaimReduction verifier avoids per-chunk allocations by evaluating lane weights via an inner product.
Add tracing::instrument spans to claim reduction methods for profiling.
Gate MontU128Challenge import behind cfg(not(feature = "challenge-254-bit")).
Centralize canonical lane offsets and expose a sparse evaluator for the bytecode lane vector.
This localizes one-hot/boolean semantics so higher-level protocols can avoid dense scans.
Avoid materializing/binding dense weight chunk polynomials by separating eq(r_bc,cycle) from lane-only weights.
Also adds a fast first-round evaluator using sparse one-hot/boolean lane semantics.
Move mid-file `use` statements to top-level import blocks and replace
fully qualified paths (3+ segments) with short names via `use` statements.
Refactors BytecodeReadRafCycleSumcheckProver and BooleanityCycleSumcheckProver
to derive cycle-phase state directly from Stage 6a openings instead of
replaying the address sumcheck.

Key changes:
- Add `gruen_poly_from_evals_with_q0` helper to construct round polynomials
  by computing q(0) directly, avoiding need for stage claims
- Remove `prev_round_claims` and `prev_round_polys` fields from cycle prover
- Compute `bound_val_evals` from accumulator-staged Val claims or direct eval
- Add `ActiveLaneValue` enum and `for_each_active_lane_value` for sparse
  lane iteration in bytecode VMV contribution
- Parallelize `compute_bytecode_vmp_contribution` with thread-local accum
Resolved conflicts in RAM module by keeping ProgramMode branching logic
for Full vs Committed modes. Preserved separate RAMPreprocessing (metadata)
and ProgramImagePreprocessing (data) structs to support committed-mode
verification without O(program_size) data.
The analyze function's return type ProgramSummary was not in scope
because the imports were placed inside the function body, not at
the module level. Using the fully qualified path fixes the compile error.
Add tracing spans to compute_message and ingest_challenge methods
in BooleanityCycleSumcheckProver and BytecodeReadRafCycleSumcheckProver.
Match prover pattern: store bytecode_read_raf_params and booleanity_params
in JoltVerifier fields between stage 6a and 6b instead of passing as arguments.
Reuse main_sigma_nu() in case available
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants