fips205-slhdsa-verified/README.md

144 lines
7.4 KiB
Markdown
Raw Normal View History

# 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`).
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 14:18:44 +00:00
## STATUS: THREE CERTIFICATES PROVEN — chain (5) + WOTS+ loop (8) + XMSS path (10)
2026-07-23 09:34:59 +00:00
`verification/check.sh` is **green** (exit 0): the model compiles, the
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 14:18:44 +00:00
proofs compile, and the axiom audit passes. **Three certificates proven so
far, bottom-up:**
2026-07-23 09:34:59 +00:00
- **`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`.
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 14:18:44 +00:00
- **`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).
2026-07-23 09:34:59 +00:00
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).
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 14:18:44 +00:00
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](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.chain` … `slh.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](TRUSTED-BASE.md), not hidden (H5).