THE SEAM. This repository is checked by two scripts, and until now neither
asserted anything about the other's scope. check.sh's dead-file gate simply
SKIPPED anything named Scalar*, so a new Proofs/ScalarX.lean was gated by
nothing at all: absent from one manifest by exemption, from the other by
omission, compiled by neither, inventoried by neither. Each button now reads
the other's manifest and requires every shipped proof source to belong to
EXACTLY ONE of them — neither orphaned nor double-claimed, both directions,
plus a phantom check on entries naming files that do not exist. Negative-tested
four ways, including the exact hole this item names.
THE SCALAR BUTTON. Closing the seam exposed it as the estate's weakest link,
having been left behind by every hardening round while the main button gained
five phases. 45 lines to 227:
- source-integrity check over its sources;
- harness-pin verification, so running THIS button alone is protected and not
only running it after check.sh;
- a kernel-side axiom-declaration gate over the compiled artifacts, replacing
a source-text grep that is evadable four ways on v4.30.0-rc2;
- a declaration inventory of ~1880 constants against its own allowlist,
diffed both directions with a count trailer. These 13 modules were the only
part of the proof corpus with no inventory: check.sh Phase 2c named them as
uncovered on every run, and now names the button that covers them instead;
- per-certificate exact-cone assertions replacing `-eq 13` over matching
output lines. A count cannot say WHICH certificate is clean and passes just
as happily if one cone is reported twice.
Every fork-specific fact was read from the existing script rather than assumed:
risc0 and betrusted audit sub_loop1_one_spec where dalek and anza audit
cond_add_l_one_spec, untouched.
THREE BUGS, ONE ROOT CAUSE, all found by the gates rather than by review. Each
reasoned about how a thing is SPELLED instead of what it BELONGS TO, and the
corpus punished each: Proofs/ScalarPackSpec.lean is named like the scalar layer
and owned by the main button.
- the scalar dead-file gate globbed Scalar* and demanded ScalarPackSpec be
scalar-owned. REMOVED rather than special-cased: the seam check tests
membership in exactly one manifest, which is strictly stronger than any
prefix;
- the scalar axiom gate scanned Scalar*.olean, reporting "14 modules" for a
13-module manifest. On a tree where check.sh had not run that artifact is
absent and the button would have failed for a false reason. It now scans
the manifest by membership and fails closed on a missing artifact;
- Phase 2c's driver discovery globbed Inventory*.lean and claimed the other
button's driver, then correctly complained its own manifest lacked those
modules.
This is the family the campaign began with: a source-text axiom grep reasoning
about spelling. Recorded in TRUSTED-BASE.md because it generalises.
Also fixed: the first negative test of the scalar gate's absence check passed
for the wrong reason — the button recompiles before the gate runs, so removing
an artifact merely caused it to be rebuilt. Retested against the lifted phase,
where absence is a persistent condition.
Verified green: 24 runs across the four repositories — four main buttons, four
scalar buttons, and sixteen self-tests — zero red.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README: the pyramid diagram claimed the cofactored ZIP-215 equation,
which is NOT the proven statement - corrected to the actual theorem
(accepted IFF compress([s]B-[k]A) = R, byte-for-byte) and the signature
row now names verify_accepts_iff; new "The signature apex (phase 1)"
section states the theorem, this repo's glue architecture, the exact
button-enforced axiom cone, and the phase-2 deferral.
- TRUSTED-BASE: item 5 rewritten from an aspirational hash paragraph to
the structural boundary - certificate name, exact allowed cone, and the
Phase 3b enforcement that fails the build on any deviation.
- Dead pre-merge artifacts removed: gen/CurveScalar, CurveScalar.llbc,
extract-scalar.sh (the merged gen/CurveField universe is the single
model; check-scalar.sh remains the scalar button, header updated).
- lean-guard: Guard 3a retry ladder (LEAN_MEM_WAIT_SEC) - a clamped run
that dies on memory retries as headroom improves, converting ambient
memory pressure from a deterministic abort into a delayed pass.
Fresh green buttons after these changes: check.sh (incl. Phase 3b apex
audit) + check-scalar.sh, both at shipped defaults, coherence pass 3
sweep 2026-07-05.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gen merge: extract.sh now co-extracts the Scalar52 backend and the public
scalar::from_bytes_mod_order[_wide] conversions into the SAME CurveField
model, so the whole library — field, curve_models, edwards, scalar — shares
one type universe (Scalar is a single structure, not two). The scalar proof
chain repoints by one import line (ScalarDenote: CurveScalar.Funs ->
CurveField.Funs); check-scalar.sh's gen list follows. Both buttons — the
scalar certificates and the field/group/dsm certificates — pass fresh over
the merged gen, so the merge is proven-safe, not merely hoped-safe.
Verify glue (gen/CurveSig): the extracted ed25519-dalek verify_sha512 path,
integrated against the proven model:
- TypesExternal.lean imports CurveField.Types, so CompressedEdwardsY /
EdwardsPoint / Scalar in the glue ARE the proven model's types. Only the
genuinely foreign types stay opaque: sha2.Sha512, ed25519.Signature,
signature.error.Error.
- FunsExternal.lean imports CurveField.Funs, so every curve/scalar call
(compress, vartime_double_scalar_mul_basepoint, as_bytes, neg,
from_bytes_mod_order[_wide]) resolves to a proven definition — no axioms.
The `?`-operator plumbing (Try::branch, FromResidual::from_residual) and
compressed_from_bytes get real definitions. Only the SHA-512 hasher
(sha512_new/update/finalize_bytes) and two opaque wire accessors
(Signature.to_bytes, Error.new) remain axiomatized — the deliberate,
documented hash-oracle boundary.
Audited: `verify_sha512`'s entire axiom cone is
[propext, Classical.choice, Quot.sound,
sha2.Sha512, sha512_new, sha512_update, sha512_finalize_bytes,
ed25519.Signature.to_bytes, signature.error.Error.new]
— zero curve axioms, zero scalar axioms. The verify path is definitionally
grounded in the certified model; the only trust boundary is SHA-512.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The apex brick of the scalar layer: for any 64 bytes (the opaque SHA-512
digest), [from_bytes_wide bytes] = (LE 512-bit value) mod l, with
canonical 52-bit-bounded output. Composition: bytes_unpack_spec (8x8
loops) -> split_words_lo/hi_spec (exact div/mod per limb, disjoint ORs
as additions) -> wide_split_telescope (isolated omega) -> montgomery_mul
by R and RR (R cancels as a unit, RR restores it) -> the canonical add.
The two kernel-capacity walls found and crossed en route (control repo
FAILURES.md updated):
- a montgomery_mul inside any walk motive replays its 400-line body at
every kernel step (fix: named prefix functions in the pinned source);
- straight-line IndexMut closure chains make kernel defeq exponential in
depth (fix: struct-literal construction - the split halves now build
Scalar52([...]) directly). Full certificate: 77 s kernel-inclusive.
Regenerated gen (sources factor from_bytes_wide -> from_bytes_wide_parts
-> split_words_lo/hi; documented pure refactors, cargo-checked).
check-scalar.sh: 13 proof files, 13 kernel audits, all exactly
[propext, Classical.choice, Quot.sound]. Button pressed fresh: green.
Toward Scalar::from_hash: bytes_unpack_spec proves the from_bytes_wide
word-unpack loops pack 64 little-endian bytes into 8 words exactly.
- Proofs/ScalarBytesSpec.lean (3308 lines): bytes_word_loop_spec_0..7,
each split head/tail at j=4 (the 8-fold monolith grows exponentially
in elaboration - METHOD 4). Disjoint-bit ORs become additions via
core's Nat.two_pow_add_eq_or_of_lt with explicit calc bridges (the
default simp set literalizes 2^8 -> 256 and breaks pow-form rewrites;
simp only everywhere).
- Proofs/ScalarUnpackSpec.lean: bytes_unpack_spec composes the eight
inner lemmas through the outer loop (iterator start needs a term-level
equality rewrite per peel).
The from_bytes_wide main walk itself is proven at elaboration level
(fail-probe verified end to end) but its single-decl kernel certificate
replays >30min; it ships next as a phase-split (plan in the control
repo's method notes). check-scalar.sh: 12 proof files, 12 kernel audits,
all exactly [propext, Classical.choice, Quot.sound]. Button green.
Canonicity pass (the layer is now closed under its own preconditions):
- sub_val_spec post carries the exact value equation
(exists beta <= 1, scVal r + scVal b = scVal a + ell*beta, with the
underflow guard beta = 1 -> scVal a < scVal b)
- add/montgomery_reduce/mul/aggregate posts all carry scVal r < ell:
canonical inputs give canonical outputs everywhere. Needed because
from_bytes_wide (hash-to-scalar) feeds Montgomery outputs into add.
Hash-to-scalar foundation (toward Scalar::from_hash / EdDSA verify):
- extraction scope + from_bytes_wide (brings constants::R); regenerated gen
- source repos carry a documented Aeneas-compat patch: the bare
`hi[4] = words[7] >> 20` extracts ill-typed at pin bf13c42e; masked
(semantic no-op, words[7] >> 20 < 2^44)
- Proofs/ScalarWideSpec.lean: R constant lemmas (R = 2^260 mod ell,
witness 2^260 = R + 255*ell) and montgomery_mul_spec, the single
Montgomery round: [r]*2^260 = [a]*[b], canonical bounded output
check-scalar.sh: 10 proof files, 11 kernel audits, all exactly
[propext, Classical.choice, Quot.sound]. Button pressed fresh: green.
Phase B - montgomery_reduce (Proofs/ScalarMontSpec.lean + Proofs/ScalarReduceSpec.lean):
- mont_key: LFACTOR*L0 + 1 = 214835089243030*2^52 (the -1 inverse identity, norm_num)
- mont_cancel: (s + ((s*LFACTOR) % 2^52)*L0) % 2^52 = 0 via Nat.ModEq - every
part1 shift is an EXACT division, nothing discarded
- part1_spec / part2_spec: per-round helpers (carry*2^52 = sum + p*L0; exact split)
- mont_head_telescope (E0-E4) and mont_tail_telescope (E5-E8): linear_combination
certificates with weights 2^52k; mont_bound: X' < 2*ell from Z < 2^260*ell
- montgomery_reduce_spec: post `scDenote r * 2^260 = Z` in ZMod ell + 52-bit bounds.
METHOD-4 split at the round-4/5 boundary: the 74-step monolith is
elaboration-pathological; each half compiles in ~25s/3GB.
- The single death-spiral line: an omega for the nonce-sum bound with ~110
hypotheses in context never returns; extracted to nonce_sum_bound (5 hypotheses,
instant). Bisected with fail-probes; documented in control-repo FAILURES.md.
Phase C - mul (Proofs/ScalarFullMulSpec.lean):
- RR_limbs/RR_scVal/RR_lt; RR_denote: RR = 2^520 - K*ell kernel-checked, so
⟦RR⟧ = R^2; R_isUnit: 2^260 unit of ZMod ell (coprime oddness witness)
- mul_spec: mul_internal -> montgomery_reduce -> mul_internal(*, RR) ->
montgomery_reduce composed; column values folded by `ring`; R cancelled via
IsUnit.mul_right_cancel. Hypothesis scVal a * scVal b < 2^260*ell (honest
Montgomery bound; canonical inputs satisfy it).
Aggregate (Proofs/ScalarMain.lean): scalar_add/sub/mul_correct on ScBnd
interfaces + canonical_mul_bound + scalarImplementation bundling all three.
sub_val_spec/add_val_spec posts strengthened with result-limb 52-bit bounds
(the montgomery tail feeds sub's output back into mul_internal).
check-scalar.sh: 9 proof files, 10 kernel audits (was 6), all exactly
[propext, Classical.choice, Quot.sound]. Button pressed fresh: green.
Proofs/ScalarMulSpec.lean (axiom-clean, no sorry):
- m_spec: the widening 52x52->104-bit product helper — total, exact
- mul_internal_spec: all NINE schoolbook column sums proven exact
(z_k = sum_{i+j=k} a_i*b_j) and bounded (each product < 2^104, each
column < 2^107) through the 60-step straight-line extraction
This is the half of Scalar52::mul that the kernel-capacity frontier does
NOT touch. Phase B — montgomery_reduce (74 steps, part1/part2, the
Montgomery invariant result = input * R^{-1} mod l with R = 2^260, and
the double-round composition through RR) — remains the open frontier,
now precisely one function wide.
check-scalar.sh: ScalarMulSpec in manifest + audit (6/6 clean), green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Proofs/ScalarAddSpec.lean, axiom-clean, no sorry:
- add_loop_spec: the 5-limb carry loop unrolled (same skeleton as the
proven conditional-add-L chain, with b's limbs in place of L's
constants); per-limb equations r_i + 2^52*g_(i+1) = a_i + b_i + g_i.
- add_val_spec: denote(add a b) = denote a + denote b in ZMod l for
limb-bounded canonical inputs. Composition: add_telescope lifts the
carry equations to scLimbs sum + 2^260*g5 = scVal a + scVal b;
canonicity (a,b < l < 2^253) forces g5 = 0; the trailing sub(sum, L)
goes through sub_val_spec with subtrahend L — enabled by weakening
sub_val_spec's hypothesis from scVal b < l to scVal b <= l (the
gamma5=1 forcing argument only needs <=), since scVal L = l exactly.
denote L = 0 in ZMod l closes it.
check-scalar.sh: ScalarAddSpec in manifest + audit (5/5 clean), button
green at the re-budgeted 300s/4096MB caps.
With sub (previous commit): the scalar layer's + and - are both fully
verified against dalek's own extraction. Remaining: x3 fork port,
Montgomery mul/reduce (kernel frontier).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
check-scalar.sh proofs phase back to 300s/4096MB: the sub_val_spec
assembly's true peak is 753MB after the atomic-scLimbs fix — the 8192 cap
was inflation left over from the slow draft and caused the 2026-07-03
swap-pressure incident (see control repo FAILURES.md). lean-guard updated
to master with Guard 3b. Button green at the honest budget, 4/4 audits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sub_val_spec closes the top-level two-clause value spec:
denote(Scalar52.sub a b) = denote(a) - denote(b) in ZMod l
for limb-bounded inputs with canonical subtrahend (scVal b < l).
Axiom-clean [propext, Classical.choice, Quot.sound]; no sorry.
The assembly documented-as-remaining last session is now done. Key resolutions:
- Applied the WP "spec_bind" rule manually instead of "step", and reduced the
resulting "let (difference,borrow) := (dw,w)" Prod-let with an explicit
"show" — this was the destructuring friction that blocked the earlier
attempt (step's arity heuristics mis-typed the pair result).
- Inlined the subtle "Choice::from" identity (no step-spec, like csel_step).
- PERFORMANCE: kept "scLimbs" as opaque atoms in the gamma5=1 derivation
instead of "unfold ... at *" — the unfold exploded omega with 2^52..2^260
coefficients and blew past 600s; atomic form proves in ~1 min (METHOD 4,
same kernel-cost discipline as the field layer).
- The 2^260 borrow-wrap and the +l conditional-add cancel in ZMod l:
beta5=0 direct; beta5=1 forces top carry gamma5=1 from scVal b < l, closed
by linear_combination over the two telescopes (sub_telescope, add_telescope).
check-scalar.sh: sub_val_spec in the manifest + Phase-3 audit (4/4 clean);
proof-phase caps raised to 600s/8192MB for the assembly; full button green.
exponentiation.threshold raised to 300 for the 2^260 literal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New in Proofs/ScalarSubSpec.lean (all axiom-clean [propext, Classical.choice,
Quot.sound], no sorry, no native_decide):
- nat_and_mask52 / nat_shift52 / nat_shift63 : 52/63-bit ops -> %,/
- sub_step_arith : isolated per-limb borrow accounting (tiny ℕ context,
correction-on-the-left so no truncated subtraction) — the METHOD-4
discipline that keeps 2^260-scale coefficients out of any one certificate
- sub_loop_spec : the FULL 5-limb borrow chain of Scalar52::sub, unrolled
via loop_step/range_next_*; wrapping_sub, 52-bit mask store, borrow-out
bit. This is the loop unroll that blocked the earlier attempt.
- csel_step : step-spec for the subtle conditional_select (faithful model)
- cond_add_l_zero_spec / cond_add_l_one_spec : BOTH cases of conditional
add-of-L, full carry chains; the condition-1 case steps the addend as a
clean value so the index_mut write-back matches sub_loop's pattern
- sub_telescope / add_telescope : the 2^52i-weighted value telescopes to
2^260, discharged by omega (no kernel-capacity blowup)
check-scalar.sh: ScalarSubSpec added to the compile manifest; Phase-3 axiom
audit extended to sub_loop_spec + cond_add_l_one_spec (3/3 clean). Full
button green.
Honest boundary: top-level sub_val_spec (⟦sub a b⟧ = ⟦a⟧-⟦b⟧ in ZMod ℓ)
is documented as remaining — every lemma it needs is proven; what's left is
the Aeneas binding-arity for destructuring sub_loop_spec's pair-valued
multi-existential postcondition inside the do-block, a mechanical not a
mathematical gap. No sorry shipped (Invariants H1/H4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- check.sh: proofs memory default 6144 -> 8192 (ReduceSpec's norm_num
step peaks above 6144; guard aborted gracefully — R3 was broken, S1
held). Matches pasta's calibration.
- check.sh: dead-file gate now exempts Scalar* (delegated to
check-scalar.sh); the gate had been un-passable since the scalar layer
landed, masked by the memory failure.
- check.sh: axiom-audit phase routed through lean-guard (cgroup + flock;
was raw lean -M), audit temp file moved into the workspace (lake env
rejects /tmp inputs — the /tmp phase had never run green).
- check-scalar.sh: NEW Phase 3 kernel axiom audit — ScalarProofs.L_val
must report exactly [propext, Classical.choice, Quot.sound].
- README: signature layer '⏳ planned' (was 'in progress' with nothing
started); planned certificate names marked as such.
Validated: full check.sh + check-scalar.sh green end-to-end in the pass-2
sweep (see formal-verification-control/COHERENCE-PASS-2.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
extract-scalar.sh: function-level roots (add/sub/mul/square/montgomery_*)
yield a 28-def Scalar52 limb-arithmetic gen with ZERO iterator/byte/wrapper
entanglement — the byte-serialization and high-level Scalar wrapper (which
pull untranslatable chunks/zip iterators) are excluded by scoping, not faked.
Proofs/ScalarDenote.lean (compiles, axiom-clean): Ell = ℓ = 2^252+..., the
Scalar52 denotation ⟦·⟧ : Scalar52 → ZMod ℓ, the ScBnd 52-bit limb invariant,
and L_val — the transpiled constants::L denotes EXACTLY the group order ℓ
(kernel-checked, no native_decide).
add/sub (Range-loop conditional reductions, tractable — field-layer pattern)
and the Montgomery mul path (shares pasta's big-coefficient kernel limit) are
in progress. check-scalar.sh is green for the foundation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>