2026-07-22 22:00:53 +00:00
|
|
|
|
/- drafts/ChainSpec.lean — WIP: Algorithm 5 (chain) fidelity.
|
|
|
|
|
|
|
|
|
|
|
|
Goal: the extracted `chain_free` loop equals the explicit s-fold
|
|
|
|
|
|
application of the (opaque) hash F, with hash-address set to
|
|
|
|
|
|
i, i+1, …, i+s−1 in turn. Proving this rules out off-by-one loop
|
|
|
|
|
|
bounds, a wrong address field, and wrong threading — the exact bug
|
|
|
|
|
|
class the SLH-DSA verify path is exposed to. F stays opaque
|
|
|
|
|
|
(verify_mono.oracle.f), so the certificate cone is the three kernel
|
|
|
|
|
|
axioms + oracle.f only.
|
|
|
|
|
|
-/
|
|
|
|
|
|
import SlhVerify.Funs
|
|
|
|
|
|
open Aeneas Aeneas.Std Result ControlFlow
|
|
|
|
|
|
open fips205
|
|
|
|
|
|
|
|
|
|
|
|
set_option maxHeartbeats 2000000
|
|
|
|
|
|
|
|
|
|
|
|
namespace fips205
|
|
|
|
|
|
|
|
|
|
|
|
/-- The mathematical chaining fold, threading the address exactly as the
|
|
|
|
|
|
extracted body does: at each step set the hash address to the current
|
|
|
|
|
|
index, hash, advance the index (monadically, matching the u32 range
|
post-flip drill over phase-2 window: all claims held; one doc upgrade
Re-verified from primary sources:
- THE DEEP CHECK: the three discharged u32 Step defs vs the PINNED
rustc's own library/core/src/iter/range.rs (nightly-2026-06-01,
u32 = narrower arm on 64-bit): forward/backward = try_from-then-
checked_{add,sub} (try_from succeeds iff n < 2^32), steps_between =
(0, None) iff start > end else saturated diff twice. Branch-for-
branch identical to the defs in FunsExternal.lean.
- commit scopes: bde63f5 = exactly the 3 axiom->def swaps; d6e4d93 =
only the new draft file.
- fresh audits: the u32 Step INSTANCE and each of the three defs are
axiom-clean ([propext(, Classical.choice, Quot.sound)]); check.sh
green fresh; draft compiles with exactly ONE sorry (line 65, succ
branch); base case genuinely closed.
- the 'dalek u32 Step axioms are vestigial' claim: confirmed — every
IteratorRange.next call site in dalek's gen uses StepUsize.
- remote heads match local everywhere.
Drill catch (documentation, not error): the loop increments the index
BEFORE each oracle call (forward_checked inside next, .panic on
overflow), the fold AFTER (add's overflow error) — equal only under
the theorem's start.val + s < 2^32 precondition, which is exactly why
that precondition exists. Now documented on chainFoldN so the
step-case prover discharges both increments from the bound and nobody
weakens it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 07:01:06 +00:00
|
|
|
|
iterator's `forward_checked`). Recursion on the step count.
|
|
|
|
|
|
|
|
|
|
|
|
EFFECT-ORDER NOTE (audited 2026-07-23): the extracted loop increments the
|
|
|
|
|
|
index FIRST (inside `IteratorRange.next`, via `forward_checked`, failing
|
|
|
|
|
|
with `.panic` on overflow BEFORE any oracle call), while this fold hashes
|
|
|
|
|
|
first and increments AFTER (failing with the add's overflow error). The
|
|
|
|
|
|
two therefore agree only where neither increment can fail — which is
|
|
|
|
|
|
exactly what the theorem's precondition `start.val + s < 2^32` provides
|
|
|
|
|
|
(it makes every intermediate index < 2^32, so `forward_checked` always
|
|
|
|
|
|
yields `some` and `start + 1#u32` always succeeds). The step-case proof
|
|
|
|
|
|
must discharge BOTH monadic increments from that bound; do not weaken the
|
|
|
|
|
|
precondition. -/
|
2026-07-22 22:00:53 +00:00
|
|
|
|
noncomputable def chainFoldN {N : Std.Usize} (pk_seed : Slice Std.U8) :
|
|
|
|
|
|
types.Adrs → Array Std.U8 N → Std.U32 → Nat → Result (Array Std.U8 N)
|
|
|
|
|
|
| _, tmp, _, 0 => ok tmp
|
|
|
|
|
|
| adrs, tmp, start, (k+1) => do
|
|
|
|
|
|
let adrs1 ← helpers.Adrs.set_hash_address adrs start
|
|
|
|
|
|
let s ← lift (Array.to_slice tmp)
|
|
|
|
|
|
let tmp1 ← verify_mono.oracle.f N pk_seed adrs1 s
|
|
|
|
|
|
let start1 ← start + 1#u32
|
|
|
|
|
|
chainFoldN pk_seed adrs1 tmp1 start1 k
|
|
|
|
|
|
|
|
|
|
|
|
/-- The loop over the range [start, start+s) equals the s-step fold. -/
|
|
|
|
|
|
theorem chain_free_loop_eq {N : Std.Usize} (pk_seed : Slice Std.U8) (s : Nat) :
|
|
|
|
|
|
∀ (start : Std.U32) (adrs : types.Adrs) (tmp : Array Std.U8 N),
|
|
|
|
|
|
start.val + s < 2 ^ 32 →
|
|
|
|
|
|
∀ (stop : Std.U32), stop.val = start.val + s →
|
|
|
|
|
|
verify_mono.chain_free_loop { start := start, «end» := stop } pk_seed adrs tmp
|
|
|
|
|
|
= chainFoldN pk_seed adrs tmp start s := by
|
|
|
|
|
|
induction s with
|
|
|
|
|
|
| zero =>
|
|
|
|
|
|
intro start adrs tmp _ stop hstop
|
|
|
|
|
|
-- empty range: start.val = stop.val, so start = stop and lt is false
|
|
|
|
|
|
have hse : start = stop := by
|
|
|
|
|
|
apply Std.UScalar.eq_of_val_eq; omega
|
|
|
|
|
|
subst hse
|
|
|
|
|
|
unfold verify_mono.chain_free_loop chainFoldN
|
|
|
|
|
|
rw [loop.eq_1]
|
|
|
|
|
|
unfold verify_mono.chain_free_loop.body core.iter.range.IteratorRange.next
|
|
|
|
|
|
simp [core.cmp.impls.PartialOrdU32.lt]
|
|
|
|
|
|
| succ k ih =>
|
|
|
|
|
|
intro start adrs tmp hb stop hstop
|
|
|
|
|
|
-- non-empty: lt start stop is true, so the iterator yields `some start`
|
|
|
|
|
|
-- and steps to start+1; one loop step then aligns with one fold step and
|
|
|
|
|
|
-- the IH closes the tail.
|
|
|
|
|
|
have hlt : start.val < stop.val := by omega
|
|
|
|
|
|
unfold verify_mono.chain_free_loop chainFoldN
|
|
|
|
|
|
rw [loop.eq_1]
|
|
|
|
|
|
unfold verify_mono.chain_free_loop.body core.iter.range.IteratorRange.next
|
|
|
|
|
|
-- OPEN FRONT (the crux): align the loop's monadic `forward_checked start 1`
|
|
|
|
|
|
-- (a `Result U32`) with the fold's `start1 ← start + 1#u32`, then fold the
|
|
|
|
|
|
-- continuation `loop body (…)` back into `chain_free_loop` and apply `ih`
|
|
|
|
|
|
-- at start+1 / stop / k. Needs the U32 add-spec (no overflow from `hb`) and
|
|
|
|
|
|
-- ControlFlow bind-normalisation. Tractable (dalek loop-spec pattern), WIP.
|
|
|
|
|
|
sorry
|
|
|
|
|
|
|
|
|
|
|
|
end fips205
|