feat: Clean DSL + Simplified per-chip RISCV spec + Generic field + Trace Theorms + Partial Honest Witness Gen#97
Merged
Merged
Conversation
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>
…rather than sub-circuits
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>
…ccinctlabs/sp1-lean into full-clean-dsl-implementation
tamirhemo
approved these changes
Jun 16, 2026
* 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>
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.
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
toPropmapping. 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/SP1Chipslibraries are removed and replaced by a newSP1Clean/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 isx0(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) withnative_decideanchors, 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 fromupdate_constraints.pytoupdate_extracted.py.SP1Clean/layoutExtracted/— constraint data generated from the SP1 Rust sourceFoundations/— re-created core primitives: 64-bitWord(four 16-bit limbs), bitwise/byte helpers, registers, busChanneldefinitions, 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 aDefs.leanwith basic setup, aFormal.leanwith the final circuit proof (as aGeneralFormalCircuit), andBridge.leanthen 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 asFormalAssertions withpopulatefunctions 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_decideanchor 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 containsorry, 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: