Repository navigation
proof(constraints): prove (39) up to six shift/rotate table lemmas - #17
Merged
Merged
Conversation
quangvdao
marked this pull request as ready for review
September 28, 2026 23:22
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.
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.
quangvdao
force-pushed
the
qvd/registers-34-36
branch
from
September 29, 2026 00:20
76679ef to
0d9fa22
Compare
quangvdao
force-pushed
the
qvd/lookup-39
branch
from
September 29, 2026 00:22
9fb7a4a to
b5483ab
Compare
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reduces constraint (39),
LookupOutput(t) = Σ_k Σ_T TableFlag_T(t) · InstructionRa(k,t) · T(k), to one entry lemma per lookup table, and proves 49 of the 55. The main theoremhonestWitness_lookupOutputEqInstructionReadRafhas nosorryof its own. ItssorryAxcomes only from the 6 shift/rotate lemmas (VirtualSRL,VirtualSRA,VirtualSRLW,VirtualSRAW,VirtualROTR,VirtualROTRW), which are false in the current model; kernel-checked counterexamples are included. The cause is a missing program-validity fact on the Lean side, not a Rust bug (details below).Stacked on #14 (
qvd/registers-34-36@ 0d9fa22). Draft until the six lemmas are scoped: they need the program-validity fact, not more proof.constraints.mdstill lists (39) as TODO.qvd/registers-34-36@ 0d9fa22qvd/lookup-39@ b5483abPextandPextSigned, in the newLookupPextProofHelpers.leanHow the proof goes
Each step is an equality, so the constraint at a row fails exactly when that row's table lemma fails.
instructionRead_honestcollapsesΣ_k InstructionRa(k,t) · T(k)toTat the honest lookup index.Finset.sum_ite_eqcollapses the flag sum to the one flagged table.rowLookupOutput_eq_zero_of_lookupTable_none. Padding rows are handled separately in the main theorem.lookupEntryCorrect_of_lookupTablematches on the table kind and calls the 55 lemmaslookupEntryCorrect_<Table>inLookupEntryProofHelpers.lean.Per-table status
The assert tables (HalfwordAlignment through ValidUnsignedRemainder) get their condition from the row's successful execution (
JoltTraceRow.executes).Pext and PextSigned
LookupPextProofHelpers.leanprovespext_eq_jolt_virtual_pext_valueandpextSigned_eq_jolt_virtual_pext_signed_valuefor all 64-bit operands. They relate the table functions inlookup_table.leanto the honest values fromJoltBytecode/JoltISA/Values.lean.2^tz · nwithn &&& (n+1) = 0, sojolt_pext_nat_two_pow_mulandjolt_pext_nat_of_low_onesreducejolt_pext_nat 64to(x >>> tz) &&& n. The casen = 2^64 − 1(wheren + 1wraps) is covered.forInbecomes aList.foldlof one step on(bits, k, out). The invariant afternpasses isjolt_pext_nat 64 x y = out + 2^k · jolt_pext_nat 64 x bits, without < 2^k,k ≤ n, and the lownbits ofbitsclear. Sobits = 0after 64 passes.jolt_pext_nat_peelhandles one step: clearing the lowest set bitpof2^p(2q+1)gives2^(p+1)q.cpopequalsjolt_popcount_nat 64. The extracted value is below2^popcount(jolt_pext_nat_lt), and its bitpopcount − 1isx's bit atlog2 y(jolt_pext_nat_testBit_top), which is the bitwindowSignBitreads. So adding2^64 − 2^popcountequals or-ing in~~~(2^popcount − 1).The six false lemmas
The shift and rotate tables read the right operand as a right-shift bitmask, with ones from bit
supward. They are correct only for such masks. The honest output shifts byctzof whatever mask the row carries.4with mask2gives indexinterleaveLookupOperands 4 2 = 36. The table entry is 0 for the four shift tables and 4 for the two rotate tables. The honest output is 2 for all six.VirtualSRLWwith mask 0 gives index 34, entry 0 and output 5. The earlier zero-mask fix coveredjolt_virtual_srliw_valueonly.SRLICounterexample.not_lookupEntryCorrectis a one-row programVirtualSRLI v32, v33, 2. The row is accepted becauseOperandsRepresentableonly asks2 < 2^64, andLookupEntryCorrectfails wheneverv33 = 4. I did not build a fullJoltTracerefuting the main theorem, since that needsinit_state,prepareSourceand the linking obligations.Why this is a Lean-side gap, not a Rust bug. Rust's
to_lookup_outputalso usestrailing_zeros, e.g.crates/jolt-lookup-tables/src/instructions/virt/srlw.rsat a16z/jolt 754fc88. But Rust's expander only emits proper masks: they come from ShiftRightBitmask outputs,rotr64androtri32. Lean'sJoltProgram.expandedBytecodeis an arbitrary array (the FIXME onJoltProgram.rowValid,program.lean:173). These lemmas become provable onceJoltProgramrecords how rows are produced, i.e. the source → row construction item.One observation for the Rust side, not filed: the public helper
inline.rs::rotri(rs1, imm, rd)would emitVirtualROTRIwith any immediate. In-repo it is only called with proper masks.Other changes
instructionLookupChunkandinstructionLookupRamove to the newInstructionLookupRa.lean, so the selection lemmas no longer import the constraint file.model_review.md: the (39) row names the helper file's FIXME.JoltConstraints/Tests/LookupShiftMask.leanholds the counterexamples. Like the otherTests/files it is not in the default build; check it withlake env lean JoltConstraints/Tests/LookupShiftMask.lean. The randomized Pext comparison from the first commit is removed now that the theorems cover it.Trust footprint
#print axioms honestWitness_lookupOutputEqInstructionReadRaf:load_reservation,match_reservation,plat_term_write,sys_enable_experimental_extensions, pluspropext,Classical.choice,Quot.sound.sorryAx: only through the 6 shift/rotate lemmas above.bv_decidenative axioms (*._native.bv_decide.ax_*): results of the compiled LRAT checker that the kernel does not re-check. The repo already relies on the same mechanism, e.g.StoreFamily/Splice.lean, but this PR adds many more. Reviewers may prefer these replaced by kernel proofs over time.pext_eq_jolt_virtual_pext_valueandpextSigned_eq_jolt_virtual_pext_signed_valueuse onlypropext,Classical.choiceandQuot.sound.lookupEntryCorrect_PextandlookupEntryCorrect_PextSignedhave nosorryAx. Their 256 native axioms all come from the shareduninterleave_fst_getLsbDanduninterleave_snd_getLsbD.Validation
At
b5483ab:lake build JoltConstraints: 3566 jobs OK. The newsorrywarnings are the 6 shift/rotate lemmas inLookupEntryProofHelpers.lean.LookupPextProofHelpers.leancompiles with no warnings.lake env lean JoltConstraints/Tests/LookupShiftMask.leanchecks with no errors.#print axiomson the main theorem gives the list above:sorryAxplus the 215 native axioms.maxHeartbeatschanges.LookupEntryProofHelpers.leantakes about 45–70 s wall.AI usage
Written with Claude Code. An agent drafted the reduction and the table lemmas; the build, axiom listing and counterexample checks were rerun separately before opening. The Pext proofs were written in this session and checked the same way.
🤖 Generated with Claude Code