mirror of
https://github.com/saymrwulf/fips205-slhdsa-verified.git
synced 2026-09-03 19:53:49 +00:00
verification/check.sh is green (exit 0): 3 phases — model compiles, proofs compile, axiom audit passes. fips205.chain_free_loop_eq (Proofs/ChainSpec.lean): the extracted chain_free loop = the explicit s-fold hash chain, hash address i..i+s-1. Machine-checked, for the deployed monomorphic SHA2-128s verify path, that there is no off-by-one loop bound, no wrong address field, no wrong threading. #print axioms cone = EXACTLY [propext, Classical.choice, Quot.sound, verify_mono.oracle.f] — kernel three + the one hash oracle, zero transpiler plumbing (the u32 Step machinery was discharged earlier with real defs). check.sh Phase 3 enforces cone subset of kernel-3 + the five SHA-2 oracles, failing the build otherwise. Proof structure (all lemmas axiom-clean, no sorry): u32_succ + fwd_succ (the monadic u32 increment, checked against pinned rustc semantics); loop_unfold_bind (one turn of the Aeneas loop fixpoint, closed by cases because a hand-written match compiles to a non-defeq matcher); hnext + hbody (iterator step and loop body as clean equations); chain_step (one loop step = one fold step); chain_free_loop_eq (induction, IH threaded under the opaque binds with bind_congr). Both prior sorries closed. Certificate lives in Proofs/ (not drafts/); the WIP draft is retired. check.sh committed as -F stdin per the no-backticks-in-commit-messages rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
160 lines
7.3 KiB
Text
160 lines
7.3 KiB
Text
/- Proofs/ChainSpec.lean — Algorithm 5 (chain / WOTS+ chaining) fidelity.
|
||
|
||
THEOREM chain_free_loop_eq: the extracted `chain_free` loop equals the
|
||
explicit s-fold application of the hash F, with the hash-address set to
|
||
i, i+1, …, i+s−1 in turn. This rules out — machine-checked, for the
|
||
deployed monomorphic SHA2-128s verify path — an off-by-one loop bound, a
|
||
wrong address field, and wrong threading. F stays opaque
|
||
(verify_mono.oracle.f), so the certificate cone is the three kernel axioms
|
||
+ oracle.f, and nothing else (audited by check.sh Phase 3).
|
||
|
||
The proof: an induction on the step count. `chain_step` is one loop step =
|
||
one fold step, proven by unfolding the Aeneas `loop` fixpoint one turn
|
||
(loop_unfold_bind), reducing the range iterator to a clean equation
|
||
(fips205_hnext) and the loop body to a clean do-block (fips205_hbody), and
|
||
aligning the monadic u32 index increment (u32_succ / fwd_succ) with the
|
||
fold's. The succ case threads the IH under the opaque binds with
|
||
bind_congr.
|
||
-/
|
||
import SlhVerify.Funs
|
||
open Aeneas Aeneas.Std Result ControlFlow
|
||
open fips205
|
||
|
||
set_option maxHeartbeats 4000000
|
||
|
||
namespace fips205
|
||
|
||
/-- The successful u32 increment as a clean equation (no overflow). -/
|
||
theorem u32_succ {start : Std.U32} (hb : start.val + 1 < 2 ^ 32) :
|
||
∃ w : Std.U32, start + 1#u32 = ok w ∧ w.val = start.val + 1 := by
|
||
have he := Std.UScalar.add_equiv start (1#u32)
|
||
cases hc : start + 1#u32 with
|
||
| ok w =>
|
||
refine ⟨w, rfl, ?_⟩
|
||
rw [hc] at he
|
||
have : (1#u32 : Std.U32).val = 1 := by rfl
|
||
omega
|
||
| fail e =>
|
||
exfalso; rw [hc] at he; simp [Std.UScalar.inBounds] at he
|
||
have : (1#u32 : Std.U32).val = 1 := by rfl
|
||
omega
|
||
| div => rw [hc] at he; simp at he
|
||
|
||
/-- The range iterator's forward step, when start+1 succeeds. -/
|
||
theorem fwd_succ {start w : Std.U32} (hw : start + 1#u32 = ok w) :
|
||
U32.Insts.CoreIterRangeStep.forward_checked start 1#usize = ok (some w) := by
|
||
unfold U32.Insts.CoreIterRangeStep.forward_checked
|
||
have h1 : (1#usize : Std.Usize).val < 2 ^ 32 := by decide
|
||
simp only [h1, dif_pos]
|
||
have hone : Std.U32.ofNatCore (1#usize : Std.Usize).val h1 = (1#u32 : Std.U32) := by
|
||
apply Std.UScalar.eq_of_val_eq; rfl
|
||
rw [hone]
|
||
unfold Std.U32.checked_add core.num.checked_add_UScalar Option.ofResult
|
||
rw [hw]
|
||
|
||
/-- The Aeneas `loop` fixpoint, unfolded one turn into a bind. The `casesOn`
|
||
continuation matches loop's own reduction, so it closes by cases+rfl
|
||
(a hand-written `match` would compile to a different, non-defeq matcher). -/
|
||
theorem loop_unfold_bind {α β : Type} (body : α → Result (ControlFlow α β)) (x : α) :
|
||
loop body x = body x >>= (fun r => ControlFlow.casesOn r (fun c => loop body c) (fun d => ok d)) := by
|
||
conv_lhs => rw [loop.eq_1]
|
||
cases body x with
|
||
| ok cf => cases cf <;> rfl
|
||
| fail e => rfl
|
||
| div => rfl
|
||
|
||
/-- The range iterator step on a non-empty range, as a clean equation. -/
|
||
theorem hnext {start stop w : Std.U32}
|
||
(hd : decide (start.val < stop.val) = true) (hwok : start + 1#u32 = ok w) :
|
||
core.iter.range.IteratorRange.next U32.Insts.CoreIterRangeStep
|
||
{ start := start, «end» := stop }
|
||
= ok (some start, { start := w, «end» := stop }) := by
|
||
unfold core.iter.range.IteratorRange.next
|
||
simp only [core.cmp.impls.PartialOrdU32.lt, hd, decide_true, if_true,
|
||
bind_tc_ok, bind_ok, core.clone.impls.CloneU32.clone, fwd_succ hwok]
|
||
|
||
/-- The loop body on a non-empty range reduces to a clean do-block. -/
|
||
theorem hbody {N : Std.Usize} (pk_seed : Slice Std.U8) (start stop w : Std.U32)
|
||
(adrs : types.Adrs) (tmp : Array Std.U8 N)
|
||
(hd : decide (start.val < stop.val) = true) (hwok : start + 1#u32 = ok w) :
|
||
verify_mono.chain_free_loop.body pk_seed { start := start, «end» := stop } adrs tmp
|
||
= (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
|
||
ok (cont (({ start := w, «end» := stop } : core.ops.range.Range Std.U32), adrs1, tmp1))) := by
|
||
unfold verify_mono.chain_free_loop.body
|
||
rw [hnext hd hwok]
|
||
simp
|
||
|
||
/-- The mathematical chaining fold: at each step set the hash address to the
|
||
current index, hash, advance the index (monadically, matching the u32
|
||
range iterator). Recursion on the step count. Agreement with the extracted
|
||
loop holds under `start.val + s < 2^32`, which makes every increment
|
||
succeed. -/
|
||
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
|
||
|
||
/-- One full loop step on a non-empty range = one fold step, tail as the
|
||
continuation loop. -/
|
||
theorem chain_step {N : Std.Usize} (pk_seed : Slice Std.U8) (start stop : Std.U32)
|
||
(adrs : types.Adrs) (tmp : Array Std.U8 N)
|
||
(hlt : start.val < stop.val) (hb : start.val + 1 < 2 ^ 32) :
|
||
verify_mono.chain_free_loop { start := start, «end» := stop } pk_seed adrs tmp
|
||
= (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
|
||
verify_mono.chain_free_loop { start := start1, «end» := stop } pk_seed adrs1 tmp1) := by
|
||
obtain ⟨w, hwok, _⟩ := u32_succ hb
|
||
have hd : decide (start.val < stop.val) = true := by simp [hlt]
|
||
conv_lhs => rw [verify_mono.chain_free_loop, loop_unfold_bind]
|
||
dsimp only
|
||
rw [hbody pk_seed start stop w adrs tmp hd hwok]
|
||
simp only [bind_assoc, bind_ok]
|
||
conv_rhs => rw [show (start + 1#u32) = ok w from hwok]
|
||
simp only [bind_tc_ok, bind_ok]
|
||
rfl
|
||
|
||
/-- **Algorithm 5 fidelity.** The extracted chain loop over [start, start+s)
|
||
equals the explicit s-fold hash-chain. -/
|
||
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
|
||
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
|
||
have hlt : start.val < stop.val := by omega
|
||
have hb1 : start.val + 1 < 2 ^ 32 := by omega
|
||
obtain ⟨w, hwok, hwv⟩ := u32_succ hb1
|
||
rw [chain_step pk_seed start stop adrs tmp hlt hb1]
|
||
unfold chainFoldN
|
||
rw [hwok]
|
||
simp only [bind_tc_ok, bind_ok]
|
||
have hbound : w.val + k < 2 ^ 32 := by omega
|
||
have hstop' : stop.val = w.val + k := by omega
|
||
apply bind_congr; intro adrs1
|
||
apply bind_congr; intro s
|
||
apply bind_congr; intro tmp1
|
||
exact ih w adrs1 tmp1 hbound stop hstop'
|
||
|
||
end fips205
|