Skip to content

feat(constraints): prove public RAM output constraint (26) - #15

Merged
abiswas3 merged 2 commits into
qvd/registers-34-36from
qvd/ram-output-26
Sep 29, 2026
Merged

abiswas3 merged 2 commits into
qvd/registers-34-36from
qvd/ram-output-26

Conversation

@quangvdao

@quangvdao quangvdao commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR proves constraint (26): honestWitness_ramOutputEqPublicIo, the public RAM output check in stage 2. On the public I/O interval, the honest final-RAM column equals the public I/O word. The interval runs from input_start up to, but not including, RAM_START_ADDRESS.

Stacked on #14 ((34)–(36)). Retarget to qvd/trace-assumptions (#18) once #14 merges.

Rebased onto #18. This branch (via #14) is now rebased onto #18 (qvd/trace-assumptions @ a3e7aad0fec915b7a887e4a73aef7a70245f7ea1), which replaces the vacuous JoltTrace.allAssumptions field with TraceAssumptions. lake build JoltConstraints (3565 jobs) plus every Tests/ module (3498 jobs) pass, and (26) and (34)–(36) keep the same axioms with no sorryAx. The proofs here do not use the dropped conjuncts, so these statements now hold for nonempty traces.

The proof has one premise, on the initial device: JoltIOState.AdviceBelowInput. It says:

  • each advice buffer ends at or below input_start;
  • input_start is a whole number of words above the lowest RAM address.

This PR also proves that Rust always satisfies the premise, instead of arguing it in prose. JoltIOState.AdviceBelowInput.of_memoryLayout derives it from a Lean transcription of MemoryLayout::new together with create_emulator's advice-length checks.

The sorry is gone, the stale FIXME row in model_review.md is removed, and constraints.md now reads 57 proved, 2 TODO ((38), (39)), 3 FIXME.

Diff metadata

  • Base: qvd/registers-34-36. The merge base is 0d9fa22a0a28cf7711c2460eabe1c7770481ad80, which now sits on a3e7aad0fec915b7a887e4a73aef7a70245f7ea1 (qvd/trace-assumptions, fix(trace)!: require only satisfiable row assumptions on JoltTrace #18).
  • Head: 1a2e4bd0dc2aa2450a2cc8aaceca0c8d6b57f370
  • Commits: 2
  • Files changed: 6
  • Diff: 488 insertions, 15 deletions

Motivation

The (26) target carried a FIXME. It said the Lean model did not enforce Rust's layout and buffer bounds, and that this boundary had to be audited before attempting a proof, without restricting Rust-valid executions.

Without some such bound, the statement is false. Take an advice buffer that runs past input_start with inputs empty. Its bytes land in finalRamWord inside the window, while ramPublicIoWord is 0 there.

How trivial is (26)?

It is mostly definitional, and that is faithful to Rust. final_ram_state (crates/jolt-witness/src/backend/trace/ram.rs#L134) builds the honest final RAM as layers taken from the device, in this order:

memory image → trusted advice → untrusted advice → inputs → outputs → panic → termination

The verifier's public word stacks the last four layers from the same device, and the Lean definitions copy this structure. So (26) says only that the first three layers stay out of the I/O window.

Whether the device's outputs/panic agree with what the program actually stored is the job of (38), not (26). A blind read-back of the statement independently reached the same conclusion.

The premise, and why Rust always satisfies it

JoltIOState.AdviceBelowInput, in Constraints/RamReadData.lean:

structure _root_.JoltIOState.AdviceBelowInput (io : JoltIOState) : Prop where
  inputWordAligned : (io.layout.input.1.toNat - ramLowestAddress io.layout) % 8 = 0
  trustedAdviceBelowInput :
    io.layout.trustedAdvice.1.toNat + io.trustedAdvice.size ≤ io.layout.input.1.toNat
  untrustedAdviceBelowInput :
    io.layout.untrustedAdvice.1.toNat + io.untrustedAdvice.size ≤ io.layout.input.1.toNat

This is exactly what overlayRamBytes_below_input consumes. Nothing about region ends, absolute alignment, advice-start alignment, or the output/panic/termination placement is assumed.

inputWordAligned is necessary. With 4 bytes of advice at the lowest address and input_start 4 bytes above it, the first masked word still contains advice.

Constraints/MemoryLayout.lean (new) transcribes MemoryLayout::new up to input_start (common/src/jolt_device.rs#L346-L438) as adviceInputLayout : MemoryConfig → Option AdviceInputLayout.

  • Faithful parts: align_up, the checked sums, next_power_of_two and RAM_START_ADDRESS - io_bytes. Each Rust panic becomes none.
  • Placement: the larger advice region goes first.
  • Omitted: the power-of-two assertions and everything after input_start. These only add panics, so the Lean function succeeds on at least every configuration Rust accepts.

JoltIOState.AdviceBelowInput.of_memoryLayout then shows io.AdviceBelowInput from:

  • adviceInputLayout config = some layout;
  • the device's start addresses equal the computed ones;
  • each advice length ≤ its configured maximum. This is the check in create_emulator, tracer/src/lib.rs#L382-L393.

Sanity check. #eval adviceInputLayout ⟨0, 0, 0, 8⟩ gives input_start = 0x7fffffe0. That matches the layout printed by the Rust tracer harness for the same MemoryConfig at a16z/jolt 754fc88.

The theorem keeps the weaker premise rather than taking "the layout is MemoryLayout::new config" as a hypothesis, so it applies to any device with the property. The bridge theorem connects it to Rust.

Proof structure

  1. IOSetupFrame.lean (new). A frame calculus over JoltMonad: Same, Preserves, the pure/bind/lift/throw/get/modify rules, register, memory and device rules, execHostIO_rule and execInstr_rule. No instruction changes the layout or either advice buffer. finalTraceState_ioSame lifts this to the whole trace, which matters because finalRamWord remaps with the initial layout.
  2. overlayRamBytes_below_input. An overlay whose bytes end at or below input_start is the identity from the input's first word onward. It needs no alignment of the overlay start: remapping rounds down, which only moves bytes lower.
  3. finalRamWord_eq_ramPublicIoWord. Inside the mask, L + 8a < RAM_START_ADDRESS, so the memory-image layer is 0. Both advice layers drop out by step 2. The remaining overlays are syntactically equal.
  4. honestWitness_ramOutputEqPublicIo. Closes with sub_self inside the mask.

Trust surface

  • New premise: only adviceBelowInput, on the initial state, with the machine-checked bridge above.
  • Transcription: adviceInputLayout is hand-transcribed from Rust, the same trust model as JoltISA.
  • No new axioms, sorry, admit, native_decide or option changes (other than autoImplicit false).
  • #print axioms honestWitness_ramOutputEqPublicIo: load_reservation, match_reservation, plat_term_write and sys_enable_experimental_extensions, which are existing LeanRV64D Sail platform axioms (I did not trace which lemma pulls them in), plus propext, Classical.choice and Quot.sound.
  • #print axioms JoltIOState.AdviceBelowInput.of_memoryLayout: propext, Classical.choice, Quot.sound.
  • Not checked:
    • Rust's final_ram_state remaps with preprocessing.memory_layout but reads start addresses from device.memory_layout. Lean uses one initial layout for both. If the two can differ, the model diverges.
    • That Rust's dram_start is always ≥ RAM_START_ADDRESS. Lean gates the memory image at RAM_START_ADDRESS.

Commit map

  • 280da7e: I/O frame, the (26) proof, and a first pair of premises, JoltIOLayout.Valid and JoltIOState.AdviceFits.
  • 1a2e4bd: replaces those with the single weaker AdviceBelowInput, and adds the MemoryLayout::new transcription plus of_memoryLayout.

Validation at 1a2e4bd

  • Full lake build succeeded locally (8589 jobs).
  • The axiom footprints are as listed above.
  • #eval of the transcription matches the executed Rust layout.
  • The diff against 0d9fa22 adds no sorry, admit, native_decide or axiom.
  • CI status: see the checks tab.

Remaining work

  • (38) and (39) remain TODO; (01), (16) and (37) remain FIXME, blocked by upstream Rust issues.
  • The transcription stops at input_start. Later theorems, such as (38) and constraint soundness, will want the rest of MemoryLayout::new.

Reviewer map

  1. Constraints/RamReadData.lean: AdviceBelowInput.
  2. Constraints/MemoryLayout.lean: compare adviceInputLayout line by line with jolt_device.rs#L346-L438, then of_memoryLayout.
  3. Constraints/RamOutputEqPublicIo.lean: the theorem statement, then the two lemmas.
  4. Constraints/IOSetupFrame.lean: skim the rules; read execInstr_rule and finalTraceState_ioSame.
  5. constraints.md, model_review.md.

AI usage

Claude Code (Opus) found the premise, checked it against the Rust source, transcribed MemoryLayout::new, and wrote the proofs under my direction. A separate Claude Code agent did a blind read-back of the statement. I reviewed the statement, the premise, the transcription and the Rust citations, and confirmed the build and axiom footprints locally.

🤖 Generated with Claude Code

Execution preserves the device layout and both advice buffers
(JoltIOSetupFrame.execInstr_rule, finalTraceState_ioSame). With the
Rust-enforced JoltIOLayout.Valid and JoltIOState.AdviceFits premises on the
initial device, the honest final RAM word equals the public I/O word on the
public interval.
…layout

Replace JoltIOLayout.Valid and JoltIOState.AdviceFits with the single
premise the proof uses, JoltIOState.AdviceBelowInput: each advice buffer
ends at or below input_start, and input_start is word-aligned relative
to the lowest RAM address. Region ends and absolute alignment are no
longer assumed.

Add a transcription of MemoryLayout::new up to input_start and prove
AdviceBelowInput.of_memoryLayout: every device laid out by Rust whose
advice passes create_emulator's length checks satisfies the premise.
@quangvdao
quangvdao removed this pull request from stack #16 September 29, 2026 02:22
@abiswas3
abiswas3 merged commit 2843209 into qvd/registers-34-36 Sep 29, 2026
11 checks passed
abiswas3 added a commit that referenced this pull request Sep 29, 2026
* build: migrate Lake configuration to TOML

* docs: update jolt-qed.pdf

* refactor(isa): share branch and operand helpers

* feat(isa): model device memory and advice tape

* feat(trace): add program and execution trace types

* feat(witness): define NP witness type dependent on witness parameters

* feat(metadata): add instruction metadata mappings

* feat(witness): define honest NP witness length-T columns

* feat(witness): define honest NP witness chunked one-hot selectors

* feat(witness): define honest NP witness unchunked selectors

* feat(witness): define honest NP witness value arrays

* docs: justify the store assumption in trace row

* feat(witness): combine all helpers to make np witness

* feat(lookup_tables): Started on some lookup tables

Most of them are empty.

* feat(constraints): Starting to model constraints on witnesses

* feat(isa): add VirtualXORROTL1 instruction

* docs(bug-report): document CSRRS completeness issue (tentative)

* docs(bug-report): explain CSRRS x0 rewrite bypass (tentative)

* fix(constraints): restore destination regression imports

* feat(model): align traces and final operands with Rust

Add the pending trace, witness, and constraint definitions alongside the normalized final-row ISA representation and its proof updates. Add focused regression modules, including FinalOperands. Completeness proofs remain deferred.

* fix(witness): reject traces beyond cycle domain

* fix(witness): require prover-padded trace length

* fix(witness): bind bytecode domain to expanded program

* fix(witness): bound prover chunk dimensions

* proof(constraints): close shift and one-hot Booleanity targets

* proof(constraints): close ten Spartan witness targets

* proof(constraints): close assertion lookup under passing operands

* docs(bug-report): document terminating self-branch constraint failure

* proof(constraints): close ten more honest-witness targets

* proof(constraints): close bytecode read and addition targets

* proof(constraints): close RAM and expansion targets

* feat(bundle): A new bundle to handle the JAL translation issue

* fix(jal): new native proof with correct translation

* Clean general projection lemmas.

* refactor(constraints): reduce lookup operand proofs to active rows

* docs(constraints): clarify translation gaps in pending proofs

* test(constraints): cover load capture consistency checks

* chore(gitignore)

* refactor(isa): share operand and branch calculations

Port 2a46c17 with its branch proof updates. Issue draft: bug-report/isa-shared-helpers.md.

* fix(isa): model device memory and runtime advice state

Port 907d734 with its operand preservation, shared arithmetic helpers, and proof dependencies. Document device memory, advice tape, and operand representation issues under bug-report/.

* feat(isa): add VirtualXORROTL1 execution

Port f42ae4f and its proof-support cases. Issue draft: bug-report/isa-xorrotl1.md.

* fix(isa): represent decoded operands at Rust widths

Port the final-operand representation and proof changes from dfd9e93. Keep LD destination handling for a separate port. Issue draft: bug-report/isa-decoded-operands.md.

* fix(isa): preserve final LD destinations during execution

Port the LD execution and native proof repair from dfd9e93. Issue draft: bug-report/isa-ld-destination.md.

* fix(isa): model JAL using the Rust PC correspondence

Port the JoltBytecode changes from feae045, 3bf46b5, and 78bbca7 together so the bundle, semantics, proof, and projection helpers stay consistent. Include the reviewed issue draft in bug-report/pc_nextPc.md.

* docs(isa): index migration issues and record build verification

* docs(isa): move migration issue drafts into issues directory

* fix(isa): complete native dispatch and operand correspondence

* chore(docs): keep issue drafts local

* test(witness): check native dispatch against witness extraction

Check the normalized ADDI immediate and JALR x0 temporary destination, captured value, target lookup, and PC preservation. Exercise target 6 to guard against restoring the Sail alignment check in the Rust instruction body.

* fix(csrrs): Fixing the issue for csrrs x0

* fix(srliw): Other issue posted on Jolt

* broken(BytecodeRafAtEntryEqOne)

This is not a real pass.
FIXME: This is a real issue

* backup:

This is a backup

* fix(isa): model VirtualHostIO live and replay behavior

* feat(trace): require initialized program state

Prove initial register values through the initializer and expose zero source registers from each initialized trace.

* docs: explain Rust and Sail NextPC correspondence

Document the JAL execution-stage mapping, witness PC columns, and the assumptions used by the updated equivalence proof.

* feat(constraints): implement comparison, slice, and assertion lookup tables

Add `uninterleave`, which follows Rust's `uninterleave_bits` step by step,
and transcribe 24 more tables from their Rust `materialize_entry`: Andn,
Equal, NotEqual, the unsigned and signed comparisons, RangeCheck,
RangeCheckAligned, AlignAddr, UpperWord, LowerHalfWord, SignMask,
SignExtendWord, Pow2, Pow2W, ShiftRightBitmask, ShiftRightBitmaskW,
HalfwordAlignment, WordAlignment, MulUNoOverflow, ValidDiv0 and
ValidUnsignedRemainder. The remaining tables still use the placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): implement negate-if, XOR-rotate, and shift-data lookup tables

Transcribe 15 more tables from their Rust `materialize_entry`:
VirtualNegateIf, the four VirtualXORROT tables (rotations 32, 24, 16, 63),
the seven VirtualXORROTW tables (rotations 16, 12, 8, 7, 22, 19, 6) and
ShiftDataB/H/W. As in Rust, each XOR-rotate family is one definition
parameterised by the rotation amount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): implement window-mask, rev8w, shift/rotate loop, and pext lookup tables

Transcribe the last 12 tables from their Rust `materialize_entry`:
WindowMaskB/H/W, VirtualRev8W, VirtualSRL, VirtualSRA, VirtualSRLW,
VirtualSRAW, VirtualROTR, VirtualROTRW, Pext and PextSigned. The Rust
helpers `rev8w`, `u32::swap_bytes`, `pext`, `window_sign_bit` and
`pext_signed` get their own Lean definitions. Rust's `while` loop in `pext`
becomes a 64-pass `for` loop with the same exit condition.

Every LookupTableKind now has a table, so the `sorry` fallback in
`lookupTableEntry` is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): close constraints 13 and 14

Prove honest-witness lookup-result and jump-return destination writeback.
Connect successful ISA execution to captured destinations and derive the
trace PC invariant using the existing bytecode memory helpers.

Add the all_assumptions conjunction and expose scoped operand assumptions
through JoltTrace. Split static program definitions from traces and carry
expanded instructions and destination normalization through witness helpers.

Keep the existing source-to-row translation obligations explicit.

Validation: lake build; both constraint proofs have no sorryAx dependency.

* docs(bug-report): document RAM address wrap completeness bug

* Updated review

* docs(model): record fork replay audit and deferred work

* docs(spec): ai helper md file

* docs(bug-report): mark CSRRS writeback issue fixed

* refactor(model): centralize jump row validity

* feat(constraints): prove next source PC for jumps

* feat(constraints): prove next source PC for other rows

* docs(constraints): record proof status and reusable method

* docs: refresh constraint review after fork fast-forward

* feat(constraints): prove register value history

* test(ram): reproduce termination-word history failure

* fix(trace)!: require only satisfiable row assumptions on JoltTrace

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* Prove register read/write constraints (34)-(36)

Add source-operand helpers (register35_rs1?, register36_rs2?) with real-row
characterisations of Rs1Ra/Rs1Value/Rs2Ra/Rs2Value and canonical-source address
round-tripping, expose register42_registersVal_preState, and use them to prove
honestWitness_rs1ValueEqRegistersRead, honestWitness_rs2ValueEqRegistersRead and
honestWitness_rdWriteValueEqRegistersReadWrite.

* refactor(constraints): name register-read helpers by content

The helpers added for (34)-(36) were prefixed with the constraint number
they were first written for, but (36) and (34) also use them. Name them
after what they state instead.

* fix(trace)!: require only satisfiable row assumptions on JoltTrace (#18)

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* feat(constraints): prove public RAM output constraint (26) (#15)

* Prove public RAM output constraint (26)

Execution preserves the device layout and both advice buffers
(JoltIOSetupFrame.execInstr_rule, finalTraceState_ioSame). With the
Rust-enforced JoltIOLayout.Valid and JoltIOState.AdviceFits premises on the
initial device, the honest final RAM word equals the public I/O word on the
public interval.

* refactor(constraints): weaken (26) premise and derive it from Rust's layout

Replace JoltIOLayout.Valid and JoltIOState.AdviceFits with the single
premise the proof uses, JoltIOState.AdviceBelowInput: each advice buffer
ends at or below input_start, and input_start is word-aligned relative
to the lowest RAM address. Region ends and absolute alignment are no
longer assumed.

Add a transcription of MemoryLayout::new up to input_start and prove
AdviceBelowInput.of_memoryLayout: every device laid out by Rust whose
advice passes create_emulator's length checks satisfies the premise.

* proof(constraints): prove (39) up to six shift/rotate table lemmas (#17)

* proof(constraints): reduce (39) to per-table lookup entry lemmas

Collapse the lookup-address and table sums in (39) to the flagged table's
entry at the honest lookup index, then prove 47 of the 55 per-table entry
lemmas. Six shift/rotate lemmas are false for arbitrary expanded bytecode
(non-bitmask shift masks); Pext and PextSigned are open. Counterexamples in
JoltConstraints/Tests/LookupShiftMask.lean.

* proof(constraints): close the Pext and PextSigned lookup entry lemmas

Prove that the PEXT table's pext (contiguous fast path plus the 64-pass
clear-lowest-set-bit loop) equals jolt_virtual_pext_value, and that
pextSigned equals jolt_virtual_pext_signed_value, in the new
LookupPextProofHelpers.lean. Only propext, Classical.choice and Quot.sound.

Constraint (39) now depends on six per-table sorries, the shift/rotate
lemmas that are false for arbitrary bytecode. Drop the randomized Pext
check from Tests/LookupShiftMask.lean, which the theorems supersede.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Quang Dao <qvd@andrew.cmu.edu>
abiswas3 added a commit that referenced this pull request Oct 9, 2026
* build: migrate Lake configuration to TOML

* docs: update jolt-qed.pdf

* refactor(isa): share branch and operand helpers

* feat(isa): model device memory and advice tape

* feat(trace): add program and execution trace types

* feat(witness): define NP witness type dependent on witness parameters

* feat(metadata): add instruction metadata mappings

* feat(witness): define honest NP witness length-T columns

* feat(witness): define honest NP witness chunked one-hot selectors

* feat(witness): define honest NP witness unchunked selectors

* feat(witness): define honest NP witness value arrays

* docs: justify the store assumption in trace row

* feat(witness): combine all helpers to make np witness

* feat(lookup_tables): Started on some lookup tables

Most of them are empty.

* feat(constraints): Starting to model constraints on witnesses

* feat(isa): add VirtualXORROTL1 instruction

* docs(bug-report): document CSRRS completeness issue (tentative)

* docs(bug-report): explain CSRRS x0 rewrite bypass (tentative)

* fix(constraints): restore destination regression imports

* feat(model): align traces and final operands with Rust

Add the pending trace, witness, and constraint definitions alongside the normalized final-row ISA representation and its proof updates. Add focused regression modules, including FinalOperands. Completeness proofs remain deferred.

* fix(witness): reject traces beyond cycle domain

* fix(witness): require prover-padded trace length

* fix(witness): bind bytecode domain to expanded program

* fix(witness): bound prover chunk dimensions

* proof(constraints): close shift and one-hot Booleanity targets

* proof(constraints): close ten Spartan witness targets

* proof(constraints): close assertion lookup under passing operands

* docs(bug-report): document terminating self-branch constraint failure

* proof(constraints): close ten more honest-witness targets

* proof(constraints): close bytecode read and addition targets

* proof(constraints): close RAM and expansion targets

* feat(bundle): A new bundle to handle the JAL translation issue

* fix(jal): new native proof with correct translation

* Clean general projection lemmas.

* refactor(constraints): reduce lookup operand proofs to active rows

* docs(constraints): clarify translation gaps in pending proofs

* test(constraints): cover load capture consistency checks

* chore(gitignore)

* refactor(isa): share operand and branch calculations

Port 2a46c17 with its branch proof updates. Issue draft: bug-report/isa-shared-helpers.md.

* fix(isa): model device memory and runtime advice state

Port 907d734 with its operand preservation, shared arithmetic helpers, and proof dependencies. Document device memory, advice tape, and operand representation issues under bug-report/.

* feat(isa): add VirtualXORROTL1 execution

Port f42ae4f and its proof-support cases. Issue draft: bug-report/isa-xorrotl1.md.

* fix(isa): represent decoded operands at Rust widths

Port the final-operand representation and proof changes from dfd9e93. Keep LD destination handling for a separate port. Issue draft: bug-report/isa-decoded-operands.md.

* fix(isa): preserve final LD destinations during execution

Port the LD execution and native proof repair from dfd9e93. Issue draft: bug-report/isa-ld-destination.md.

* fix(isa): model JAL using the Rust PC correspondence

Port the JoltBytecode changes from feae045, 3bf46b5, and 78bbca7 together so the bundle, semantics, proof, and projection helpers stay consistent. Include the reviewed issue draft in bug-report/pc_nextPc.md.

* docs(isa): index migration issues and record build verification

* docs(isa): move migration issue drafts into issues directory

* fix(isa): complete native dispatch and operand correspondence

* chore(docs): keep issue drafts local

* test(witness): check native dispatch against witness extraction

Check the normalized ADDI immediate and JALR x0 temporary destination, captured value, target lookup, and PC preservation. Exercise target 6 to guard against restoring the Sail alignment check in the Rust instruction body.

* fix(csrrs): Fixing the issue for csrrs x0

* fix(srliw): Other issue posted on Jolt

* broken(BytecodeRafAtEntryEqOne)

This is not a real pass.
FIXME: This is a real issue

* backup:

This is a backup

* fix(isa): model VirtualHostIO live and replay behavior

* feat(trace): require initialized program state

Prove initial register values through the initializer and expose zero source registers from each initialized trace.

* docs: explain Rust and Sail NextPC correspondence

Document the JAL execution-stage mapping, witness PC columns, and the assumptions used by the updated equivalence proof.

* feat(constraints): implement comparison, slice, and assertion lookup tables

Add `uninterleave`, which follows Rust's `uninterleave_bits` step by step,
and transcribe 24 more tables from their Rust `materialize_entry`: Andn,
Equal, NotEqual, the unsigned and signed comparisons, RangeCheck,
RangeCheckAligned, AlignAddr, UpperWord, LowerHalfWord, SignMask,
SignExtendWord, Pow2, Pow2W, ShiftRightBitmask, ShiftRightBitmaskW,
HalfwordAlignment, WordAlignment, MulUNoOverflow, ValidDiv0 and
ValidUnsignedRemainder. The remaining tables still use the placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): implement negate-if, XOR-rotate, and shift-data lookup tables

Transcribe 15 more tables from their Rust `materialize_entry`:
VirtualNegateIf, the four VirtualXORROT tables (rotations 32, 24, 16, 63),
the seven VirtualXORROTW tables (rotations 16, 12, 8, 7, 22, 19, 6) and
ShiftDataB/H/W. As in Rust, each XOR-rotate family is one definition
parameterised by the rotation amount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): implement window-mask, rev8w, shift/rotate loop, and pext lookup tables

Transcribe the last 12 tables from their Rust `materialize_entry`:
WindowMaskB/H/W, VirtualRev8W, VirtualSRL, VirtualSRA, VirtualSRLW,
VirtualSRAW, VirtualROTR, VirtualROTRW, Pext and PextSigned. The Rust
helpers `rev8w`, `u32::swap_bytes`, `pext`, `window_sign_bit` and
`pext_signed` get their own Lean definitions. Rust's `while` loop in `pext`
becomes a 64-pass `for` loop with the same exit condition.

Every LookupTableKind now has a table, so the `sorry` fallback in
`lookupTableEntry` is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): close constraints 13 and 14

Prove honest-witness lookup-result and jump-return destination writeback.
Connect successful ISA execution to captured destinations and derive the
trace PC invariant using the existing bytecode memory helpers.

Add the all_assumptions conjunction and expose scoped operand assumptions
through JoltTrace. Split static program definitions from traces and carry
expanded instructions and destination normalization through witness helpers.

Keep the existing source-to-row translation obligations explicit.

Validation: lake build; both constraint proofs have no sorryAx dependency.

* docs(bug-report): document RAM address wrap completeness bug

* Updated review

* docs(model): record fork replay audit and deferred work

* docs(spec): ai helper md file

* docs(bug-report): mark CSRRS writeback issue fixed

* refactor(model): centralize jump row validity

* feat(constraints): prove next source PC for jumps

* feat(constraints): prove next source PC for other rows

* docs(constraints): record proof status and reusable method

* docs: refresh constraint review after fork fast-forward

* feat(constraints): prove register value history

* test(ram): reproduce termination-word history failure

* fix(trace)!: require only satisfiable row assumptions on JoltTrace

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* Prove register read/write constraints (34)-(36)

Add source-operand helpers (register35_rs1?, register36_rs2?) with real-row
characterisations of Rs1Ra/Rs1Value/Rs2Ra/Rs2Value and canonical-source address
round-tripping, expose register42_registersVal_preState, and use them to prove
honestWitness_rs1ValueEqRegistersRead, honestWitness_rs2ValueEqRegistersRead and
honestWitness_rdWriteValueEqRegistersReadWrite.

* refactor(constraints): name register-read helpers by content

The helpers added for (34)-(36) were prefixed with the constraint number
they were first written for, but (36) and (34) also use them. Name them
after what they state instead.

* fix(trace)!: require only satisfiable row assumptions on JoltTrace (#18)

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* feat(constraints): prove public RAM output constraint (26) (#15)

* Prove public RAM output constraint (26)

Execution preserves the device layout and both advice buffers
(JoltIOSetupFrame.execInstr_rule, finalTraceState_ioSame). With the
Rust-enforced JoltIOLayout.Valid and JoltIOState.AdviceFits premises on the
initial device, the honest final RAM word equals the public I/O word on the
public interval.

* refactor(constraints): weaken (26) premise and derive it from Rust's layout

Replace JoltIOLayout.Valid and JoltIOState.AdviceFits with the single
premise the proof uses, JoltIOState.AdviceBelowInput: each advice buffer
ends at or below input_start, and input_start is word-aligned relative
to the lowest RAM address. Region ends and absolute alignment are no
longer assumed.

Add a transcription of MemoryLayout::new up to input_start and prove
AdviceBelowInput.of_memoryLayout: every device laid out by Rust whose
advice passes create_emulator's length checks satisfies the premise.

* proof(constraints): prove (39) up to six shift/rotate table lemmas (#17)

* proof(constraints): reduce (39) to per-table lookup entry lemmas

Collapse the lookup-address and table sums in (39) to the flagged table's
entry at the honest lookup index, then prove 47 of the 55 per-table entry
lemmas. Six shift/rotate lemmas are false for arbitrary expanded bytecode
(non-bitmask shift masks); Pext and PextSigned are open. Counterexamples in
JoltConstraints/Tests/LookupShiftMask.lean.

* proof(constraints): close the Pext and PextSigned lookup entry lemmas

Prove that the PEXT table's pext (contiguous fast path plus the 64-pass
clear-lowest-set-bit loop) equals jolt_virtual_pext_value, and that
pextSigned equals jolt_virtual_pext_signed_value, in the new
LookupPextProofHelpers.lean. Only propext, Classical.choice and Quot.sound.

Constraint (39) now depends on six per-table sorries, the shift/rotate
lemmas that are false for arbitrary bytecode. Drop the randomized Pext
check from Tests/LookupShiftMask.lean, which the theorems supersede.

* docs(human-comments): Started documenting program.lean

* chore: clean up capture API and audit docs

* refactor: separate constraints from completeness proofs

Give each constraint a dedicated definition module and a matching completeness module. Add the AllConstraints bundle and an aggregate honest-witness theorem, and clean up proof linter warnings.

* fix(completeness): assemble branch constraint from its theorem

Admit the individual branch completeness proof pending the Rust and Lean model reconciliation, then use it in the aggregate theorem without assuming the constraint as a premise.

* refactor(isa): mirror Rust JoltDevice and MemoryLayout

Replace JoltIOLayout and JoltIOState with Rust's MemoryLayout (all 20
fields) and JoltDevice, using Rust's names. Rename SailJoltState.io to
jolt_device, ramStartAddress to RAM_START_ADDRESS, and the device byte
functions to JoltDevice.load? and JoltDevice.store?, built on Rust's
is_* region checks. Add STACK_CANARY_SIZE.

* fix(isa): model LD and SD with Jolt's MMU

LD and SD now read and write memory directly, following Rust's MMU, with
Rust's checks as panics. The Sail equivalence proofs keep their Sail
assumptions and add JoltRamLoadOk / JoltRamStoreOk. MemoryLayout and
JoltDevice move to JoltISA/JoltDevice.lean.

* refactor: remove dead LD/SD code and unused memory-window arguments

* refactor: read HostIO bytes through Jolt's MMU and drop unused trace assumptions

HostIO byte reads now follow Rust's Mmu::load (trace_load, then load_raw),
so no Jolt instruction uses Sail's memory read. TraceAssumptions keeps only
xRegReadable; the RAM window, privilege and MPRV facts and the per-row
assumption operands are no longer needed.

* docs: start a new model review

* feat: add Rust-faithful program and honest trace models

* feat: prove facts about the Jolt program's rows

* feat: prove uniqueness and the preprocessed bytecode layout

* feat: prove HostIO keeps Sail state and registers stay readable

* feat: prove asserts hold, registers start at 0, and size RAM like Rust

* feat: prove advice regions sit below the inputs

* feat: prove the trace starts at the entry slot and LD/SD addresses are aligned

* feat: add the memory address of a trace row; record completeness conditions

* feat: model Rust's prover config (trace length and RAM size)

* feat: prove the prover's trace length fits and pads the run

* feat: prove the prover's RAM size holds every access; FIXME for upstream ram_K bug

* feat: state the spoil assert condition

* docs: explain when a guest reaches a spoil assert

* docs: link every completeness assumption to its upstream issue

* feat: model Rust's last-row-is-a-jump check (a16z/jolt#1968)

* feat: assume executed code is unchanged (FIXME a16z/jolt#1952)

* feat: make expand opaque; assume next PC does not wrap (asked a16z)

* feat: build witness sizes from the prover config (rewiring phase 0)

* refactor: witness columns read the honest trace (rewiring phase 1)

* docs: list the store fact among the proofs owed

* chore: delete the old program and trace models (kept in history at d044e63)

* docs: bug report and repro for final RAM over 2 GiB

* docs: drop the untested fix from the final RAM report

* docs: FIXME exception for final RAM above 2 GiB (asked a16z)

* refactor: prove the bytecode layout the completeness proofs use (rewiring phase 2)

* refactor: Terminated and RamFits over the honest trace, proved for Rust's sizes

* refactor: honest witness over the trace; Rust's sizes meet the old size assumptions (rewiring phase 2)

* refactor: prove the old trace's facts for the honest trace (rewiring phase 3)

* refactor: constraint proof helpers over the honest trace (rewiring phase 3)

* refactor: completeness theorems over the honest trace (rewiring phase 4); a16z: PC must not wrap

* remove Tests, written against the old trace (rewiring phase 5)

* prove prover_config_log_ram_K_lt: ram_K comes from 64-bit addresses

* prove address-0 accesses: an LD at 0 reads 0, no SD stores at 0; ramAccessesValid is alignment only, proved

* final theorem at Rust's sizes; no completeness theorem takes Terminated; a16z: 2 GiB RAM limit is an assumption

* a16z: a program must not change its own code is an assumption

* final theorem against the verifier's public I/O: the instance's inputs, claimed outputs and panic flag

* prove (16): a taken branch is never the last row (#1968), so the next row starts at PC + imm

* model_review lists only open issues; drop the archives; rename program_fresh to program

* refactor(lean): separate general traces from honest execution

Define Trace independently of HonestTrace and express AllConstraints over
a witness with its instance-bound ConstraintContext. Keep honest witness
construction and completeness in the honest layer.

Prove store-word presence for indexed rows of HonestTrace by induction,
replace the unrestricted admitted lemma, and check its proof and witness
dependencies in the default build.

Share state-preservation rules across memory, Sail, and device proofs.
Move completeness helpers out of the constraint relation layer and keep
model_review focused on open issues.

Validation: lake build (8672 jobs), including StoreWordPresence checks.

* docs: add soundness review notes and Lean tutorials

Add the soundness method and completeness/soundness review document,
alongside soundness examples and Lean tutorial materials.

* docs: close A5 in the completeness review; HonestTrace admits every run Rust's tracer produces

* fix(lean): enforce verifier input and witness-size checks

Require valid public inputs, the padded trace limit, the instance's bytecode
domain, and the verifier's RAM minimum and maximum in ConstraintContext.
Derive input, trace, and bytecode checks from the existing completeness inputs.

Add the approved ramSizeAccepted premise to allConstraints_rust_sizes and
explain it in English beside the theorem. Prover acceptance alone does not
imply this check: the included Rust reproducer traces an empty ELF, obtains
ram_K = 4 from the prover, and receives InvalidRamK with minimum 16 from the
verifier. Its self-jump control passes the verifier input checks.

Build regression checks for oversized outputs, a zero-based layout, invalid
trace and bytecode domains, and RAM sizes outside the verifier bounds.

Validation: lake build (8674 jobs), including both default-build checks;
the saved Rust reproducer and self-jump control pass their observed checks.

* fix(lean): keep RAM completeness explicit and kernel checked

Remove the ramSizeAccepted premise and retain the general RAM-size obligation as a documented sorry, consistent with the existing Rust-bug convention. Prove the nonempty-image lower inequality when the verifier minimum computes. Replace native evaluation in the boundary checks with kernel evaluation and audit the relevant axiom dependencies. Validation: lake build passes (8675 jobs).

* fix(lean): bind trusted advice to the instance

Model trusted-advice contents directly in the fixed instance and retain untrusted advice and the execution tape as prover inputs. Prove that initialized trusted advice cannot vary with prover inputs and that AllConstraints is tape-independent. Include both new files and axiom checks in the default build. Document the separate tape-consistency obligation and the unmodeled bytes-without-commitment case. Validation: lake build JoltConstraints passed (8675 jobs).

* refactor(lean): general runs, instance-level code assumption, Rust runtime advice

- ValidRun: a run of the instance's program (expansion, initial state, start,
  step, link) with no stop rule or advice choice; HonestTrace now extends it.
- JoltInstance.CodeUnchanged replaces the HonestTrace.code_unchanged field
  (a16z, 2026-10-07, #1952); honest traces get it from the premise.
- HonestTrace.advice_from_rust: VirtualAdvice rows carry the values Rust's
  tracer patches in (SourceInstruction.rustAdvice for the division family,
  reusing the bytecode project's advice values); SC.W/SC.D left as a TODO.
- Rv64ProgramImage.ImageNonempty: a16z (phone, 2026-10-08) says a program is
  never empty; premise of the final theorem and of witness_params_ram_bounds.
- witness_params_ram_bounds: FIXME, upper half blocked by #1951 item 4
  (compute_max_ram_k rounds down); docs updated to match.
- Review notes: B1/B2 closed, B4 decision recorded.

Validation: lake build JoltConstraints passes (8675 jobs).

* fix(lean): chunk widths are the prover's choice; drop check scripts

- WitnessParams keeps only Rust's structural chunk-width checks; the prover's
  4/16-or-8/32 choice is WitnessParams.ProverChunkConfig, used only by
  completeness (HonestTrace.witness_params_chunk_config). Remove the unused
  chunkBits_lt_128.
- Move ramAccessesValid out of the relation into
  Completeness/Helpers/RamAccesses.lean.
- Remove the Checks/ build-time check scripts; import advice_inputs directly.
- Docs: constraint_proving.md names the current trace facts; model_review.md
  header states the Rust checkout; review notes close B3 and record the
  soundness premises (2^127 < ringChar F, NextPCNoWrap, CodeUnchanged).

Validation: lake build JoltConstraints passes (8673 jobs).

* feat(lean): define expansions and prove expanded-row facts

Bring SourceInstruction.expand over from the bytecode project (expansions.lean,
expansion_helpers.lean), with LR/SC programs in place of the generator output.
Check every expansion's rows in expansion_facts.lean and use them to prove
expand_program_rows_valid and the six shift lookupEntryCorrect lemmas.

Shift tables: LookupShiftProofHelpers.lean proves the SRL, SRA, SRLW and SRAW
table loops on right-shift bitmasks. shiftMaskOk_of_honest shows every honest
row reads such a mask: immediate masks are checked in the bytecode, and a
register mask is written by the VirtualShiftRightBitmask row just before.

Move the nextPC frame lemmas to nextpc_frame.lean.

* chore(lean): drop unused pc_map_ok_iff

Neither the completeness nor the soundness statement uses it; the proofs work
with pc_map_ok directly.

* feat(lean): state soundness, proof sorried

JoltInstance.InLanguage is Jolt's claim: for some private inputs, Rust's run
stops at the PC stall with the instance's outputs and panic flag. Soundness
assumes a field of characteristic above 2^127, NextPCNoWrap and CodeUnchanged.
Expected to fail today for programs that stop or panic in ways SDK guests
never do (B6).

* ci: bump lean-action to v1.6.1 (pins its cache actions by SHA)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Quang Dao <qvd@andrew.cmu.edu>
abiswas3 added a commit that referenced this pull request Oct 22, 2026
* build: migrate Lake configuration to TOML

* docs: update jolt-qed.pdf

* refactor(isa): share branch and operand helpers

* feat(isa): model device memory and advice tape

* feat(trace): add program and execution trace types

* feat(witness): define NP witness type dependent on witness parameters

* feat(metadata): add instruction metadata mappings

* feat(witness): define honest NP witness length-T columns

* feat(witness): define honest NP witness chunked one-hot selectors

* feat(witness): define honest NP witness unchunked selectors

* feat(witness): define honest NP witness value arrays

* docs: justify the store assumption in trace row

* feat(witness): combine all helpers to make np witness

* feat(lookup_tables): Started on some lookup tables

Most of them are empty.

* feat(constraints): Starting to model constraints on witnesses

* feat(isa): add VirtualXORROTL1 instruction

* docs(bug-report): document CSRRS completeness issue (tentative)

* docs(bug-report): explain CSRRS x0 rewrite bypass (tentative)

* fix(constraints): restore destination regression imports

* feat(model): align traces and final operands with Rust

Add the pending trace, witness, and constraint definitions alongside the normalized final-row ISA representation and its proof updates. Add focused regression modules, including FinalOperands. Completeness proofs remain deferred.

* fix(witness): reject traces beyond cycle domain

* fix(witness): require prover-padded trace length

* fix(witness): bind bytecode domain to expanded program

* fix(witness): bound prover chunk dimensions

* proof(constraints): close shift and one-hot Booleanity targets

* proof(constraints): close ten Spartan witness targets

* proof(constraints): close assertion lookup under passing operands

* docs(bug-report): document terminating self-branch constraint failure

* proof(constraints): close ten more honest-witness targets

* proof(constraints): close bytecode read and addition targets

* proof(constraints): close RAM and expansion targets

* feat(bundle): A new bundle to handle the JAL translation issue

* fix(jal): new native proof with correct translation

* Clean general projection lemmas.

* refactor(constraints): reduce lookup operand proofs to active rows

* docs(constraints): clarify translation gaps in pending proofs

* test(constraints): cover load capture consistency checks

* chore(gitignore)

* refactor(isa): share operand and branch calculations

Port 2a46c17 with its branch proof updates. Issue draft: bug-report/isa-shared-helpers.md.

* fix(isa): model device memory and runtime advice state

Port 907d734 with its operand preservation, shared arithmetic helpers, and proof dependencies. Document device memory, advice tape, and operand representation issues under bug-report/.

* feat(isa): add VirtualXORROTL1 execution

Port f42ae4f and its proof-support cases. Issue draft: bug-report/isa-xorrotl1.md.

* fix(isa): represent decoded operands at Rust widths

Port the final-operand representation and proof changes from dfd9e93. Keep LD destination handling for a separate port. Issue draft: bug-report/isa-decoded-operands.md.

* fix(isa): preserve final LD destinations during execution

Port the LD execution and native proof repair from dfd9e93. Issue draft: bug-report/isa-ld-destination.md.

* fix(isa): model JAL using the Rust PC correspondence

Port the JoltBytecode changes from feae045, 3bf46b5, and 78bbca7 together so the bundle, semantics, proof, and projection helpers stay consistent. Include the reviewed issue draft in bug-report/pc_nextPc.md.

* docs(isa): index migration issues and record build verification

* docs(isa): move migration issue drafts into issues directory

* fix(isa): complete native dispatch and operand correspondence

* chore(docs): keep issue drafts local

* test(witness): check native dispatch against witness extraction

Check the normalized ADDI immediate and JALR x0 temporary destination, captured value, target lookup, and PC preservation. Exercise target 6 to guard against restoring the Sail alignment check in the Rust instruction body.

* fix(csrrs): Fixing the issue for csrrs x0

* fix(srliw): Other issue posted on Jolt

* broken(BytecodeRafAtEntryEqOne)

This is not a real pass.
FIXME: This is a real issue

* backup:

This is a backup

* fix(isa): model VirtualHostIO live and replay behavior

* feat(trace): require initialized program state

Prove initial register values through the initializer and expose zero source registers from each initialized trace.

* docs: explain Rust and Sail NextPC correspondence

Document the JAL execution-stage mapping, witness PC columns, and the assumptions used by the updated equivalence proof.

* feat(constraints): implement comparison, slice, and assertion lookup tables

Add `uninterleave`, which follows Rust's `uninterleave_bits` step by step,
and transcribe 24 more tables from their Rust `materialize_entry`: Andn,
Equal, NotEqual, the unsigned and signed comparisons, RangeCheck,
RangeCheckAligned, AlignAddr, UpperWord, LowerHalfWord, SignMask,
SignExtendWord, Pow2, Pow2W, ShiftRightBitmask, ShiftRightBitmaskW,
HalfwordAlignment, WordAlignment, MulUNoOverflow, ValidDiv0 and
ValidUnsignedRemainder. The remaining tables still use the placeholder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): implement negate-if, XOR-rotate, and shift-data lookup tables

Transcribe 15 more tables from their Rust `materialize_entry`:
VirtualNegateIf, the four VirtualXORROT tables (rotations 32, 24, 16, 63),
the seven VirtualXORROTW tables (rotations 16, 12, 8, 7, 22, 19, 6) and
ShiftDataB/H/W. As in Rust, each XOR-rotate family is one definition
parameterised by the rotation amount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): implement window-mask, rev8w, shift/rotate loop, and pext lookup tables

Transcribe the last 12 tables from their Rust `materialize_entry`:
WindowMaskB/H/W, VirtualRev8W, VirtualSRL, VirtualSRA, VirtualSRLW,
VirtualSRAW, VirtualROTR, VirtualROTRW, Pext and PextSigned. The Rust
helpers `rev8w`, `u32::swap_bytes`, `pext`, `window_sign_bit` and
`pext_signed` get their own Lean definitions. Rust's `while` loop in `pext`
becomes a 64-pass `for` loop with the same exit condition.

Every LookupTableKind now has a table, so the `sorry` fallback in
`lookupTableEntry` is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(constraints): close constraints 13 and 14

Prove honest-witness lookup-result and jump-return destination writeback.
Connect successful ISA execution to captured destinations and derive the
trace PC invariant using the existing bytecode memory helpers.

Add the all_assumptions conjunction and expose scoped operand assumptions
through JoltTrace. Split static program definitions from traces and carry
expanded instructions and destination normalization through witness helpers.

Keep the existing source-to-row translation obligations explicit.

Validation: lake build; both constraint proofs have no sorryAx dependency.

* docs(bug-report): document RAM address wrap completeness bug

* Updated review

* docs(model): record fork replay audit and deferred work

* docs(spec): ai helper md file

* docs(bug-report): mark CSRRS writeback issue fixed

* refactor(model): centralize jump row validity

* feat(constraints): prove next source PC for jumps

* feat(constraints): prove next source PC for other rows

* docs(constraints): record proof status and reusable method

* docs: refresh constraint review after fork fast-forward

* feat(constraints): prove register value history

* test(ram): reproduce termination-word history failure

* fix(trace)!: require only satisfiable row assumptions on JoltTrace

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* Prove register read/write constraints (34)-(36)

Add source-operand helpers (register35_rs1?, register36_rs2?) with real-row
characterisations of Rs1Ra/Rs1Value/Rs2Ra/Rs2Value and canonical-source address
round-tripping, expose register42_registersVal_preState, and use them to prove
honestWitness_rs1ValueEqRegistersRead, honestWitness_rs2ValueEqRegistersRead and
honestWitness_rdWriteValueEqRegistersReadWrite.

* refactor(constraints): name register-read helpers by content

The helpers added for (34)-(36) were prefixed with the constraint number
they were first written for, but (36) and (34) also use them. Name them
after what they state instead.

* fix(trace)!: require only satisfiable row assumptions on JoltTrace (#18)

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* feat(constraints): prove public RAM output constraint (26) (#15)

* Prove public RAM output constraint (26)

Execution preserves the device layout and both advice buffers
(JoltIOSetupFrame.execInstr_rule, finalTraceState_ioSame). With the
Rust-enforced JoltIOLayout.Valid and JoltIOState.AdviceFits premises on the
initial device, the honest final RAM word equals the public I/O word on the
public interval.

* refactor(constraints): weaken (26) premise and derive it from Rust's layout

Replace JoltIOLayout.Valid and JoltIOState.AdviceFits with the single
premise the proof uses, JoltIOState.AdviceBelowInput: each advice buffer
ends at or below input_start, and input_start is word-aligned relative
to the lowest RAM address. Region ends and absolute alignment are no
longer assumed.

Add a transcription of MemoryLayout::new up to input_start and prove
AdviceBelowInput.of_memoryLayout: every device laid out by Rust whose
advice passes create_emulator's length checks satisfies the premise.

* proof(constraints): prove (39) up to six shift/rotate table lemmas (#17)

* proof(constraints): reduce (39) to per-table lookup entry lemmas

Collapse the lookup-address and table sums in (39) to the flagged table's
entry at the honest lookup index, then prove 47 of the 55 per-table entry
lemmas. Six shift/rotate lemmas are false for arbitrary expanded bytecode
(non-bitmask shift masks); Pext and PextSigned are open. Counterexamples in
JoltConstraints/Tests/LookupShiftMask.lean.

* proof(constraints): close the Pext and PextSigned lookup entry lemmas

Prove that the PEXT table's pext (contiguous fast path plus the 64-pass
clear-lowest-set-bit loop) equals jolt_virtual_pext_value, and that
pextSigned equals jolt_virtual_pext_signed_value, in the new
LookupPextProofHelpers.lean. Only propext, Classical.choice and Quot.sound.

Constraint (39) now depends on six per-table sorries, the shift/rotate
lemmas that are false for arbitrary bytecode. Drop the randomized Pext
check from Tests/LookupShiftMask.lean, which the theorems supersede.

* docs(human-comments): Started documenting program.lean

* docs: scaffold Zola blog in isolated directory

* chore(ci): remove blog publishing workflow

* docs(site): publish initial Hello World site

* fix(site): retain Pages marker across builds

* chore: clean up capture API and audit docs

* refactor: separate constraints from completeness proofs

Give each constraint a dedicated definition module and a matching completeness module. Add the AllConstraints bundle and an aggregate honest-witness theorem, and clean up proof linter warnings.

* fix(completeness): assemble branch constraint from its theorem

Admit the individual branch completeness proof pending the Rust and Lean model reconciliation, then use it in the aggregate theorem without assuming the constraint as a premise.

* docs(template): blog up

No content of worth has been transferred yet.

* refactor(isa): mirror Rust JoltDevice and MemoryLayout

Replace JoltIOLayout and JoltIOState with Rust's MemoryLayout (all 20
fields) and JoltDevice, using Rust's names. Rename SailJoltState.io to
jolt_device, ramStartAddress to RAM_START_ADDRESS, and the device byte
functions to JoltDevice.load? and JoltDevice.store?, built on Rust's
is_* region checks. Add STACK_CANARY_SIZE.

* fix(isa): model LD and SD with Jolt's MMU

LD and SD now read and write memory directly, following Rust's MMU, with
Rust's checks as panics. The Sail equivalence proofs keep their Sail
assumptions and add JoltRamLoadOk / JoltRamStoreOk. MemoryLayout and
JoltDevice move to JoltISA/JoltDevice.lean.

* refactor: remove dead LD/SD code and unused memory-window arguments

* refactor: read HostIO bytes through Jolt's MMU and drop unused trace assumptions

HostIO byte reads now follow Rust's Mmu::load (trace_load, then load_raw),
so no Jolt instruction uses Sail's memory read. TraceAssumptions keeps only
xRegReadable; the RAM window, privilege and MPRV facts and the per-row
assumption operands are no longer needed.

* docs: start a new model review

* feat: add Rust-faithful program and honest trace models

* feat: prove facts about the Jolt program's rows

* feat: prove uniqueness and the preprocessed bytecode layout

* feat: prove HostIO keeps Sail state and registers stay readable

* feat: prove asserts hold, registers start at 0, and size RAM like Rust

* feat: prove advice regions sit below the inputs

* feat: prove the trace starts at the entry slot and LD/SD addresses are aligned

* feat: add the memory address of a trace row; record completeness conditions

* feat: model Rust's prover config (trace length and RAM size)

* feat: prove the prover's trace length fits and pads the run

* feat: prove the prover's RAM size holds every access; FIXME for upstream ram_K bug

* feat: state the spoil assert condition

* docs: explain when a guest reaches a spoil assert

* docs: link every completeness assumption to its upstream issue

* feat: model Rust's last-row-is-a-jump check (a16z/jolt#1968)

* feat: assume executed code is unchanged (FIXME a16z/jolt#1952)

* feat: make expand opaque; assume next PC does not wrap (asked a16z)

* feat: build witness sizes from the prover config (rewiring phase 0)

* refactor: witness columns read the honest trace (rewiring phase 1)

* docs: list the store fact among the proofs owed

* chore: delete the old program and trace models (kept in history at d044e63)

* docs: bug report and repro for final RAM over 2 GiB

* docs: drop the untested fix from the final RAM report

* docs: FIXME exception for final RAM above 2 GiB (asked a16z)

* refactor: prove the bytecode layout the completeness proofs use (rewiring phase 2)

* refactor: Terminated and RamFits over the honest trace, proved for Rust's sizes

* refactor: honest witness over the trace; Rust's sizes meet the old size assumptions (rewiring phase 2)

* refactor: prove the old trace's facts for the honest trace (rewiring phase 3)

* refactor: constraint proof helpers over the honest trace (rewiring phase 3)

* refactor: completeness theorems over the honest trace (rewiring phase 4); a16z: PC must not wrap

* remove Tests, written against the old trace (rewiring phase 5)

* prove prover_config_log_ram_K_lt: ram_K comes from 64-bit addresses

* prove address-0 accesses: an LD at 0 reads 0, no SD stores at 0; ramAccessesValid is alignment only, proved

* final theorem at Rust's sizes; no completeness theorem takes Terminated; a16z: 2 GiB RAM limit is an assumption

* a16z: a program must not change its own code is an assumption

* final theorem against the verifier's public I/O: the instance's inputs, claimed outputs and panic flag

* prove (16): a taken branch is never the last row (#1968), so the next row starts at PC + imm

* model_review lists only open issues; drop the archives; rename program_fresh to program

* refactor(lean): separate general traces from honest execution

Define Trace independently of HonestTrace and express AllConstraints over
a witness with its instance-bound ConstraintContext. Keep honest witness
construction and completeness in the honest layer.

Prove store-word presence for indexed rows of HonestTrace by induction,
replace the unrestricted admitted lemma, and check its proof and witness
dependencies in the default build.

Share state-preservation rules across memory, Sail, and device proofs.
Move completeness helpers out of the constraint relation layer and keep
model_review focused on open issues.

Validation: lake build (8672 jobs), including StoreWordPresence checks.

* docs: add soundness review notes and Lean tutorials

Add the soundness method and completeness/soundness review document,
alongside soundness examples and Lean tutorial materials.

* docs: close A5 in the completeness review; HonestTrace admits every run Rust's tracer produces

* fix(lean): enforce verifier input and witness-size checks

Require valid public inputs, the padded trace limit, the instance's bytecode
domain, and the verifier's RAM minimum and maximum in ConstraintContext.
Derive input, trace, and bytecode checks from the existing completeness inputs.

Add the approved ramSizeAccepted premise to allConstraints_rust_sizes and
explain it in English beside the theorem. Prover acceptance alone does not
imply this check: the included Rust reproducer traces an empty ELF, obtains
ram_K = 4 from the prover, and receives InvalidRamK with minimum 16 from the
verifier. Its self-jump control passes the verifier input checks.

Build regression checks for oversized outputs, a zero-based layout, invalid
trace and bytecode domains, and RAM sizes outside the verifier bounds.

Validation: lake build (8674 jobs), including both default-build checks;
the saved Rust reproducer and self-jump control pass their observed checks.

* fix(lean): keep RAM completeness explicit and kernel checked

Remove the ramSizeAccepted premise and retain the general RAM-size obligation as a documented sorry, consistent with the existing Rust-bug convention. Prove the nonempty-image lower inequality when the verifier minimum computes. Replace native evaluation in the boundary checks with kernel evaluation and audit the relevant axiom dependencies. Validation: lake build passes (8675 jobs).

* fix(lean): bind trusted advice to the instance

Model trusted-advice contents directly in the fixed instance and retain untrusted advice and the execution tape as prover inputs. Prove that initialized trusted advice cannot vary with prover inputs and that AllConstraints is tape-independent. Include both new files and axiom checks in the default build. Document the separate tape-consistency obligation and the unmodeled bytes-without-commitment case. Validation: lake build JoltConstraints passed (8675 jobs).

* refactor(lean): general runs, instance-level code assumption, Rust runtime advice

- ValidRun: a run of the instance's program (expansion, initial state, start,
  step, link) with no stop rule or advice choice; HonestTrace now extends it.
- JoltInstance.CodeUnchanged replaces the HonestTrace.code_unchanged field
  (a16z, 2026-10-07, #1952); honest traces get it from the premise.
- HonestTrace.advice_from_rust: VirtualAdvice rows carry the values Rust's
  tracer patches in (SourceInstruction.rustAdvice for the division family,
  reusing the bytecode project's advice values); SC.W/SC.D left as a TODO.
- Rv64ProgramImage.ImageNonempty: a16z (phone, 2026-10-08) says a program is
  never empty; premise of the final theorem and of witness_params_ram_bounds.
- witness_params_ram_bounds: FIXME, upper half blocked by #1951 item 4
  (compute_max_ram_k rounds down); docs updated to match.
- Review notes: B1/B2 closed, B4 decision recorded.

Validation: lake build JoltConstraints passes (8675 jobs).

* fix(lean): chunk widths are the prover's choice; drop check scripts

- WitnessParams keeps only Rust's structural chunk-width checks; the prover's
  4/16-or-8/32 choice is WitnessParams.ProverChunkConfig, used only by
  completeness (HonestTrace.witness_params_chunk_config). Remove the unused
  chunkBits_lt_128.
- Move ramAccessesValid out of the relation into
  Completeness/Helpers/RamAccesses.lean.
- Remove the Checks/ build-time check scripts; import advice_inputs directly.
- Docs: constraint_proving.md names the current trace facts; model_review.md
  header states the Rust checkout; review notes close B3 and record the
  soundness premises (2^127 < ringChar F, NextPCNoWrap, CodeUnchanged).

Validation: lake build JoltConstraints passes (8673 jobs).

* feat(lean): define expansions and prove expanded-row facts

Bring SourceInstruction.expand over from the bytecode project (expansions.lean,
expansion_helpers.lean), with LR/SC programs in place of the generator output.
Check every expansion's rows in expansion_facts.lean and use them to prove
expand_program_rows_valid and the six shift lookupEntryCorrect lemmas.

Shift tables: LookupShiftProofHelpers.lean proves the SRL, SRA, SRLW and SRAW
table loops on right-shift bitmasks. shiftMaskOk_of_honest shows every honest
row reads such a mask: immediate masks are checked in the bytecode, and a
register mask is written by the VirtualShiftRightBitmask row just before.

Move the nextPC frame lemmas to nextpc_frame.lean.

* chore(lean): drop unused pc_map_ok_iff

Neither the completeness nor the soundness statement uses it; the proofs work
with pc_map_ok directly.

* feat(lean): state soundness, proof sorried

JoltInstance.InLanguage is Jolt's claim: for some private inputs, Rust's run
stops at the PC stall with the instance's outputs and panic flag. Soundness
assumes a field of characteristic above 2^127, NextPCNoWrap and CodeUnchanged.
Expected to fail today for programs that stop or panic in ways SDK guests
never do (B6).

* ci: bump lean-action to v1.6.1 (pins its cache actions by SHA)

* refactor(lean): restate the advice field of HonestTrace, model the SC flag

advice_from_rust now says, for every step n of the trace, that the value its
VirtualAdvice writes is the value Jolt's tracer gives it (tracerAdviceAtStepN).
The lookups are named helpers: the expansion a step belongs to, the advice steps
before it, and the guest program's instruction at an address. They replace
advice_ordinal and isVirtualAdvice.

rustAdvice now gives SC.W and SC.D the tracer's reservation-success flag,
computed from virtual registers 32 and 33, which Jolt's LR and SC expansions
keep equal to the tracer's reservation. Before, an honest SC always failed.

Comments on RuntimeAdvice, TraceRow and SourceInstruction reworded;
model_review.md and the review method updated for the SC flag.

* refactor(lean): name the honest tracer and some tracer in the advice block

TraceRow.adviceValue? -> someTracerAdvice?, SourceInstruction.rustAdvice ->
honestTracerAdvice, tracerAdviceAtStepN -> honestTracerAdviceAtStepN,
advice_from_rust -> advice_from_honest_tracer. Comments say "the honest
tracer (Jolt's tracer)" instead of "Rust". Docs updated for the new names.

* feat(lean): bound the one-hot chunk width by the verifier's Dory setup

The prover picks the one-hot chunk width; Jolt's verifier only checks it is
nonzero, ordered and divisible. What bounds it is the commitment: Dory's
verifier rejects an opening point with more coordinates than its setup has
variables, and the one-hot chunks are opened at a point with logT + chunkBits
coordinates. The setup size comes from the verifier's preprocessing.

dorySetupLogN translates that setup size (setup_total_vars, rounded up to
even); ConstraintContext gains oneHotFitsSetup. Soundness needs it to show
each cycle selects exactly one bytecode row (2^chunkBits below the field's
characteristic). Completeness proves it for the honest prover's sizes
(witness_params_one_hot_fits_setup, no sorry); its statement is unchanged.

* bug-report: SC.W forced failure is accepted by Jolt's verifier

Honest prover outputs 7; a tracer that writes failure into the SC.W advice
outputs 0x100, and Jolt's unmodified verifier accepts both proofs. Whether
this is a bug depends on which claim a Jolt proof proves (asked in issue.md).
Includes the reproducer (run.sh, make_elf.py, sc_flag.rs, tracer.patch) and
the zip for the issue.

* docs: comments on matches_outputs and withRuntimeAdvice; soundness plan

matches_outputs now says which side is the instance's claimed bytes and which
is the device the honest tracer writes. withRuntimeAdvice gets a comment.
methods/soundness.md gains the layered plan for the soundness proof, what is
done, and where we expect to get stuck.

* feat(lean): soundness layers 0-4 and the start of layer 5

Layer 0: field-cast injectivity, the chunk-width bound from the verifier's
Dory setup, and the 64-bit word bridge.
Layer 1: chunk, bytecode, lookup and RAM selection, and the column reads at
the selected entry.
Layer 2: register-table histories, the canonical register map, initial-state
agreement, and x0 staying zero.
Layer 3: RAM histories and updates, access flags, address remapping, the
layout bound and initial-state agreement.
Layer 4: every lookup table interpreted at its recovered address; the pure
table identities move here from the completeness helpers.
Layer 5 (in progress): independent advice-tape answers with the fixed-tape
inclusion, narrow advice loads, ADDI, register writes and memory addresses.

Soundness premises: characteristic above 2^128 (Akita to be modeled
separately), NoStoreConditional (temporary, a16z/jolt#2037), and
RamRegionFits (to confirm with a16z). The plan in methods/soundness.md
records each layer, the open obligations and the questions for a16z.
bug-report/ram-address-wraparound shows the layout checks alone allow a RAM
domain past 2^64.

* refactor: generate LR and SC expansions in the automated module

* feat(soundness): state temporary tracer address restrictions explicitly

* feat(model): adopt PR 2039 deterministic store-conditional expansion

* feat(soundness): prove XOR and SLTIU steps and SC advice absence

* feat(model): migrate Jolt reference model to 8bbb2357

Model checked memory layout and prover sizing at the public Rust pin.
Keep corrected sizing explicit and transfer completeness to Rust sizing
when the highest touched slot is strictly inside the accepted RAM domain.

Retain the merged advice-length removal, record reproducible LR/SC
generator patches, and refresh trace, constraint and source correspondence
across the six reviewed migration chunks.

Validation: both Lean libraries build; 33 declarations axiom-audited.
Existing sorry obligations and native bit-vector dependencies are retained
and documented for separate review. No heartbeat limits are increased.

* feat(theorems): bundle soundness and completeness assumptions

Soundness takes a JoltField, JoltInstance.SoundnessAssumptions and
JoltInstance.TemporaryRestrictions (Soundness/Assumptions.lean).

Completeness is stated as JoltInstance.completeness over
JoltInstance.CompletenessAssumptions and HonestTrace.ProverAccepts
(Completeness/Assumptions.lean), at Jolt's own prover sizes. The
preprocessed bytecode, entry slot and public I/O are now derived from
the honest trace instead of taken as hypotheses.

Declare HonestTrace and JoltDevice theorems outside the JoltConstraints
namespace instead of with _root_. Note in JoltInstance that the PCS is
ignored and how that differs from Jolt's verifier inputs.

* feat(language): make L exactly what Jolt says it proves

JoltInstance.InLanguage now also requires that Jolt's prover accepts the
honest trace (prover_config.isSome) and that no spoil assert fails.

Completeness becomes the clean statement: CompletenessAssumptions and
InLanguage give private inputs and a witness satisfying AllConstraints.
Helpers derive the corrected sizes from Jolt's prover accepting
(corrected_prover_config_isSome), the preprocessed bytecode, the entry
slot and the public I/O. HonestTrace.ProverAccepts is removed.

* refactor: share the language file and drop unused sizing results

Move the definition of Jolt's language to JoltConstraints/language.lean,
imported directly by both soundness.lean and completeness.lean.

Delete results nothing uses since completeness takes InLanguage:
HonestTrace.allConstraints_rust_sizes, prover_config_eq_corrected_iff,
prover_config_highest_le, prover_config_log_bounds,
HonestTrace.witness_params, HonestTrace.highest_touched_slot, and the
checkedTraceLength_bounds, checkedTraceLength_power and
checkedRamSize_power lemmas. The private RAM helper keeps only the
address fact the corrected-sizing lemma uses.

* docs: record the language and completeness decisions

L stays the fixed-tape language, and now includes Jolt's prover
accepting the trace and its spoil asserts passing; the relaxed tape
semantics is a proof device, and Layer 7 rebuilds one tape from the
witness's answers. Update the soundness statement, the advice-tape
sections, Layers 5a, 7 and 8, the status log, and the assumption table
for the bundles and JoltInstance.completeness.

* refactor(soundness): share Layer 5b register steps and advice bridge

Factor canonical source recovery, lookup-write reasoning and successful
register writes into shared lemmas used by XOR, SLTIU and ADDI. Apply
runtime advice in their execution conclusions to match the trace API,
retaining full post-states, alias coverage and last-cycle execution.

Validation: both Lean libraries build (8751 jobs); the 11 affected public
lemmas use only the standard Lean axioms and the allowed Sail constant.
No new soundness premise, sorry, native proof or heartbeat override.

* feat(soundness): prove six more Layer 5b instruction cases

* feat(soundness): prove alignment and ordinary RAM store steps

* feat(soundness): extend Layer 5b row coverage and temporary liveness

Prove routine register, assertion, and ordinary RAM row steps; add the bytecode PC bridge and temporary HostIO/termination restrictions. Check each short-lived expansion temporary is written before its first read, excluding reserved LR/SC registers. Document the remaining dispatcher and device obligations.

Validation: lake build JoltConstraints (8783 jobs); axiom audit of expand_temps_ok and host_call_exec_sail_frame.

* docs(soundness): prepare Layer 5b fresh-session handoff

Record the 74 proved row kinds, two unreachable kinds, twelve open steps, HostIO liveness boundary, remaining device and dispatcher work, and proof constraints. Reconcile migration summaries with the deleted unused Rust-sizes corollary and mark the older review as historical.

* Add AUIPC and HostIO soundness steps; update Layer 5b handoff

* Start top-down soundness; add panic restrictions and bug write-ups

Soundness proof:
- JoltInstance.soundness is now a short proof from sorried top-level
  helpers in Soundness/TopLevel.lean (tape, Represents, trace
  reconstruction, outputs, prover acceptance, spoil asserts), listed
  under "Proofs owed" in model_review.md.
- New FIXME restrictions PanicStoresOne and ClaimsNoPanic
  (Layer5/PanicRestriction.lean) exclude B6's two panic counterexamples;
  both are Jolt bugs to be fixed upstream.
- HostIO: expanded rows target temporary 40 (HostCallExpansion.lean),
  agreement except that temporary, and liveness lemmas; JAL step.

Docs:
- methods/soundness.md: change of course to top-down, the argument in
  brief, where to start, no known-false statements without approval.
- model_review.md and the review doc point to the new plan.

Blog:
- One hidden-capable post per finding: panic definition, early stop at
  a jump, host-call destination, host-call failure, tracer address
  checks, termination word.
- Hidden-post flag ([extra] hidden), tag taxonomy removed, feed without
  the site-wide last-updated date.

* Checkpoint soundness proof and trace model

* Checkpoint Layer 7 stopping proof integration

Prove last-jump, termination-store, padding, JAL and branch replay lemmas; stage remaining JALR and no-store obligations as named declarations.

* Checkpoint Layer 7 stopping work; add agent guidance and step (c) plan

Prove both architectural JALR replay steps, split the last-row lemma into
named cases, and extract the shared replay-step helpers. Five gaps remain in
StallSuffix.lean; methods/step_c_closing_plan.md is the plan for closing them.
AGENTS.md records the working rules for agents.

StallSuffix.lean and TerminationStore.lean were last edited after the agent's
final build report and have not been rebuilt since.

* Step (c) plan: start by building the last unbuilt edits

* Step (c) plan: add the tape rebuild's advice-pair check to Part 1

* Step (c) plan: state advicePairsOk in the exact form narrow advice programs emit

* Step (c) plan: work on the branch head

* Add the tape rebuild closing plan

* Allow slower proofs only when significantly simpler

* Accept bv_decide for now, tracked for later removal

* Close step (c) stopping proof through shared expansion checks

* Stage Layer 7 tape rebuild obligations

* Prove advice masking and replay stop transfers

* Prove spoil transfer and immediate masking

* Prove RAM address and dimension transfer for tape replay

* Prove rebuilt tape reads stay in low-byte prefix

* Stage rowwise fixed-tape replay certificate

* Replace native bitvector decision with bit-level proofs

* Specialize replay helper to rebuilt tape initial state

* Stage tape rebuild proof and cursor lemma

* Prove tape cursor and non-read replay frames

* Prove tape prefix, rebasing and honest-advice transfer lemmas

The file elaborates; its only remaining sorry is rebuild_rows.

* Construct fixed-tape replay rows from relaxed advice runs

* Lift local instruction pairs to materialized bytecode rows

* Share instruction preservation across tape and device frames

* Close tape rebuild through shared adjacent-row properties

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Quang Dao <qvd@andrew.cmu.edu>
abiswas3 added a commit that referenced this pull request Oct 27, 2026
…ons (#31)

* docs(bug-report): document RAM address wrap completeness bug

* Updated review

* docs(model): record fork replay audit and deferred work

* docs(spec): ai helper md file

* docs(bug-report): mark CSRRS writeback issue fixed

* refactor(model): centralize jump row validity

* feat(constraints): prove next source PC for jumps

* feat(constraints): prove next source PC for other rows

* docs(constraints): record proof status and reusable method

* docs: refresh constraint review after fork fast-forward

* feat(constraints): prove register value history

* test(ram): reproduce termination-word history failure

* fix(trace)!: require only satisfiable row assumptions on JoltTrace

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* Prove register read/write constraints (34)-(36)

Add source-operand helpers (register35_rs1?, register36_rs2?) with real-row
characterisations of Rs1Ra/Rs1Value/Rs2Ra/Rs2Value and canonical-source address
round-tripping, expose register42_registersVal_preState, and use them to prove
honestWitness_rs1ValueEqRegistersRead, honestWitness_rs2ValueEqRegistersRead and
honestWitness_rdWriteValueEqRegistersReadWrite.

* refactor(constraints): name register-read helpers by content

The helpers added for (34)-(36) were prefixed with the constraint number
they were first written for, but (36) and (34) also use them. Name them
after what they state instead.

* fix(trace)!: require only satisfiable row assumptions on JoltTrace (#18)

`JoltTrace.allAssumptions` required `all_assumptions` at every row's
pre-state. Its `MstatusMppMachine` conjunct is false at `init_state`, whose
virtual registers are zero as in Rust's `Cpu::new`, so every trace was
empty and every constraint theorem over traces held vacuously.

Replace the field with `rowAssumptions : TraceAssumptions ...`, which keeps
only what the constraint proofs use: x-register readability, the LD/SD
memory-window facts (`RamWindowAssumptions`), `CurPrivilegeMachine` and
`MstatusMprvZero`. `TraceAssumptions.of_all_assumptions` shows it is weaker.

Tests/TraceNonempty.lean builds a terminated one-row `jal x0, 0` trace
from `init_state`, pins its axioms (no `sorryAx`), and shows
`all_assumptions` fails at its pre-state.

* feat(constraints): prove public RAM output constraint (26) (#15)

* Prove public RAM output constraint (26)

Execution preserves the device layout and both advice buffers
(JoltIOSetupFrame.execInstr_rule, finalTraceState_ioSame). With the
Rust-enforced JoltIOLayout.Valid and JoltIOState.AdviceFits premises on the
initial device, the honest final RAM word equals the public I/O word on the
public interval.

* refactor(constraints): weaken (26) premise and derive it from Rust's layout

Replace JoltIOLayout.Valid and JoltIOState.AdviceFits with the single
premise the proof uses, JoltIOState.AdviceBelowInput: each advice buffer
ends at or below input_start, and input_start is word-aligned relative
to the lowest RAM address. Region ends and absolute alignment are no
longer assumed.

Add a transcription of MemoryLayout::new up to input_start and prove
AdviceBelowInput.of_memoryLayout: every device laid out by Rust whose
advice passes create_emulator's length checks satisfies the premise.

* proof(constraints): prove (39) up to six shift/rotate table lemmas (#17)

* proof(constraints): reduce (39) to per-table lookup entry lemmas

Collapse the lookup-address and table sums in (39) to the flagged table's
entry at the honest lookup index, then prove 47 of the 55 per-table entry
lemmas. Six shift/rotate lemmas are false for arbitrary expanded bytecode
(non-bitmask shift masks); Pext and PextSigned are open. Counterexamples in
JoltConstraints/Tests/LookupShiftMask.lean.

* proof(constraints): close the Pext and PextSigned lookup entry lemmas

Prove that the PEXT table's pext (contiguous fast path plus the 64-pass
clear-lowest-set-bit loop) equals jolt_virtual_pext_value, and that
pextSigned equals jolt_virtual_pext_signed_value, in the new
LookupPextProofHelpers.lean. Only propext, Classical.choice and Quot.sound.

Constraint (39) now depends on six per-table sorries, the shift/rotate
lemmas that are false for arbitrary bytecode. Drop the randomized Pext
check from Tests/LookupShiftMask.lean, which the theorems supersede.

* docs(human-comments): Started documenting program.lean

* docs: scaffold Zola blog in isolated directory

* chore(ci): remove blog publishing workflow

* docs(site): publish initial Hello World site

* fix(site): retain Pages marker across builds

* chore: clean up capture API and audit docs

* refactor: separate constraints from completeness proofs

Give each constraint a dedicated definition module and a matching completeness module. Add the AllConstraints bundle and an aggregate honest-witness theorem, and clean up proof linter warnings.

* fix(completeness): assemble branch constraint from its theorem

Admit the individual branch completeness proof pending the Rust and Lean model reconciliation, then use it in the aggregate theorem without assuming the constraint as a premise.

* docs(template): blog up

No content of worth has been transferred yet.

* refactor(isa): mirror Rust JoltDevice and MemoryLayout

Replace JoltIOLayout and JoltIOState with Rust's MemoryLayout (all 20
fields) and JoltDevice, using Rust's names. Rename SailJoltState.io to
jolt_device, ramStartAddress to RAM_START_ADDRESS, and the device byte
functions to JoltDevice.load? and JoltDevice.store?, built on Rust's
is_* region checks. Add STACK_CANARY_SIZE.

* fix(isa): model LD and SD with Jolt's MMU

LD and SD now read and write memory directly, following Rust's MMU, with
Rust's checks as panics. The Sail equivalence proofs keep their Sail
assumptions and add JoltRamLoadOk / JoltRamStoreOk. MemoryLayout and
JoltDevice move to JoltISA/JoltDevice.lean.

* refactor: remove dead LD/SD code and unused memory-window arguments

* refactor: read HostIO bytes through Jolt's MMU and drop unused trace assumptions

HostIO byte reads now follow Rust's Mmu::load (trace_load, then load_raw),
so no Jolt instruction uses Sail's memory read. TraceAssumptions keeps only
xRegReadable; the RAM window, privilege and MPRV facts and the per-row
assumption operands are no longer needed.

* docs: start a new model review

* feat: add Rust-faithful program and honest trace models

* feat: prove facts about the Jolt program's rows

* feat: prove uniqueness and the preprocessed bytecode layout

* feat: prove HostIO keeps Sail state and registers stay readable

* feat: prove asserts hold, registers start at 0, and size RAM like Rust

* feat: prove advice regions sit below the inputs

* feat: prove the trace starts at the entry slot and LD/SD addresses are aligned

* feat: add the memory address of a trace row; record completeness conditions

* feat: model Rust's prover config (trace length and RAM size)

* feat: prove the prover's trace length fits and pads the run

* feat: prove the prover's RAM size holds every access; FIXME for upstream ram_K bug

* feat: state the spoil assert condition

* docs: explain when a guest reaches a spoil assert

* docs: link every completeness assumption to its upstream issue

* feat: model Rust's last-row-is-a-jump check (a16z/jolt#1968)

* feat: assume executed code is unchanged (FIXME a16z/jolt#1952)

* feat: make expand opaque; assume next PC does not wrap (asked a16z)

* feat: build witness sizes from the prover config (rewiring phase 0)

* refactor: witness columns read the honest trace (rewiring phase 1)

* docs: list the store fact among the proofs owed

* chore: delete the old program and trace models (kept in history at d044e63)

* docs: bug report and repro for final RAM over 2 GiB

* docs: drop the untested fix from the final RAM report

* docs: FIXME exception for final RAM above 2 GiB (asked a16z)

* refactor: prove the bytecode layout the completeness proofs use (rewiring phase 2)

* refactor: Terminated and RamFits over the honest trace, proved for Rust's sizes

* refactor: honest witness over the trace; Rust's sizes meet the old size assumptions (rewiring phase 2)

* refactor: prove the old trace's facts for the honest trace (rewiring phase 3)

* refactor: constraint proof helpers over the honest trace (rewiring phase 3)

* refactor: completeness theorems over the honest trace (rewiring phase 4); a16z: PC must not wrap

* remove Tests, written against the old trace (rewiring phase 5)

* prove prover_config_log_ram_K_lt: ram_K comes from 64-bit addresses

* prove address-0 accesses: an LD at 0 reads 0, no SD stores at 0; ramAccessesValid is alignment only, proved

* final theorem at Rust's sizes; no completeness theorem takes Terminated; a16z: 2 GiB RAM limit is an assumption

* a16z: a program must not change its own code is an assumption

* final theorem against the verifier's public I/O: the instance's inputs, claimed outputs and panic flag

* prove (16): a taken branch is never the last row (#1968), so the next row starts at PC + imm

* model_review lists only open issues; drop the archives; rename program_fresh to program

* refactor(lean): separate general traces from honest execution

Define Trace independently of HonestTrace and express AllConstraints over
a witness with its instance-bound ConstraintContext. Keep honest witness
construction and completeness in the honest layer.

Prove store-word presence for indexed rows of HonestTrace by induction,
replace the unrestricted admitted lemma, and check its proof and witness
dependencies in the default build.

Share state-preservation rules across memory, Sail, and device proofs.
Move completeness helpers out of the constraint relation layer and keep
model_review focused on open issues.

Validation: lake build (8672 jobs), including StoreWordPresence checks.

* docs: add soundness review notes and Lean tutorials

Add the soundness method and completeness/soundness review document,
alongside soundness examples and Lean tutorial materials.

* docs: close A5 in the completeness review; HonestTrace admits every run Rust's tracer produces

* fix(lean): enforce verifier input and witness-size checks

Require valid public inputs, the padded trace limit, the instance's bytecode
domain, and the verifier's RAM minimum and maximum in ConstraintContext.
Derive input, trace, and bytecode checks from the existing completeness inputs.

Add the approved ramSizeAccepted premise to allConstraints_rust_sizes and
explain it in English beside the theorem. Prover acceptance alone does not
imply this check: the included Rust reproducer traces an empty ELF, obtains
ram_K = 4 from the prover, and receives InvalidRamK with minimum 16 from the
verifier. Its self-jump control passes the verifier input checks.

Build regression checks for oversized outputs, a zero-based layout, invalid
trace and bytecode domains, and RAM sizes outside the verifier bounds.

Validation: lake build (8674 jobs), including both default-build checks;
the saved Rust reproducer and self-jump control pass their observed checks.

* fix(lean): keep RAM completeness explicit and kernel checked

Remove the ramSizeAccepted premise and retain the general RAM-size obligation as a documented sorry, consistent with the existing Rust-bug convention. Prove the nonempty-image lower inequality when the verifier minimum computes. Replace native evaluation in the boundary checks with kernel evaluation and audit the relevant axiom dependencies. Validation: lake build passes (8675 jobs).

* fix(lean): bind trusted advice to the instance

Model trusted-advice contents directly in the fixed instance and retain untrusted advice and the execution tape as prover inputs. Prove that initialized trusted advice cannot vary with prover inputs and that AllConstraints is tape-independent. Include both new files and axiom checks in the default build. Document the separate tape-consistency obligation and the unmodeled bytes-without-commitment case. Validation: lake build JoltConstraints passed (8675 jobs).

* refactor(lean): general runs, instance-level code assumption, Rust runtime advice

- ValidRun: a run of the instance's program (expansion, initial state, start,
  step, link) with no stop rule or advice choice; HonestTrace now extends it.
- JoltInstance.CodeUnchanged replaces the HonestTrace.code_unchanged field
  (a16z, 2026-10-07, #1952); honest traces get it from the premise.
- HonestTrace.advice_from_rust: VirtualAdvice rows carry the values Rust's
  tracer patches in (SourceInstruction.rustAdvice for the division family,
  reusing the bytecode project's advice values); SC.W/SC.D left as a TODO.
- Rv64ProgramImage.ImageNonempty: a16z (phone, 2026-10-08) says a program is
  never empty; premise of the final theorem and of witness_params_ram_bounds.
- witness_params_ram_bounds: FIXME, upper half blocked by #1951 item 4
  (compute_max_ram_k rounds down); docs updated to match.
- Review notes: B1/B2 closed, B4 decision recorded.

Validation: lake build JoltConstraints passes (8675 jobs).

* fix(lean): chunk widths are the prover's choice; drop check scripts

- WitnessParams keeps only Rust's structural chunk-width checks; the prover's
  4/16-or-8/32 choice is WitnessParams.ProverChunkConfig, used only by
  completeness (HonestTrace.witness_params_chunk_config). Remove the unused
  chunkBits_lt_128.
- Move ramAccessesValid out of the relation into
  Completeness/Helpers/RamAccesses.lean.
- Remove the Checks/ build-time check scripts; import advice_inputs directly.
- Docs: constraint_proving.md names the current trace facts; model_review.md
  header states the Rust checkout; review notes close B3 and record the
  soundness premises (2^127 < ringChar F, NextPCNoWrap, CodeUnchanged).

Validation: lake build JoltConstraints passes (8673 jobs).

* feat(lean): define expansions and prove expanded-row facts

Bring SourceInstruction.expand over from the bytecode project (expansions.lean,
expansion_helpers.lean), with LR/SC programs in place of the generator output.
Check every expansion's rows in expansion_facts.lean and use them to prove
expand_program_rows_valid and the six shift lookupEntryCorrect lemmas.

Shift tables: LookupShiftProofHelpers.lean proves the SRL, SRA, SRLW and SRAW
table loops on right-shift bitmasks. shiftMaskOk_of_honest shows every honest
row reads such a mask: immediate masks are checked in the bytecode, and a
register mask is written by the VirtualShiftRightBitmask row just before.

Move the nextPC frame lemmas to nextpc_frame.lean.

* chore(lean): drop unused pc_map_ok_iff

Neither the completeness nor the soundness statement uses it; the proofs work
with pc_map_ok directly.

* feat(lean): state soundness, proof sorried

JoltInstance.InLanguage is Jolt's claim: for some private inputs, Rust's run
stops at the PC stall with the instance's outputs and panic flag. Soundness
assumes a field of characteristic above 2^127, NextPCNoWrap and CodeUnchanged.
Expected to fail today for programs that stop or panic in ways SDK guests
never do (B6).

* ci: bump lean-action to v1.6.1 (pins its cache actions by SHA)

* refactor(lean): restate the advice field of HonestTrace, model the SC flag

advice_from_rust now says, for every step n of the trace, that the value its
VirtualAdvice writes is the value Jolt's tracer gives it (tracerAdviceAtStepN).
The lookups are named helpers: the expansion a step belongs to, the advice steps
before it, and the guest program's instruction at an address. They replace
advice_ordinal and isVirtualAdvice.

rustAdvice now gives SC.W and SC.D the tracer's reservation-success flag,
computed from virtual registers 32 and 33, which Jolt's LR and SC expansions
keep equal to the tracer's reservation. Before, an honest SC always failed.

Comments on RuntimeAdvice, TraceRow and SourceInstruction reworded;
model_review.md and the review method updated for the SC flag.

* refactor(lean): name the honest tracer and some tracer in the advice block

TraceRow.adviceValue? -> someTracerAdvice?, SourceInstruction.rustAdvice ->
honestTracerAdvice, tracerAdviceAtStepN -> honestTracerAdviceAtStepN,
advice_from_rust -> advice_from_honest_tracer. Comments say "the honest
tracer (Jolt's tracer)" instead of "Rust". Docs updated for the new names.

* feat(lean): bound the one-hot chunk width by the verifier's Dory setup

The prover picks the one-hot chunk width; Jolt's verifier only checks it is
nonzero, ordered and divisible. What bounds it is the commitment: Dory's
verifier rejects an opening point with more coordinates than its setup has
variables, and the one-hot chunks are opened at a point with logT + chunkBits
coordinates. The setup size comes from the verifier's preprocessing.

dorySetupLogN translates that setup size (setup_total_vars, rounded up to
even); ConstraintContext gains oneHotFitsSetup. Soundness needs it to show
each cycle selects exactly one bytecode row (2^chunkBits below the field's
characteristic). Completeness proves it for the honest prover's sizes
(witness_params_one_hot_fits_setup, no sorry); its statement is unchanged.

* bug-report: SC.W forced failure is accepted by Jolt's verifier

Honest prover outputs 7; a tracer that writes failure into the SC.W advice
outputs 0x100, and Jolt's unmodified verifier accepts both proofs. Whether
this is a bug depends on which claim a Jolt proof proves (asked in issue.md).
Includes the reproducer (run.sh, make_elf.py, sc_flag.rs, tracer.patch) and
the zip for the issue.

* docs: comments on matches_outputs and withRuntimeAdvice; soundness plan

matches_outputs now says which side is the instance's claimed bytes and which
is the device the honest tracer writes. withRuntimeAdvice gets a comment.
methods/soundness.md gains the layered plan for the soundness proof, what is
done, and where we expect to get stuck.

* feat(lean): soundness layers 0-4 and the start of layer 5

Layer 0: field-cast injectivity, the chunk-width bound from the verifier's
Dory setup, and the 64-bit word bridge.
Layer 1: chunk, bytecode, lookup and RAM selection, and the column reads at
the selected entry.
Layer 2: register-table histories, the canonical register map, initial-state
agreement, and x0 staying zero.
Layer 3: RAM histories and updates, access flags, address remapping, the
layout bound and initial-state agreement.
Layer 4: every lookup table interpreted at its recovered address; the pure
table identities move here from the completeness helpers.
Layer 5 (in progress): independent advice-tape answers with the fixed-tape
inclusion, narrow advice loads, ADDI, register writes and memory addresses.

Soundness premises: characteristic above 2^128 (Akita to be modeled
separately), NoStoreConditional (temporary, a16z/jolt#2037), and
RamRegionFits (to confirm with a16z). The plan in methods/soundness.md
records each layer, the open obligations and the questions for a16z.
bug-report/ram-address-wraparound shows the layout checks alone allow a RAM
domain past 2^64.

* refactor: generate LR and SC expansions in the automated module

* feat(soundness): state temporary tracer address restrictions explicitly

* feat(model): adopt PR 2039 deterministic store-conditional expansion

* feat(soundness): prove XOR and SLTIU steps and SC advice absence

* feat(model): migrate Jolt reference model to 8bbb2357

Model checked memory layout and prover sizing at the public Rust pin.
Keep corrected sizing explicit and transfer completeness to Rust sizing
when the highest touched slot is strictly inside the accepted RAM domain.

Retain the merged advice-length removal, record reproducible LR/SC
generator patches, and refresh trace, constraint and source correspondence
across the six reviewed migration chunks.

Validation: both Lean libraries build; 33 declarations axiom-audited.
Existing sorry obligations and native bit-vector dependencies are retained
and documented for separate review. No heartbeat limits are increased.

* feat(theorems): bundle soundness and completeness assumptions

Soundness takes a JoltField, JoltInstance.SoundnessAssumptions and
JoltInstance.TemporaryRestrictions (Soundness/Assumptions.lean).

Completeness is stated as JoltInstance.completeness over
JoltInstance.CompletenessAssumptions and HonestTrace.ProverAccepts
(Completeness/Assumptions.lean), at Jolt's own prover sizes. The
preprocessed bytecode, entry slot and public I/O are now derived from
the honest trace instead of taken as hypotheses.

Declare HonestTrace and JoltDevice theorems outside the JoltConstraints
namespace instead of with _root_. Note in JoltInstance that the PCS is
ignored and how that differs from Jolt's verifier inputs.

* feat(language): make L exactly what Jolt says it proves

JoltInstance.InLanguage now also requires that Jolt's prover accepts the
honest trace (prover_config.isSome) and that no spoil assert fails.

Completeness becomes the clean statement: CompletenessAssumptions and
InLanguage give private inputs and a witness satisfying AllConstraints.
Helpers derive the corrected sizes from Jolt's prover accepting
(corrected_prover_config_isSome), the preprocessed bytecode, the entry
slot and the public I/O. HonestTrace.ProverAccepts is removed.

* refactor: share the language file and drop unused sizing results

Move the definition of Jolt's language to JoltConstraints/language.lean,
imported directly by both soundness.lean and completeness.lean.

Delete results nothing uses since completeness takes InLanguage:
HonestTrace.allConstraints_rust_sizes, prover_config_eq_corrected_iff,
prover_config_highest_le, prover_config_log_bounds,
HonestTrace.witness_params, HonestTrace.highest_touched_slot, and the
checkedTraceLength_bounds, checkedTraceLength_power and
checkedRamSize_power lemmas. The private RAM helper keeps only the
address fact the corrected-sizing lemma uses.

* docs: record the language and completeness decisions

L stays the fixed-tape language, and now includes Jolt's prover
accepting the trace and its spoil asserts passing; the relaxed tape
semantics is a proof device, and Layer 7 rebuilds one tape from the
witness's answers. Update the soundness statement, the advice-tape
sections, Layers 5a, 7 and 8, the status log, and the assumption table
for the bundles and JoltInstance.completeness.

* refactor(soundness): share Layer 5b register steps and advice bridge

Factor canonical source recovery, lookup-write reasoning and successful
register writes into shared lemmas used by XOR, SLTIU and ADDI. Apply
runtime advice in their execution conclusions to match the trace API,
retaining full post-states, alias coverage and last-cycle execution.

Validation: both Lean libraries build (8751 jobs); the 11 affected public
lemmas use only the standard Lean axioms and the allowed Sail constant.
No new soundness premise, sorry, native proof or heartbeat override.

* feat(soundness): prove six more Layer 5b instruction cases

* feat(soundness): prove alignment and ordinary RAM store steps

* feat(soundness): extend Layer 5b row coverage and temporary liveness

Prove routine register, assertion, and ordinary RAM row steps; add the bytecode PC bridge and temporary HostIO/termination restrictions. Check each short-lived expansion temporary is written before its first read, excluding reserved LR/SC registers. Document the remaining dispatcher and device obligations.

Validation: lake build JoltConstraints (8783 jobs); axiom audit of expand_temps_ok and host_call_exec_sail_frame.

* docs(soundness): prepare Layer 5b fresh-session handoff

Record the 74 proved row kinds, two unreachable kinds, twelve open steps, HostIO liveness boundary, remaining device and dispatcher work, and proof constraints. Reconcile migration summaries with the deleted unused Rust-sizes corollary and mark the older review as historical.

* Add AUIPC and HostIO soundness steps; update Layer 5b handoff

* Start top-down soundness; add panic restrictions and bug write-ups

Soundness proof:
- JoltInstance.soundness is now a short proof from sorried top-level
  helpers in Soundness/TopLevel.lean (tape, Represents, trace
  reconstruction, outputs, prover acceptance, spoil asserts), listed
  under "Proofs owed" in model_review.md.
- New FIXME restrictions PanicStoresOne and ClaimsNoPanic
  (Layer5/PanicRestriction.lean) exclude B6's two panic counterexamples;
  both are Jolt bugs to be fixed upstream.
- HostIO: expanded rows target temporary 40 (HostCallExpansion.lean),
  agreement except that temporary, and liveness lemmas; JAL step.

Docs:
- methods/soundness.md: change of course to top-down, the argument in
  brief, where to start, no known-false statements without approval.
- model_review.md and the review doc point to the new plan.

Blog:
- One hidden-capable post per finding: panic definition, early stop at
  a jump, host-call destination, host-call failure, tracer address
  checks, termination word.
- Hidden-post flag ([extra] hidden), tag taxonomy removed, feed without
  the site-wide last-updated date.

* Checkpoint soundness proof and trace model

* Checkpoint Layer 7 stopping proof integration

Prove last-jump, termination-store, padding, JAL and branch replay lemmas; stage remaining JALR and no-store obligations as named declarations.

* Checkpoint Layer 7 stopping work; add agent guidance and step (c) plan

Prove both architectural JALR replay steps, split the last-row lemma into
named cases, and extract the shared replay-step helpers. Five gaps remain in
StallSuffix.lean; methods/step_c_closing_plan.md is the plan for closing them.
AGENTS.md records the working rules for agents.

StallSuffix.lean and TerminationStore.lean were last edited after the agent's
final build report and have not been rebuilt since.

* Step (c) plan: start by building the last unbuilt edits

* Step (c) plan: add the tape rebuild's advice-pair check to Part 1

* Step (c) plan: state advicePairsOk in the exact form narrow advice programs emit

* Step (c) plan: work on the branch head

* Add the tape rebuild closing plan

* Allow slower proofs only when significantly simpler

* Accept bv_decide for now, tracked for later removal

* Close step (c) stopping proof through shared expansion checks

* Stage Layer 7 tape rebuild obligations

* Prove advice masking and replay stop transfers

* Prove spoil transfer and immediate masking

* Prove RAM address and dimension transfer for tape replay

* Prove rebuilt tape reads stay in low-byte prefix

* Stage rowwise fixed-tape replay certificate

* Replace native bitvector decision with bit-level proofs

* Specialize replay helper to rebuilt tape initial state

* Stage tape rebuild proof and cursor lemma

* Prove tape cursor and non-read replay frames

* Prove tape prefix, rebasing and honest-advice transfer lemmas

The file elaborates; its only remaining sorry is rebuild_rows.

* Construct fixed-tape replay rows from relaxed advice runs

* Lift local instruction pairs to materialized bytecode rows

* Share instruction preservation across tape and device frames

* Close tape rebuild through shared adjacent-row properties

* Update plan docs after closing Layer 7; add the Layer 6 plan

Bring methods/soundness.md and model_review.md in line with the proof:
Layers 7 and 8 are proved, and the only remaining sorries are the
execution-prefix agreement and helpers C and D. Add methods/layer6/
with a plan for Layers 5b-6 split across five agents, and a runbook for
starting them in methods/soundness.md. Mark the step (c) and tape
rebuild plans as completed.

* AGENTS.md: Claude is the auditor; reports must be checkable

* Fix the witness-to-run relation for division advice; move the Jolt pin to 5def437f

RepresentsRows no longer compares Rs1Value and Rs2Value at instruction
temporaries (registers 40-47). For REM, REMU, REMW, REMUW and DIVUW with a
zero divisor, every quotient advice passes the expansion's checks, so a
witness may record advice other than the honest tracer's while the result is
the same. The honest run and the witness then differ only in temporaries.
represented_last_control_read takes a non-temporary hypothesis, discharged at
its three callers by expanded_control_read_not_temporary.

Record a16z/jolt main 5def437f as the current pin, cite the stopping rule at
PR #2109's merge commit 0249591a, and refresh citations into changed Jolt
files. Replace the old Layer 6 agent plans with the advice-fix and pin-update
plans and reports. Relax proof-speed targets in AGENTS.md for now.

* Model the verifier's 256-cycle floor; restrict full-length traces for helper C

Jolt's base verifier now rejects trace lengths below 256 (a16z/jolt 521e53f6).
ConstraintContext gains traceLengthFloor, and completeness supplies it from
the prover's padding. This removes helper C's first counterexample.

The remaining mismatch (a witness whose real rows fill the largest
power-of-two length within the maximum is accepted by Jolt's verifier but
refused by its prover) becomes the FIXME restriction NoMaxLengthTrace in
TemporaryRestrictions. Helper C takes that field and is no longer known false;
its DANGER labels are removed. C remains unproved.

* Model operandless HostIO; drop the HostCallRdZero restriction

Jolt now decodes VirtualHostIO with no operands and rejects a nonzero rd
(a16z/jolt bc4bfb1f). Both instruction types lose HostIO's operands; its row
names no destination or source, so the witness register table is unchanged and
host_call_selected_step returns full RegisterAgreement via
RegisterAgreement.noWrite. HostIO's executor still reads x10-x13 implicitly.

The HostCallRdZero restriction, RegisterAgreementExceptHostTemp and its
lemmas, and HostCallExpansion.lean existed only for the old rd=x0 rewrite to
temporary 40 and are deleted. HostCallsSucceed remains.

* Layer 6 stage 1: stage the execution-prefix induction over source instructions

Move soundness_execution_prefix_agreement, statement unchanged, to
Soundness/Layer6/PrefixAgreement.lean and prove it by strong induction from
two staged lemmas: boundaryState_empty and boundary_step. The invariant
BoundaryState holds between source instructions, where every expansion is
complete: represented rows and RAM, real rows, no earlier stop, honest advice,
the last row ending its source, register agreement outside instruction
temporaries (new RegisterAgreementOn), a zero termination word before any
termination store, and NextStep readiness for the next source instruction.

* Remove finished plans; add the Layer 6 stage 2 plan

The step (c), tape-rebuild, advice-fix, pin-update and Layer 6 stage 1 plans
are done; their reports remain and git keeps the plans. Stage 2 splits
boundary_step into expansion_start, row_step, division_block and
expansion_finish.

* AGENTS.md: cite Jolt by file and function; refresh only changed code

* Layer 6 stage 2: split one source instruction into rows

Prove boundary_step, statement unchanged, from four staged lemmas:
expansion_start, row_step (one row of an expansion with no advice row),
division_block (a whole advice-bearing division expansion, with honest advice
in the run), and expansion_finish (the source-boundary transition). The new
ExpansionState carries the boundary invariant inside an expansion, with
scratch registers live only while the expansion is unfinished.

* Add the Layer 6 reuse map and the stage 3a and 3b plans

* Layer 6: one file per staged lemma; RegisterAgreementOn helpers

Move the Layer 6 definitions to PrefixState.lean and each staged lemma to its
own file (BoundaryStart, DivisionBlock, RowStep, ExpansionFinish), with no
statement or proof changed, so agents can work in parallel. Add
RegisterAgreementOn.mono and RegisterAgreement.toOn.

* Layer 6 stage 3a: stage the division block

Prove division_block, statement unchanged, from five staged steps: the source
layout (one of eight divisions, rows = its generated program with the advice
patched), the witness-advice run of a straight-line arithmetic sequence
(generic over the twelve row kinds the divisions use), the final Sail result
from *Program_sound, the honest run from *Program_concrete agreeing outside
instruction temporaries, and appending the honest rows.

* Layer 6 stage 3b: prove expansion_start; build the empty run

expansion_start is proved. boundaryState_empty constructs the empty run and
proves every BoundaryState field, staging two facts: first_row_of_entry_slot
(the entry slot's row starts its source instruction) and empty_run_ram_word
(the empty run's final RAM image equals the initial image outside the
termination word). context_initial_termination_zero is now public.

* Add the Layer 6 stage 4a and 4b plans

* Layer 6 stage 4b: prove the entry row and the empty run's RAM word

first_row_of_entry_slot and empty_run_ram_word are proved with their
statements unchanged. The initial panic word is staged as
initialized_device_words.

* Layer 6 stage 4a part A: live-register step lemmas and the VirtualAdvice step

The register helpers and the eight step lemmas used by the division
expansions now work over RegisterAgreementOn; the full-agreement versions
are derived from them. advice_step executes a VirtualAdvice row with the
witness's range-checked word.

* Layer 6 stage 4c: prove the initial panic word; share the I/O zero-word lemma

initialized_device_words is proved, so boundaryState_empty no longer
reaches sorryAx. initialRamWord_after_output_zero covers both the panic
and termination words; first_address_row_starts is split out of
first_row_of_entry_slot.

* Add the Layer 6 stage 5b plan: close a source expansion

* Stage 5b plan: the terminal next-PC lemma needs a real successor cycle

* Layer 6 stage 5b: prove expansion_finish from the staged terminal next-PC lemma

expansion_finish is proved with its statement unchanged. The witness's
next unexpanded PC after a source's last row, given a real next cycle, is
staged as terminal_next_unexpanded_pc. selected_interior_current_remaining
is extracted from selected_interior_predecessor, and stall_next_cycle_of_target
is public as next_cycle_of_target.

* Add the Layer 6 stage 5c plan: the next PC after a source's last row

* Remove finished Layer 6 stage plans

* Add the Layer 6 stage 6a plan: append the honest division rows

* Stage 6a plan: avoid an import cycle with a definitions file

* Stage 6a plan: describe the uncommitted Part B work for a fresh agent

* Layer 6 stage 4a part B and stage 5c: witness-advice run, terminal next PC

Part B proves arithmetic_advice_sequence_run with its statement unchanged,
through the plain row step for the twelve division row kinds; the other
plain kinds are staged as plain_row_step_other.

Stage 5c proves terminal_next_unexpanded_pc for ordinary, JAL and JALR
rows; the branch case is staged as terminal_branch.

* Stage 5c plan: the taken-branch target bound; stage 6a plan: Part B is committed

* Add the Layer 6 stage 6b plan: live-register steps for the remaining plain rows

* Stage 6a plan: moved private helpers may become public

* Stage 6b plan: leave the two new step lemmas for a later stage

* Layer 6 stage 6a: prove division_append_honest from three staged replay lemmas

The shared division declarations move unchanged to DivisionDefs.lean;
arithmetic_exec_eq becomes public. division_append_honest is proved with
its statement unchanged from division_replay_rows, division_replay_complete
and division_replay_honest_new, which are staged in DivisionAppend.lean.

* Add the Layer 6 stage 6c and 6d plans; remove the finished 6a plan

* Layer 6 stage 5c: prove the branch case; expansion_finish is sorry-free

terminal_branch is proved with its statement unchanged: the real next row
bounds the taken target, so it does not wrap. next_cycle_address is
extracted from next_cycle_of_target, and represented_last_branch_registers
from stall_suffix_initial_branch.

* Docs: bring Proofs owed, the Layer 6 status and the Layer 5b handoff up to date

List the ten remaining soundness sorrys with their current files, describe
the Layer 6 induction as built, replace the stale Layer 5b handoff (deleted
HostIO files, missing layer6/README.md) with the current step-lemma state,
record the 5def437f pin, and add the Layer 6 cleanup items.

* Docs: Layer 7 is proved except helpers C and D

* Add the Layer 7 helper D plan: spoil asserts pass

* Helper D plan: cite where SpoilAssertsPass is defined

* Plan: do the unknown areas first (device-memory LD/SD, helper C); drop the deleted HostCallRdZero

* Runbook: three agents, two strong and one medium

* Layer 6 stage 6c (partial): prove division_replay_honest_new

The appended division rows record the honest tracer's advice. Three
private helpers become public with their statements unchanged.

* Layer 6 stage 6b: port the remaining plain-row step lemmas to RegisterAgreementOn

Fifteen Layer 5 families now take agreement on live registers, need their
sources live and add their destination. ImmediatePowerMask and XorRotation
are left unchanged because no source expansion emits their instructions.

* Remove the finished stage 6b plan

* Proofs owed: the two unreachable Layer 5 families

* Layer 7 helper D: prove spoil asserts pass from one staged expansion fact

binaryAssertion_passes_of_inputs extracts the shared constraint argument.
D follows from it, RepresentsRows and the staged
source_spoil_assert_read_not_temporary: no expansion emits a spoil assert
that reads an instruction temporary.

* Add the Layer 7 helper C staging plan

* Remove the finished helper D plan

* Add the helper D spoil-assert fact plan (checker extension, approved by Ari)

* Helper D fact plan: the checker is a proof tool

* Layer 7 helper C: stage its three parts and prove C from them

represented_trace_ends_in_jump, checked_trace_length_fits and
represented_ram_size_computes are staged in ProverAccepts.lean; C's
statement is unchanged.

* Remove the finished helper C staging plan

* Runbook: agent 3 is the strong agent; agents 1 and 2 are medium

* Add the device-memory LD/SD staging plan (largest remaining unknown)

* Layer 6 stage 6d: prove the division program result and honest rerun

division_program_result and division_program_honest move unchanged to
DivisionPrograms.lean and are proved from the JoltBytecode *Program_sound
and *Program_concrete theorems and one frame over the program rows.

* Remove the finished stage 6d plan

* Device memory: stage the LD and SD steps by region; no counterexample found

ld_device_step and sd_device_step are staged over seven device regions with
DeviceRamAgreement, the below-RAM part of the cut RAM agreement. The audit
finds every region covered by the constraints and the current restrictions.

* Remove the finished device-memory staging plan

* Layer 6 stage 6c: prove division_replay_rows

The honest program run is split into rows and appended to the prefix.
zeroRuntimeAdvice is generalized to runtimeAdviceFor, which takes the word;
four private PlainRow helpers become public with statements unchanged.

* Add the device-memory step proof plan

* Layer 7 helper D closed: the checker rules out spoil asserts in sequences

noSpoilAsserts joins sequenceOk, and source_spoil_assert_read_not_temporary
is proved from it and the native rows' temporary check. expansion_facts.lean
checks in 17.5 s, up from 17.1 s.

* Remove the finished helper D fact plan

* Add the helper C padded-length plan

* Layer 7 helper C: prove checked_trace_length_fits

The row count plus one fits the largest power of two at most the public
maximum, so checked padding succeeds within it.

* Remove the finished helper C padded-length plan

* Device memory: prove the LD and SD steps from five staged region lemmas

sd_device_step and ld_device_step keep their statements. The table-column
argument, address classification and byte packing are proved once; the
store cases, the output, panic and termination store images, and the load
image bytes are staged.

* Device-memory proof plan: the five region lemmas

* Proofs owed: the eleven remaining soundness sorrys at the end of 2026-10-23

* Device-memory report: the region lemmas need run-level device frames

* Layer 6 stage 6c: the division prefix frame

One parameterized frame proof now gives both DivisionFrame and, for rows
that write only temporaries, Sail equality. division_program_prefix_frame:
every proper prefix of a generated division program keeps the Sail state.
division_replay_complete is still staged.

* Runbook: agents 2 and 3 are strong; agent 1 is medium

* Add the helper C ends-in-a-jump plan

* Add the helper C RAM-size plan

* Layer 7 helper C: prove the trace ends in a jump

A branch stall cannot end a represented trace: its replay keeps a nonzero
next unexpanded PC through every remaining witness cycle, contradicting the
final zero shift and padding. The selection, branch-replay and branch-start
proofs are extracted in StallSuffix.lean and the old lemmas derived from them.

* Remove the finished helper C ends-in-a-jump plan

* Layer 6 stage 6c: block-row frames for the division replay

rs1Value_real and rs2Value_real now take any Trace. Candidate and replay
pre-states keep the prefix frame at every block row, and equal final states
give equal final RAM words. division_replay_complete is proved from these and
the staged division_replay_complete_transfer.

* Layer 7 helper C closed: prove represented_ram_size_computes

Every recorded RAM address is zero or at least the layout's lowest address,
and verifier sizing bounds the image end, so checked prover RAM sizing
succeeds. Two private lemmas become public with statements unchanged.

* Remove the finished helper C RAM-size plan

* Device memory: prove the output-store image; run-level device frames

UntrustedAnswerRun.device_relation, final_device_same and
final_outputs_fit_layout are proved for any run, and the completed-trace
versions are derived from them. device_output_store_image is proved; four
region lemmas remain staged.

* Add the row_step staging plan

* row_step plan: prepared-state PC lemma and live-register steps (agent 2's audit)

* row_step staging audit report

* row_step plan: avoid the PlainRow/StallSuffix import cycle

* Layer 6: stage row_step's per-kind steps and prove row_step from them

The prepared-state PC fact is extracted from untrusted_row_pc and
untrusted_row_next_pc, and the append constructor is generalized to rows that
touch memory. row_step dispatches once on a small row classification to
plain_row_step and seven staged steps: LD and SD for ordinary RAM and device
memory, terminal HostIO, JAL and JALR.

* row_step plan: next, prove the staged steps

* Layer 6 stage 6c closed: prove division_replay_complete_transfer

The bytecode source selectors equal the witness operands
(bytecodeRs1Register_eq_rs1Operand, bytecodeRs2Register_eq_rs2Operand), and
the honest replay inherits every ExpansionState field of the witness block.
division_append_honest no longer depends on sorryAx.

* Remove the finished stage 6c plan

* Layer 5: the JALR step over live registers

jalr_step reads the live base before writing the link, clears target bit
zero, and adds the destination to the live set.

* Device memory closed: prove the four remaining region lemmas

The shared instruction frame gains a variant whose byte-store obligation
applies only to SD; every existing public frame statement is derived from it.
With it, a non-SD row keeps the whole device, the run keeps panic false until
a recorded panic store, and the store-cases, panic, termination and load
region lemmas are proved. ld_device_step and sd_device_step no longer depend
on sorryAx.

* Remove the finished device-memory proof plan

* Add the branch/tape-load step and division layout plan

* Branch step: derive target alignment from the witness; record the odd-offset model gap

Agent 3 found that an image may contain an odd branch offset, which Jolt's
decoder never produces, and that a taken one fails Sail's alignment
assertion. A satisfying witness can never take it, so the branch step gets
alignment from the next cycle, as terminal_branch does.

* Layer 6: prove the JALR and LD/SD row steps

ramAgreement_iff splits the cut RAM agreement into ordinary-RAM and device
agreement once, for both the next and final columns. One append per
instruction family covers both address regions. ld_ram_step, sd_ram_step and
sd_ram_step_agreement now take agreement on live registers; five shared
append helpers become public with statements unchanged.

* Add the plain_row_step_other plan

* Layer 5: port jal_step and host_call_selected_step to live registers

* plain_row_step_other plan: corrections from the audit (reuse as is; move helpers down)

* Layer 6: prove the JAL, HostIO and scalar plain-row steps; branch step and misa invariant

jal_row_step and host_row_step are proved from a shared append for
non-memory rows. plain_row_step_other is proved for every scalar kind;
the branch and tape-load cases remain staged. The branch step derives
target alignment from the witness and uses the preserved misa value.

* Plans: reassign the tape-load step; the checker does not check advice-load widths

* Plain steps report: branch step, misa invariant, advice-load width counterexample

* plain-row-other plan: tape-load step over any destination, not adviceLoad_step

* Division layout: move the staged theorem below DivisionPrograms (plan correction and report)

* Layer 6: prove the branch and tape-load plain rows; plain_row_step_other closed

The checker now requires advice-load byte counts of 1, 2, 4 or 8.
The tape-load step covers any destination and shares the range-check
written-value lemma with advice_step. Delete the finished plan.

* Layer 6: prove division_expansion_layout (moved unchanged into DivisionLayout.lean)

Every soundness lemma now has a proof. Delete the finished plan.

* Plan: remove every bv_decide, the soundness use first

* bv_decide plan: correct the use count

* Docs: soundness theorem closed; record axioms, bv_decide list and cleanup list

Full build passes at b9efd78; #print axioms JoltInstance.soundness lists
propext, sys_enable_experimental_extensions, Classical.choice, Quot.sound
and one bv_decide axiom (branch_target_toNat), with no sorryAx. Replace the
stale Layer 6 runbook, order and handoff sections with the remaining work.

* bv_decide removal: limit scope to the soundness path (Ari)

* bv_decide removal: restore Part 2 (all 19 older uses go too, after Part 1 is pushed)

* Remove the soundness-path bv_decide: prove the branch-offset sign extension bit by bit

encoded_branch_offset now has a bit-level proof, and branch_target_toNat
uses it. After a full build, #print axioms JoltInstance.soundness lists
only propext, sys_enable_experimental_extensions, Classical.choice and
Quot.sound.

* Docs: soundness axioms after removing its bv_decide

* Remove the remaining 19 bv_decide uses

Shared bit-mask lemmas (BitVecMasks.lean) replace the mask, alignment and
byte-extraction uses in Splice, Sd and the lookup-entry helpers; the
byte-swap facts reuse the soundness proof's kernel-checked lemmas, moved
unchanged into Layer4/ByteSwap.lean. No statement changed. The full build
passes (8,832 jobs); JoltInstance.soundness and JoltInstance.completeness
depend on no bv_decide axiom.

* Delete the rev8_32 and rev8w_eq wrappers; use the shared byte-swap lemmas directly

Full build passes (8,832 jobs); soundness and completeness axioms unchanged.

* Docs: no bv_decide remains; record the axioms; delete the finished plan

* bv_decide report: agent's recheck after the cleanup

* chore: remove obsolete blog site and update writeup references

---------

Co-authored-by: Quang Dao <qvd@andrew.cmu.edu>
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