Machine-checked verification campaign for the SLH-DSA (FIPS 205) verify path: Rust (fips205-source) -> Charon/Aeneas -> Lean 4. Parameter set SLH-DSA-SHA2-128s first. STATUS: skeleton - nothing proven yet.
Find a file
mrwulf 0fa36c7258 phase 2: THIRD CERTIFICATE — XMSS auth-path Merkle loop (Algorithm 10)
fips205.xmss_loop_eq (Proofs/XmssSpec.lean): the extracted
xmss_pk_from_sig_free_loop equals the explicit Merkle-path fold — at step
k set the tree height to k+1, test bit k of the leaf index; even bit:
tree_index := i/2 and H(node || auth[k]); odd bit: tree_index := (i-1)/2
and H(auth[k] || node). This pins the sibling hash ORDER, the address
schedule, and the auth-path indexing of Merkle verification. Exact cone:
[propext, Classical.choice, Quot.sound, verify_mono.oracle.h] — the first
certificate where H enters; F does not (the loop runs above the WOTS+
computation). check.sh green over all three certificates.

Fidelity review at authorship (three-way): extracted body (gen Funs.lean
761-801) == Rust verify_mono.rs xmss_pk_from_sig_free (verbatim from
upstream xmss.rs, hash calls -> oracle) == FIPS 205 Algorithm 10, incl.
the per-branch operation order (even: node-slice then auth[k]; odd:
auth[k] then node-slice) and the k+1 tree height.

Proof: the chain/wots recipe on a u32 range — u32_succ / fwd_succ / hnext
/ loop_unfold_bind reused VERBATIM from ChainSpec. New layer lesson (the
one novel obstruction, on pattern): the loop body BRANCHES on the index
bit, so the step lemma splits with by_cases + if_pos/if_neg; and the
get_tree_index pair-bind needs its matcher made concrete before the tail
normalizes — bind_congr + rintro to fix the scrutinee, then FULL simp
(only full simp iota-reduces the pair matcher; simp only will not) with
bind_assoc + bind_ok + the loop def closes each branch. The certificate's
own induction threads the IH under the opaque binds of BOTH branches with
bind_congr, per branch, ending exact ih.

check.sh: PROOFS += XmssSpec, CERTS += fips205.xmss_loop_eq, audit
imports XmssSpec (self-test structure anchors untouched). README: status
three certificates, Algorithm numbering per upstream comments (wots=8,
xmss=10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:18:44 +02:00
verification phase 2: THIRD CERTIFICATE — XMSS auth-path Merkle loop (Algorithm 10) 2026-07-23 16:18:44 +02:00
.gitignore Phase 1: clean extraction + type-checking SLH-DSA-SHA2-128s model 2026-07-22 22:21:19 +02:00
README.md phase 2: THIRD CERTIFICATE — XMSS auth-path Merkle loop (Algorithm 10) 2026-07-23 16:18:44 +02:00
TRUSTED-BASE.md audit catch 2: the oracle boundary is FIVE, not six 2026-07-22 23:02:40 +02:00

fips205-slhdsa-verified

Machine-checked verification campaign for the SLH-DSA (FIPS 205) verify path, extracted from a pure-Rust implementation into Lean 4 via Charon/Aeneas — the same pipeline, discipline, and honesty rules as the four ed25519 campaigns (dalek/anza/risc0/betrusted-ed25519-verified).

STATUS: THREE CERTIFICATES PROVEN — chain (5) + WOTS+ loop (8) + XMSS path (10)

verification/check.sh is green (exit 0): the model compiles, the proofs compile, and the axiom audit passes. Three certificates proven so far, bottom-up:

  • fips205.chain_free_loop_eq (Algorithm 5, WOTS+ chaining): the extracted chain_free loop equals the explicit s-fold hash chain, with the hash address set to i, i+1, …, i+s1 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. Its #print axioms cone is exactly [propext, Classical.choice, Quot.sound, verify_mono.oracle.f] — the three kernel axioms plus the one hash oracle it touches, and nothing else (no transpiler plumbing; the u32 range machinery was discharged with real definitions). check.sh Phase 3 fails the build if any certificate cone contains anything outside the kernel three + the five documented SHA-2 oracles.

  • fips205.wots_loop1_eq (Algorithm 8, WOTS+ pk recomputation — the chain loop): the extracted wots_pk_from_sig_free_loop1 equals the fold that, at each index i in [0, LEN), sets the chain address to i and runs chain_free on sig[i] starting at digit msg[i] for W1msg[i] steps, writing tmp[i]. This is the layer above chain: it consumes chain_free and pins that the LEN chains run with the right start indices, step counts, and slots. Cone: kernel three + verify_mono.oracle.f.

  • fips205.xmss_loop_eq (Algorithm 10, XMSS pk-from-sig — the authentication-path Merkle loop): the extracted xmss_pk_from_sig_free_loop equals the fold that, at step k, sets the tree height to k+1, tests bit k of the leaf index, and on an even bit halves the tree index and hashes H(node ∥ auth[k]), on an odd bit sets the tree index to (i1)/2 and hashes H(auth[k] ∥ node). This pins the Merkle sibling ORDER (the even/odd rule), the tree-height/tree-index address schedule, and the auth-path indexing — the heart of Merkle-path verification. Cone: kernel three + verify_mono.oracle.h (the first certificate where H enters; F does not — the loop runs above the WOTS+ computation).

Foundations behind this (2026-07-22/23): the Aeneas-compat patch (additive monomorphic verify module through a named oracle boundary; charon + aeneas exit 0); the u32 range-loop de-plumbing (faithful Step defs vs pinned rustc, axiom-clean); fidelity pinned by a differential test in the snapshot (valid / corrupted / wrong-message).

The remaining layers (hypertree, FORS, the WOTS+/XMSS input-prep plumbing, apex) are not yet proven — the pyramid rises one certificate at a time, each audited to the same boundary.

Subject

  • Upstream: integritychain/fips205 — pure-Rust FIPS 205 (final standard, 2024-08-13), zero unsafe, no_std, const-generic parameterization, modules mirroring the FIPS 205 algorithm structure.
  • Pinned at upstream commit 30bac08580aa61f653e5436d1bbacb5ffac446c4 (2025-09-01), snapshotted with full history at saymrwulf/fips205-source (snapshot head 5dca0db, whose single deviation from verbatim is the removal of upstream CI workflows, documented in that commit). Aeneas-compat patches will land in the snapshot repo as transparent, individually-justified commits — never upstream. No affiliation with, and no changes proposed to, the upstream project.
  • Parameter set: SLH-DSA-SHA2-128s first (the small-signature profile deployed in the firmware/code-signing lane). The architecture generalizes; each further parameter set is a separate claim (rigor invariant R2).

Scope

Verify path only. The extraction cone, mirroring FIPS 205's own algorithm tree:

slh_verify -> slh_verify_internal
  -> fors_pk_from_sig
  -> ht_verify -> xmss_pk_from_sig -> wots_pk_from_sig -> chain

Key generation and signing are out of scope (trusted base), exactly as ed25519 signing was. The five verify-path hash oracles (h_msg, f, h, t_l, t_len — SHA-2 instantiations; prf/prf_msg are sign-side only and never enter the cone) are opaque external models with written justifications, kept outside every certificate's dependency cone (honesty invariant H4); their semantics are the standing SHA-2 oracle boundary documented in TRUSTED-BASE.md.

Gate-0 record (2026-07-22)

Per TARGETS.md ("re-verify before use"), the subject was probed before this repository was created:

  • Charon: clean (charon cargo --preset=aeneas, roots at the verify cone, sha2/sha3/zeroize/rand_core opaque, features slh_dsa_sha2_128s) — LLBC produced, exit 0.
  • Aeneas: translated the entire const-generic verify cone to Lean definitions (wots.chainslh.slh_verify_internal all generated), with exactly one obstruction class (3 unique errors): the crate::hashers::Hashers struct of plain function pointers cannot be translated.
  • Phase 1 — DONE (2026-07-22): the Aeneas-compat patch landed in fips205-source (snapshot 2d89ee3): an additive monomorphic SHA2-128s verify module (src/verify_mono.rs) whose hash suite is reached through named free functions in verify_mono::oracle (marked opaque at the Charon boundary) — the sha512_*-shim pattern. Two further compat refinements: the message-digest input M' passes as a single &[u8] (nested &[&[u8]] is untranslatable), and one let-else became the is_err/unwrap idiom. verification/extract.sh now re-derives the model from the mono root; charon + aeneas both exit 0, and verification/check.sh compiles the result. The generic paths and all twelve parameter sets are untouched (the only change to existing code is two lines wiring the module).

What will be claimed (when the button is green, not before)

One theorem per layer, each a statement about the extracted functions (H3), compiled by verification/check.sh with a per-certificate #print axioms audit (H1): chain semantics, WOTS+ pk recomputation, XMSS path recomputation, hypertree acceptance, FORS pk recomputation, and the apex — slh_verify_internal accepts iff the recomputed hypertree root equals the pinned public-key root.

The allowed axiom set, stated precisely: unlike the ed25519 field and scalar layers (whose cones are exactly [propext, Classical.choice, Quot.sound]), the hash oracles permeate every SLH-DSA layer — chain already calls F. So each certificate's cone may contain the three kernel axioms plus at most the five named oracles (verify_mono.oracle.{h_msg, f, h, t_l, t_len}) — and nothing else: the transpiler-plumbing axioms currently in FunsExternal.lean must be discharged before any certificate ships, and the audit fails the button if any of them (or anything unlisted) appears in a cone.

Discipline

Every Lean compile in this repository runs under verification/lean-guard (memory-capped, machine-wide serialized). Extraction is reproducible from the committed extract.sh against the pinned snapshot (R1). What cannot be proven is named in TRUSTED-BASE.md, not hidden (H5).