Skip to content

feat: Clean DSL + Simplified per-chip RISCV spec + Generic field + Trace Theorms + Partial Honest Witness Gen#97

Merged
dtumad merged 10 commits into
mainfrom
full-clean-dsl-implementation
Jun 19, 2026
Merged

feat: Clean DSL + Simplified per-chip RISCV spec + Generic field + Trace Theorms + Partial Honest Witness Gen#97
dtumad merged 10 commits into
mainfrom
full-clean-dsl-implementation

Conversation

@dtumad

@dtumad dtumad commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Overview

This branch rebuilds SP1's RISC-V chip verification from the ground up. The previous version restated each chip's constraints structurally and proved facts about them under a toProp mapping. This version is Clean-native and semantically specified: every chip is a real circuit in the public Clean DSL, its arithmetic is re-derived in-project and proven faithful to SP1, and each chip is connected directly to the official RISC-V Sail specification.

The old SP1Foundations / SP1Operations / SP1Chips libraries are removed and replaced by a new SP1Clean/ tree.

Main changes

  • Built on the Clean DSL. Chips are composed from true Clean sub-circuits (gadgets + register readers) rather than inlined constraint lists. Each operation exposes one circuit and one semantic spec (what the row means, e.g. a 64-bit add identity), not a restatement of the constraint syntax.

  • Anchored to the RISC-V Sail spec. Each chip carries a small "Sail bridge" proving that a real chip row reaches the official RISC-V ISA semantics (correct_<op>_native). These now depend only on the chip's own native arithmetic — no borrowed proofs.

  • Models interactions much more SP1 faithfully. Beyond the arithmetic, the work anchors SP1's actual emitted constraints (extracted from the Rust source) to the Lean circuits, and adds a whole-trace layer: the four interaction buses (state, byte, program, memory) with per-bus consistency/balance theorems, so interactions no longer add anything to the trust base (aside from explicit assumptions about multiplicity balance).

  • New ALUX0 chip. Adds AluX0Chip, covering ALU-family instructions whose destination is x0 (result discarded). It validates the program/register accesses without computing the ALU result, using an immediate-capable reader.

  • Field-generic + honest witness generation. All circuits are generic over a prime field. A new witness-conformance layer pairs auto-generated vectors (dumped from SP1's real populate) with native_decide anchors, so the Lean model is checked against SP1's actual witness generation. This ensures conformance but is not actually a proof that witness generation is implemented faithfully.

  • Toolchain / dependencies. Moves to Lean 4.28 with mathlib v4.28, adds the public Clean DSL, and pulls the RISC-V Sail model (LeanRV64D) and ISA functions (RISCV). Constraint extraction moves from update_constraints.py to update_extracted.py.

SP1Clean/ layout

  • Extracted/ — constraint data generated from the SP1 Rust source
  • Foundations/ — re-created core primitives: 64-bit Word (four 16-bit limbs), bitwise/byte helpers, registers, bus Channel definitions, and the Sail wrappers.
  • Specs/ — consolidated input structs and semantic specs, with chip specs stated via the RV64 ISA functions.
  • Chips/ — the per-row circuits (one directory per chip): ALU (Add/Sub/Addw/Subw/Bitwise/Lt), shifts, Addi, Mul, DivRem, AluX0, U-type, control flow (Jal/Jalr/Branch), and the full load/store family — each composing readers + gadgets and (where present) its Sail bridge. Each has a Defs.lean with basic setup, a Formal.lean with the final circuit proof (as a GeneralFormalCircuit), and Bridge.lean then connects this further to monadic sail.
  • Operations/ — the witnessed operation gadgets (Add, Sub, Addw, Subw, Bitwise, Lt, Mul, compare/zero ops, address ops) with native arithmetic and semantic specs. Proven correct relative to their specs as FormalAssertions with populate functions that are called by the actual chips.
  • Readers/ — register/PC adapters that emit row interactions to the buses (CPUState, R/I/J-type readers, memory-access columns, timestamps).
  • Faithful/ — anchors proving SP1's extracted Rust constraint lists match the Lean gadget specs (asserts + bus interactions).
  • WitnessTests/ — the witness-generation conformance layer (generated vectors + native_decide anchor unit tests). Note this has no coverage for chips.
  • Soundness/ — the whole-machine layer: per-bus consistency, the chip registry, the gated execution capstone, and the instruction-coverage/routing table.

Scope

Soundness is the primary target and is axiom-clean (only propext, Classical.choice, Quot.sound). Completeness some chips still contain sorry, but this was not verified even partially in the old formulation. Some of the assumptions for completeness are also overly strong, and should eventually be relaxed to prove honest completeness.
The trace level multiplicity argument also still contains a sorry, but again this was never verified even partially before.

Docs

New docs/ (architecture, bus model, proof patterns, porting recipe, release audit, roadmap) describe the four-artifact chain and what's deferred.

Basic Example

Each chip is structured something similar to below, across a Specs/Chip.lean+Chips/<Chip>/Defs.lean+Chips/<Chip>/Formal.lean:

structure Extracted.MulCols (F : Type) where
  state : (CPUState F)
  adapter : (RTypeReader F)
  a : (Word F)
  mul_operation : (MulOperation F)
  is_mul : F
  is_mulh : F
  is_mulhu : F
  is_mulhsu : F
  is_mulw : F
deriving ProvableStruct

structure MulChip.Inputs (F : Type) where
  is_real : F
  state : Extracted.CPUState F
  adapter : Extracted.RTypeReader F
deriving ProvableStruct

@[reducible] def Inputs.op_b_val {F} (i : Inputs F) : Word F := i.adapter.op_b_memory.prev_value
@[reducible] def Inputs.op_c_val {F} (i : Inputs F) : Word F := i.adapter.op_c_memory.prev_value

def Spec (input : Inputs (ZMod p)) (cols : MulCols (ZMod p)) (_ : ProverData (ZMod p)) : Prop :=
  input.is_real = 1 →
    (cols.is_mul = 1 → Word.toBitVec64 cols.a = RV64.mul (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val)) ∧
    (cols.is_mulh = 1 → Word.toBitVec64 cols.a = RV64.mulh (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val)) ∧
    (cols.is_mulhu = 1 → Word.toBitVec64 cols.a = RV64.mulhu (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val)) ∧
    (cols.is_mulhsu = 1 → Word.toBitVec64 cols.a = RV64.mulhsu (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val)) ∧
    (cols.is_mulw = 1 → Word.toBitVec64 cols.a = RV64.mulw (Word.toBitVec64 input.op_c_val) (Word.toBitVec64 input.op_b_val))
∧... -- readers/cpu/is_binary etc. for chips w/ completeness proofs

def MulChip.main (input : Var Inputs (ZMod p)) : Circuit (ZMod p) (Var MulCols (ZMod p)) := do
  let cols ← ProvableType.witness (fun env =>
    MulOperation.populate
      #v[env input.op_b_val[0], env input.op_b_val[1], env input.op_b_val[2], env input.op_b_val[3]]
      #v[env input.op_c_val[0], env input.op_c_val[1], env input.op_c_val[2], env input.op_c_val[3]]
      (env is_mulh) (env is_mulhsu) (env is_mulw))
  assertion MulOperation.circuit
    ⟨input.op_b_val, input.op_c_val, cols, is_mul + is_mulh + is_mulhu + is_mulhsu + is_mulw,
      is_mul, is_mulh, is_mulhu, is_mulhsu, is_mulw⟩
  is_mul * (is_mul - 1) === 0
  is_mulh * (is_mulh - 1) === 0
  is_mulhu * (is_mulhu - 1) === 0
  is_mulhsu * (is_mulhsu - 1) === 0
  is_mulw * (is_mulw - 1) === 0
  (is_mul + is_mulh + is_mulhu + is_mulhsu + is_mulw)
    * ((is_mul + is_mulh + is_mulhu + is_mulhsu + is_mulw) - 1) === 0
  -- Ommitted --
  return ⟨input.state, input.adapter, a, cols, is_mul, is_mulh, is_mulhu, is_mulhsu, is_mulw⟩

instance MulChip.elaborated : ElaboratedCircuit (ZMod p) Inputs MulCols main where
  localLength _ := 54
  localLength_eq := by simp +arith [circuit_norm, main, MulOperation.circuit, Readers.CPUState.circuit, Readers.RTypeReader.circuit]
  subcircuitsConsistent := by simp only [circuit_norm, main, MulOperation.circuit, Readers.CPUState.circuit, Readers.RTypeReader.circuit]; try omega
  channelsWithGuarantees := [byteChannel.toRawGated]
  channelsWithRequirements :=
    [byteChannel.toRawGated, stateChannel.toRawGated, memoryChannel.toRaw, programChannel.toRaw]
  channelsLawful := by simp [circuit_norm, main, MulOperation.circuit, Readers.CPUState.circuit, Readers.RTypeReader.circuit]

-- Lots of lemmas and files in between these two

def MulChip.circuit : GeneralFormalCircuit (ZMod p) Inputs MulCols :=
  { MulChip.main, MulChip.elaborated,
    Assumptions := MulChip.Assumptions, Spec := MulChip.Spec,
    ProverAssumptions := MulChip.ProverAssumptions, ProverSpec := fun _ _ _ => True,
    soundness := MulChip.soundness, completeness := MulChip.completeness }

dtumad and others added 2 commits June 7, 2026 18:38
Wholesale replacement of the 4.29 olean-based sp1-lean with the
Clean-native, semantically-specified rewrite from sp1-lean-circuits
(Lean 4.28, public Clean DSL). Every prior file is removed and the
circuits tree takes its place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dtumad dtumad requested a review from tamirhemo June 8, 2026 03:24
dtumad and others added 6 commits June 8, 2026 01:26
Three fixes, plus the profiling infrastructure that found them and the
upstream proposals they motivate:

- v[i] index-bound decide fast-path (Foundations/GetElemFastPath.lean):
  literal bounds close in ~26 heartbeats instead of routing through Std's
  slice get_elem_tactic rule (~0.34s per `[i]` under a fat soundness
  context); the eight StoreByteChip eval bridges drop 175k -> 3k heartbeats.
- Reader localLength_eq simp-route: the explicit simp proof replaces the
  silent `rfl` ElaboratedCircuit default (3k vs 334k heartbeats on the two
  ALU readers), letting the maxHeartbeats/maxRecDepth bumps be dropped.
- `set_option linter.all false` on the 76 auto-generated modules (~1.6s of
  linter interpretation per file, ~34% of the sweep), emitted by all three
  update_extracted.py templates so regen produces no diff.

Profiling: scripts/profile_compile.sh gains EXCLUDE_RE; the 2026-06-10
baseline + post-fix sweeps (260 modules, 0 failures) are archived under
docs/snapshots/ with the findings table. Extracted/ 1013s -> 146s; medium
chip Formal files ~-70%; max module 462s -> 118s. New top offenders: the
DivRem/Shift conjunct proof bodies and DivRemChip.Defs.

Upstream: probe-validated Clean patch series (decide fast path +
simp-first localLength_eq default) under docs/upstream/, plus ready-to-file
lean4 issue text for the polymorphic-range get_elem rule. Also gitignore
__pycache__.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…10 skeleton, B1-Mul

Squash of the local roadmap work, merged onto the compile-performance sweep:

- W10: target-theorem skeleton (Target.sp1_target_execution), audit harness, doc/roadmap refresh
- W9: re-pin Clean to PR #398 head; sweep call sites to upstream pullIf/emit/toRaw and delete
  the compat layer; extraction reproducibility (TB-9)
- B1-Mul: MulOperation.spec_populate closes MulChip.completeness
- W1c: operand isU64 recovery from the memory-bus balance
- W5 slice: per-row clk_inc on the State bus (default 8)
- W1a: balance translation proven; capstone sorry confined to the W1b seam

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add trace gen tests for some basic chips

* cleanup

* functional completeness for the shift chips

* Restructure: FormalModel audit surface + Math/Model split + Extracted consolidation

Reorganize SP1Clean toward the five-pillar layout with a central audit surface
(namespace-preserving — no fully-qualified names change; only import paths + tooling globs):

- Foundations/ -> Math/ (general math, no Sail deps) + Model/ (SP1 Sail/bus substrate)
- auto-gen consolidated under Extracted/: Circuit/<Op>.lean (operation circuit forms,
  was Operations/<Op>/Extracted.lean) + WitnessVectors/<Op>.lean (conformance vectors,
  was WitnessTests/<Op>WitnessVectors.lean); update_extracted.py output paths updated
- Specs/ -> FormalModel/Contracts/{Readers,Operations,Chips}.lean; lifted the ALU chips'
  (Add/Addi/Addw/Sub/Subw) Assumptions/ProverAssumptions into Contracts/ChipAssumptions.lean
  so the full per-chip contract surface (Spec + Assumptions + ProverAssumptions) lives there
- lakefile.toml: added per-pillar lean_lib targets SP1Math/SP1Model/SP1Extracted/SP1FormalModel
- AGENTS.md: Architecture/Build/restructure-status rewritten for the new layout

Build green (3633 jobs); audit clean (314 probes, sorryAx confined to the known 4-sorry debt).
Deferred (per plan): Native/+Proofs/ rebucket, trace statements -> FormalModel/Trace, W11 GatedVm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Restructure: Native/+Proofs/ five-pillar rebucket + per-pillar lean_libs

Phase 6/7 of the restructure (namespace-preserving — no FQNs change):
- Chips/<Op>Chip/Defs.lean -> Native/Chips/<Op>Chip/Defs.lean (22 self-contained chips);
  Formal/Bridge/complex-chip files -> Proofs/Chips/<Op>Chip/. The 3 entangled complex chips
  (DivRem/ShiftLeft/ShiftRight) stay whole in Proofs/Chips/ (their Defs import sibling proof files).
- Operations/<Op>/{Populate,RawSpec} -> Native/Operations/; Formal -> Proofs/Operations/; flat ops -> Native.
- Readers/ -> Native/Readers/ (reader circuits; Specs already in FormalModel/Contracts/Readers).
- WitnessTests/<Op>Witness anchors -> Proofs/WitnessTests/.
- top-level Trace.lean -> Soundness/RowView.lean.
- lakefile.toml: added SP1Native + SP1Proofs lean_lib targets (SP1Proofs = Proofs+Faithful+Soundness).
- tooling: gen_axiom_probe.py globs, run_audit.sh expected_sorries, update_extracted.py sub-op import
  retargeted to the new Proofs/ paths.
- AGENTS.md: Architecture rewritten for the five physical pillars + the six layer libraries.

The five pillars are now physical dirs: Extracted (rust) / Native (impl) / Faithful / Proofs / Soundness,
plus Math + Model + FormalModel. Build green (3633 jobs); audit unchanged (314 probes, known 4-sorry debt).
Remaining: surface trace statements in FormalModel/Trace (Phase 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Restructure (Phase 5): surface guest-program model in FormalModel/Trace

Extract the SailState-level guest-program execution model (GuestProgram, fetchWord, RomLoaded,
SailConfigured, IsInitialState, SailStep, SailChain[.snoc], ECALL_ENC, HALT_SYSCALL, exitOf, SP1Halted)
from Soundness/TargetVm.lean into FormalModel/Trace/GuestProgram.lean — the "guest programs" 5th-pillar
item now lives on the audit surface (it depends only on the Model/ Sail layer). Namespace kept
SP1Clean.Soundness.Target so TargetVm resolves them unchanged via the new import.

Layering finding (documented in AGENTS.md): the trace *arguments* that consume the model
(TargetObligations, WalkOf, RefinesAt, RowEffect, the sp1_target_execution theorem, the Opcode->chip
routing in Coverage, the Emits relation) reference ChipRow / StateAccess — Soundness-layer types — so
they cannot move below FormalModel without inverting the dependency graph; they stay in Soundness/.

Build green (3634 jobs); audit PASS (314 probes unchanged, sorryAx confined to the known 4-sorry debt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Restructure (docs): update docs/ + README for the five-pillar layout

Rewrite the prose/path references across the documentation to match the reorganized tree
(namespace-preserving move; FQNs unchanged, so declaration references were already correct):

- README.md: per-directory structure section rewritten — Foundations -> Math/ + Model/, the spec
  layer -> FormalModel/Contracts/{Readers,Operations,Chips} (+ ChipAssumptions, Trace/GuestProgram),
  Operations/Chips -> Native/+Proofs/ split, WitnessTests anchors -> Proofs/ (vectors -> Extracted/).
- docs/architecture.md: layout tree rewritten to the five pillars + the per-pillar lean_libs and the
  namespace-decoupling note; spec-layer/four-artifact-chain paths retargeted.
- docs/{bus-model,roadmap,release-audit}.md and docs/agents/{proof-patterns,porting-recipe,extraction,
  lean-sail-notes,mul-operation-learnings,README}.md: Foundations/X -> Math|Model/X, Specs/X ->
  FormalModel/Contracts/X, Chips/<Op>/{Defs->Native, Formal/Bridge/...->Proofs}, Operations/<Op>
  split, Readers -> Native/Readers, Trace.lean -> Soundness/RowView.lean.

Left intentionally: docs/snapshots/* (dated historical artifacts) and the sibling-repo reference
`../sp1-lean`'s `SP1Foundations/` (a different repo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W3 (decode half): Decode.lean — decode component of OperandsBound

Land the decode half of the target theorem's OperandsBound
(Soundness/Decode.lean): each real row's committed Program-bus operand
columns are the decode of the instruction word at its pc, built on the
official LeanRV64D decoder (ext_decode) so fetch-decode coherence with
try_step holds by construction.

- instrToProgramRow: LeanRV64D instruction -> committed Program-bus
  ProgramRow, all opcode families (R/I/U/J/B/load/store/shift/word/M).
- DecodeOperandsBound / decodedInROM: the decode conjunct of
  OperandsBound + the "committed table = decode of guest ROM" membership.
- decode_bound / decode_targetBound: the decode half of
  TargetObligations.bound, from a threaded TraceProgramValid link.
- targetObligations_of_decode: assemble TargetObligations at
  OperandsBound := DecodeOperandsBound prog (lift/halt remain W7/W5 seams).

Wire the module into SP1Clean.lean and add the seven decode probes to the
axiom census (gen_axiom_probe.py + axiom_probe.lean). Build green (3635
jobs); the new decls carry no sorryAx (only the Sail model's ext_decode
axioms).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W3 (deliverable B): discharge the decode bound from Program-bus balance

Remove the threaded h_link from the decode half of TargetObligations.bound,
reducing it to (a) per-instruction decodedInROM facts + (b) the lone
LogUp/GKR balance fact — the ProgramProviderSpike pattern, now at the decode
predicate.

- ProgramProviderSpike.lean: generalize programProvider_of_validRom to an
  arbitrary predicate P (programProvider_of_valid); the ProgramRowSpec case
  is now a one-line instance. programConsistent_of_balance was already
  generic over inROM, so this is the only generalization the discharge needs.
- Decode.lean: decode_bound_of_balance / decode_targetBound_of_balance —
  the decode half of bound from a decoded ROM (∀ row ∈ rom, decodedInROM
  prog row) + the balanced Program bus, no threaded link. Composes
  programProvider_of_valid (P := decodedInROM prog) + programConsistent_of_balance
  + decode_bound. Add open SP1Clean.LookupAccessList (isConsistentBalanced
  aggregateChipRows) + a local NeZero p instance.
- axiom_probe.lean: regenerated (323 probes; +2 for the new theorems).

Build green (3635 jobs); both new theorems axiom-clean (Sail decoder axioms
only, no sorryAx). Residual for W3: deliverable A (per-instruction
decodedInROM via the ext_decode reduction, shared with W7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W3/W7 decode foundation: reduce the real Sail decoder (Model/SailDecode.lean)

Land the decode-reduction technique for the official noncomputable Sail
decoder ext_decode = encdec_backwards (a ~280-branch match/if cascade) —
the shared foundation for the W3 decodedInROM discharge and the W7 step-lift
decode stage.

- run_bind_ok_none / run_bind_ok_some: peel one cascade branch each, in
  .run s form so rw/refine apply them without descending into untaken
  branches (axiom-clean: [propext, Quot.sound]).
- decode_ADD_example: proves (ext_decode 0x003100B3).run s = .ok (RTYPE
  (x3,x2,x1,ADD)) s against the real decoder, via a clean-stop branch-skip
  walk: `repeat (refine (run_bind_ok_none _ _ _ ?_).trans ?_; · <discharger>;
  dsimp)`. Exposing the "guard runs to none" goal as a real `?_` (not an
  embedded `by`, which Lean error-recovery would sorry, walking past the
  matching arm) makes a failed discharge throw, so repeat stops exactly at
  the ADD arm; run_bind_ok_some then takes it.

Findings baked in: brute simp [encdec_backwards] explodes (bottom-up under
monadic binders); the per-branch discharger stays small (no explosion);
SailConfigured residue needed = isInitialized + cur_privilege = Machine (the
Zicfilp/forward-CFI branch reads privilege-dependent CSRs). decode_ADD_example
carries only the Sail model's sys_enable_experimental_extensions axiom — no
sorryAx. Build green (3636 jobs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* SailDecode: document the concrete-vs-symbolic scope note

Record why the decode reduction is only ever applied to concrete instruction
words (the W6b non-vacuity witness has a concrete ROM; RomLoaded ties the
fetched word to a concrete prog.rom entry), so the decode_ADD_example recipe
suffices for W3-A and W7; a symbolic-register ext_decode_RTYPE would need
bv_decide per cascade branch and is deliberately not pursued. Comment-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W6b/W7: strengthen SailConfigured to machine mode (cur_privilege = Machine)

The decode reduction (Model/SailDecode.lean) needs the platform residue
cur_privilege = Machine to resolve the Zicfilp/forward-CFI decode branch's
privilege-dependent CSR reads. Change SailConfigured from True to
s.regs.get? Register.cur_privilege = some Privilege.Machine; all consumers
(RefinesAt.cfg / RowEffect.cfg / IsInitialState.configured / DecodeOperandsBound)
are seams or hypotheses, so the full build stays green (3636 jobs). Strengthened
incrementally as W7 discovers further pins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W6b: non-vacuity witness — IsInitialState is satisfiable (axiom-clean)

The target theorem is ∀ s0, IsInitialState prog s0 → …; this exhibits a
concrete configured initial state + guest program it loads, so the hypothesis
is not vacuous.

FormalModel/Trace/Witness.lean:
- Fintype Register (derived; maxRecDepth 100000), regDefault (every
  RegisterType is Inhabited), fullRegs (fold insert over Finset.univ) +
  mem_fullRegs: every one of the ~196 Register constructors is a key.
- configuredState pc: a state with all registers present (cfgState_init =>
  isInitialized), PC pinned (cfgState_pc), cur_privilege = Machine
  (cfgState_priv, the strengthened SailConfigured residue) — reusable for any
  program.
- emptyProgram + isInitialState_nonvacuous : ∃ s0, IsInitialState emptyProgram
  s0. Axiom-clean ([propext, Classical.choice, Quot.sound]); no sorryAx, no
  native_decide.

Enriching to a non-empty ROM (real instruction bytes in mem so romLoaded
carries content) reuses configuredState. Build green (3637 jobs); Witness.lean
compiles in ~4s (the Fintype derive is cheap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W3-A/W7: fold isInitialized into SailConfigured

The decode reduction (decode_ADD_example) needs BOTH isInitialized (the
Zicfilp/CFI branch reads privilege CSRs, which must be present) and
cur_privilege = Machine. decodedInROM / DecodeOperandsBound only carry
SailConfigured, so SailConfigured must imply both for them to discharge the
decode. Change SailConfigured to s.isInitialized ∧ cur_privilege = Machine
(redundant with IsInitialState.initialized / RefinesAt.init, documented).
Witness's configured field now provides both conjuncts. All other consumers
are seams; build green (3637 jobs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W3-A: close decodedInROM end-to-end via the real Sail decoder

Compose the decode foundation into the W3 obligation shape: a one-instruction
guest program (ADD x1,x2,x3 at pc 0), its committed Program-bus row, and a
proof that decodedInROM holds — SailDecode.decode_ADD_example (the ext_decode
reduction against the official noncomputable decoder) + instrToProgramRow_rtype
(projection to committed columns). decodedInROM's SailConfigured precondition
(now isInitialized ∧ machine mode) supplies exactly what the decode reduction
consumes.

This is the per-concrete-word shape decode_bound_of_balance consumes
(∀ row ∈ rom, decodedInROM prog row), so for a fixed program each row is
dischargeable this way — closing the W3 decode chain end-to-end for a concrete
instruction. decodedInROM_addRow is axiom-clean modulo the Sail decoder's own
axiom (sys_enable_experimental_extensions); no sorryAx. Build green (3637 jobs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W4a: construct the MemoryGlobalInit provider (discharge MemProviderGenesis)

The Memory-bus analog of ProgramProviderSpike. isConsistentOnline_of_memBalance
derives the offline-memory link (every read returns the most-recent write at its
address) from balance but threads MemProviderGenesis — the off-chip
MemoryGlobalInit chip's genesis side, modeled as a predicate. This removes that
hypothesis by constructing the init chip's native contributions.

Soundness/MemoryGlobal.lean:
- memGenesisAccess / memGenesisContributions: SP1's MemoryGlobalInit content —
  one bus entry per address at key [clk_high, t0, addr, 0,0, 0,0,0,0] (initial
  value 0 at the genesis timestamp t0).
- memProviderGenesis_of_contributions: MemProviderGenesis holds for the
  constructed contributions — a real event matching a genesis key reads value 0
  (value limbs are 0) at prevTs = t0; the only residual is t0 distinct from every
  real clock (h_t0 = "init timestamp below all accesses").
- traceMemoryValid_of_genesis_and_balance: TraceMemoryValid (the single-shard
  Memory link) from the constructed provider + ordering side conditions + balance,
  no standalone MemProviderGenesis assumption — the twin of
  traceProgramLink_of_validRom_and_balance.

Both axiom-clean ([propext, Classical.choice, Quot.sound]). Build green (3638
jobs). Remaining W4a: bind the genesis value / final boundary to a concrete
prog.memImage (the per-address value the bus pins) — W2's replay precision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W2/W1c: operand-recovery hypotheses ride the constructed genesis provider

memBalanceHyps_of_genesis (Soundness/MemoryIsU64.lean) assembles MemBalanceHyps
at the W4a memGenesisContributions, discharging the genesis side via
memProviderGenesis_of_contributions. So every operand-binding fact
(operand_{a,b,c}_isU64_of_memBalance, eventsAt_values_isU64, the limb value
chain) now holds with NO standalone MemProviderGenesis assumption — only the
structural ordering/range side conditions + the t0-below-all residue + the
balanced Memory bus. The value twin of traceMemoryValid_of_genesis_and_balance;
the operand-binding entry point W2's value-half-of-bound builds on.

Axiom-clean. Build green (3638 jobs). W2's remaining core (Sail-state value
binding via RefinesAt/RowEffect exact-replay) is intertwined with W7 and the
proved capstone; a coordinated effort, not an inline change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W2+W7 keystone: strengthen RefinesAt to exact replay + RowEffect to strict write

The coordinated invariant surgery on the proved capstone, the prerequisite for
W2's operand-value binding (and the shape W7's lift must produce).

Soundness/TargetVm.lean:
- replayVal s0 path idx i: the exact value register idx holds after replaying
  the first i walk rows — the committed rdWrite of the most-recent earlier op_a
  write to idx, else s0's value.
- RefinesAt.frame: strengthened from a frame-disjunction (idx is initial OR was
  written by some earlier op_a) to EXACT replay (s.get_reg? idx = replayVal …).
- RowEffect.regs: strengthened from a per-idx disjunction to the strict write
  form — s' = some rdWrite at op_a, s' = s elsewhere (what W7's wX_bits rd
  produces; the disjunction previously permitted "unchanged" even at op_a).
- chain_to_refines re-proved: the base frame is rfl (replayVal … 0 = s0 value);
  the inductive step threads replayVal through the strict RowEffect.regs.

sp1_target_execution re-proved with the stronger invariant; no new axioms
(verified: the committed version already carried the Sail model's softfloat/
reservation axioms via try_step; no sorryAx). Decode.lean's
targetObligations_of_decode references RefinesAt/RowEffect only in signatures, so
it threads the stronger shapes unchanged. Build green (3638 jobs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(roadmap): record the 2026-06-17 critical-path sweep

Update docs/roadmap.md to reflect the landed work: a progress snapshot banner;
W3 closed end-to-end on the concrete-program path (real Sail decoder reduced in
Model/SailDecode.lean, decode bound from Program-bus balance, decodedInROM for a
concrete instruction); W6b non-vacuity witness (FormalModel/Trace/Witness.lean);
W4a MemoryGlobalInit provider constructed (Soundness/MemoryGlobal.lean); the
W2+W7 exact-replay keystone (RefinesAt→exact replay, RowEffect→strict write,
chain_to_refines re-proved); SailConfigured = isInitialized ∧ machine mode; and
W7's decode stage + RowEffect target shape now in place. Critical-path note and
W2/W3/W4a/W6b/W7 sections updated; remaining front = W2 value-half ↔ W5 ↔ W7
try_step reduction. Docs-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W2 value-half: ValueOperandsBound + the concrete decode∧value bound assembled

Soundness/ValueBound.lean — the value analog of the decode story, riding the
exact-replay invariant the W2+W7 keystone installed:
- ValueOperandsBound r s: the live Sail register values equal the row's committed
  read-value columns (op_b/op_c prev_value), per register source operand. This is
  what W7's try_step reduction consumes (interpreter rs1/rs2 reads = chip columns).
- value_targetBound: the value half of bound — compose RefinesAt.frame
  (s.get_reg? idx = replayVal …) with the cross-bus link (replayVal = committed
  prev_value). Axiom-clean ([propext, choice, Quot.sound]).
- TraceValueBinding: the one named cross-bus residual (committed value = exact
  replay value), to be discharged from the Memory-bus value chain
  (memEvent_prevValue_eq_writer) + the walk-order = clk-order bridge.
- OperandsBound_full = DecodeOperandsBound ∧ ValueOperandsBound;
  operandsBound_full_targetBound discharges the full bound (both halves);
  targetObligations_full assembles the full TargetObligations at the concrete
  OperandsBound (bound discharged, lift/halt the W7/W5 seams) — the Phase-7 glue
  entry point.

Build green (3639 jobs); no sorryAx (the combined decls carry the Sail model's
decoder/try_step axioms only). Remaining W2: discharge TraceValueBinding (the
cross-bus binding) + W2b (load/store addresses, RowEffect.rom store-replay).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(roadmap): W2 value-half assembled (ValueBound.lean)

Record ValueOperandsBound + the concrete OperandsBound = decode ∧ value +
targetObligations_full; remaining W2 = discharge the cross-bus residual
TraceValueBinding. Docs-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* W2: the walk-order = clk-order bridge (walk_clk_monotone)

The foundational fact the TraceValueBinding discharge rides: the WalkOf trail is
clk-monotonic, so replayVal's walk-position order equals the Memory-bus value
chain's clk order.

- TargetVm.lean: expose isWalk_chain (was private) — the generic IsWalk step
  lemma.
- ValueBound.lean: rcvClkOf/sndClkOf (the clk a state-bus key carries);
  sndClk_eq_rcvClk (a send/receive handoff advances clk by clk_inc — the clk
  twin of sndPc_eq_rcvPc); walk_clk_monotone (consecutive WalkOf rows:
  sndClkOf path[i] = rcvClkOf path[i+1], i.e. the walk visits rows in increasing
  clock order). Both axiom-clean.

Remaining TraceValueBinding discharge composes this with: the memory event
timestamps = row clocks (rowClkLow); the Memory-bus value chain
(memEvent_prevValue_eq_writer: read = most-recent write, read-backs preserving);
and the genesis alignment (s0's initial registers = 0 = the init chip's genesis).
Build green (3639 jobs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(roadmap): walk-clk bridge (walk_clk_monotone) landed

Record the walk-order = clk-order bridge foundation; remaining TraceValueBinding
discharge = the induction relating replayVal to the memory event chain. Docs-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* DivRemChip: close the completeness proof (axiom-clean)

Completes `DivRemChip.completeness` (`Completeness/Driver.lean`'s `completeness_driver`,
embedded in `circuit`), the last DivRem hole. `#print axioms` on both is now
`[propext, Classical.choice, Quot.sound]` — no `sorryAx`. Full `lake build SP1Clean` green.

The blocker was the 13 nested IsEqualWord/IsZeroWord sub-op `cols` pins: `circuit_proof_start`
decomposed `eval (fromElements w)` into an intractable nested record (every defeq/re-fold
closer whnf-timed-out). Fix: selective non-decomposition — bump `ProvableType.eval_fromElements`
to top `circuit_norm` priority (`attribute [local circuit_norm ↓ 100000] …`) so those cols stay
flat before `eval_eq_eval` can explode them. Only `main`'s explicit-`fromElements` sub-op cols
match, so Mul/Add/Lt/U16MSB are untouched; no struct flattening, no faithfulness impact. Each
pin closes by `simp only [toElements_fromElements, getElem_map, getElem_mapRange, circuit_norm]`.
With the cols now cheap, the driver's `maxHeartbeats` drops 256M → 64M.

Also bundles the supporting DivRem completeness WIP that this builds on: the `Completeness/`
helpers (OwnComplete/BytePulls/SubSpecs), the honest `Populate`/`Populate/*` witness layer, the
per-variant `Soundness/*` split, the sub-op support lemmas in `Operations/*`, and the DivRem
trace-gen tests. Docs: new "keeping nested sub-op cols folded" recipe in
`docs/agents/proof-patterns.md`; roadmap item 3 marked closed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: update stale layout paths after the pillar refactor

Audit of all markdown after the Math/Model/Extracted/FormalModel/Native/Proofs
refactor. Fixed current-tense path references the refactor branch left pointing at
the old flat layout (Foundations/, Operations/<Op>.lean, Chips/<Op>Chip/, etc.):

- AGENTS.md + README.md + docs/architecture.md: the four-artifact chain, the
  'one main/one Spec' principle, the audit-surface description, the single-file
  build example, and the Channels path now point at Native/Operations,
  Proofs/Operations, Native/Chips, Proofs/Chips, FormalModel/Contracts, Math/Model.
- docs/agents/porting-recipe.md: the three step headers (gadget / chip / bridge).
- docs/agents/extraction.md: the migration-shape paths (Extracted/Circuit,
  Native/Operations/Populate, Proofs/Operations/Formal, Native/Chips/Defs).
- docs/agents/proof-patterns.md + lean-sail-notes.md: gadget + bridge path refs.

Left as-is (intentionally): namespace references (decoupled from paths), the
SP1Foundations/SP1Native/SP1Chips sp1-lean reference-repo lib names, tree-diagram
sub-paths under the Native/Proofs node labels, InstructionTrace.lean, and the dated
snapshot/closed-audit-log entries (historical record). Also removed the leftover
empty SP1Clean/TraceGenTests/ directory on disk (untracked by git).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dtumad dtumad merged commit 7d7ad67 into main Jun 19, 2026
1 check passed
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