mirror of
https://github.com/saymrwulf/verifying-crypto-with-lean.git
synced 2026-09-04 20:03:41 +00:00
New Chapter 13, 'The Second Summit: A Hash-Based Pyramid' — SLH-DSA
(FIPS 205) as the transfer experiment for the whole method:
- opens on leaf 18 as the anomaly; correctness-vs-security across the
quantum divide ('a correct implementation of a broken lock is still a
broken lock')
- Lamport -> Winternitz chains with the checksum see-saw run twice on
real w=16 numbers, including a concrete failed forgery (480 -> 479,
digit 14 -> 13)
- FORS worked at napkin scale (k=2, a=2, one reuse = one forgery) and
real scale (28 of 57,344, exponent 14)
- the virtual hypertree: digest split 21/7/2 to the bit, the 54-bit
meter peeled 9 bits per layer, verification priced exactly (254 fixed
oracle calls; the see-saw itself caps a layer at 510, so worst case
3,824 — the naive 525*35 bound is unreachable, and the chapter says
why); ~2^72 to build vs ~2^12 to check
- the eleven certificates, the loop-to-fold bridges, the honest
'visible, not correct' boundary (no second semantics — and why the
natural move fails), the cone-growth table, the t_l/t_len naming
inversion told as the war story it was, the apex as an audit
invitation with the verbatim theorem named
- 'The leaf, live': leaf-vs-head precision ('plausible, and wrong
twice'), the three-clause self-reference ledger (attested machinery /
attested scheme / honest gap), one-command tryit
- six exercises with pathway'd solutions; checkpoint hands the
who-checks-them question to the finale
Structural: attestation renamed ch14 and now carries the book's single
ending (where-to-go, further reading, final reframe, prospective
checkpoint — moved from ch12); its two interior checkpoints demoted to
bigidea/tryit so the terminal checkpoint stands alone; opening now
receives ch13's baton. ch12 ends as a chapter. Front matter: three-summit
arc, fourteen-week plan, honest discussion-exercise count; ch01 promise
ladder extended to Chapters 13/14; glossary +5 entries (and the
pre-existing Hasse-bound misordering fixed); README fourteen chapters +
build.sh recipe.
Every constant verified against fips205-slhdsa-verified and
lean-transparency-log by four adversarial checkers; arithmetic
independently recomputed; didactic panel scored the chapter 9/8 —
the book's high-water mark. Build: 128 pages, zero unresolved refs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
620 lines
33 KiB
TeX
620 lines
33 KiB
TeX
\chapter{The Second Summit: A Hash-Based Pyramid}
|
|
\label{ch:secondsummit}
|
|
|
|
\section{The theorem that survives, and the reason that doesn't}
|
|
|
|
One leaf in the log does not belong to the pyramid you just climbed. Its
|
|
subject kind says \code{slh\_dsa} where eighteen others say
|
|
\code{ed25519} or \code{merkle\_accumulator}; it carries eleven
|
|
certificates where its neighbors carry sixteen, forty-four, or sixty-one;
|
|
and the scheme it attests contains no curve, no field, no scalar --- not
|
|
one line of the algebra this book spent twelve chapters verifying. Leaf~18
|
|
attests the log's first \emph{post-quantum signature scheme}, and this
|
|
chapter is the climb that earns it.
|
|
|
|
First, the honest reason such a thing exists. Every certificate in the
|
|
Ed25519 pyramid will still be \emph{true} the day a large quantum computer
|
|
boots --- whether that day is a decade out or never; what dies that day is
|
|
the reason anyone cared. Read that carefully,
|
|
because it separates two ideas this book has been keeping distinct all
|
|
along. The certificates say the code \emph{computes the right function} ---
|
|
that the extracted verifier accepts exactly the signatures the mathematics
|
|
accepts. Whether accepting those signatures keeps anyone \emph{safe} rests
|
|
on an assumption no proof in this book touches: that computing discrete
|
|
logarithms on the curve is infeasible. Shor's algorithm, on a big enough
|
|
quantum computer, computes them efficiently. Correctness certificate:
|
|
intact. Scheme: forgeable. A correct implementation of a broken lock is
|
|
still a broken lock.
|
|
|
|
Hash functions are the part of the cryptographic toolbox that takes this
|
|
future best. The strongest known quantum attack on preimage search is
|
|
Grover's algorithm, a \emph{quadratic} speedup --- the kind of erosion you
|
|
answer with modestly larger parameters, not the kind that dissolves the
|
|
problem. So in August 2024, NIST standardized FIPS~205: \textbf{SLH-DSA},
|
|
the \emph{stateless hash-based digital signature algorithm} (descended
|
|
from SPHINCS\textsuperscript{+}) --- a signature scheme built from hash
|
|
functions and \emph{nothing else}. No group. No field. Nothing for Shor
|
|
to eat.
|
|
|
|
\begin{bigidea}
|
|
The second summit asks the question the first one could not: does the
|
|
\emph{method} --- extract the real code, build a bridge, prove the
|
|
certificates, audit the cones, attest the result --- transfer to terrain
|
|
with entirely different mathematics? Signature chains and Merkle trees
|
|
instead of limbs and group laws. If the method only worked on the terrain
|
|
it was invented for, it was a trick. If it transfers, it is a discipline.
|
|
This chapter is the experiment, run for real, on the parameter set
|
|
\textbf{SLH-DSA-SHA2-128s}.
|
|
\end{bigidea}
|
|
|
|
The whole parameter set fits on an index card, and you should copy it out
|
|
now, because every number in this chapter comes from it: hash output
|
|
$n = 16$ bytes; total tree height $h = 63$, split into $d = 7$ layers of
|
|
height $h' = 9$ (check the joint: $63 = 7 \cdot 9$); FORS forest of
|
|
$k = 14$ trees of height $a = 12$; Winternitz parameter $w = 16$ (digits
|
|
are hex nibbles, $\log_2 w = 4$), giving $\mathit{len} = 35$ chains
|
|
($32$ message nibbles $+$ $3$ checksum digits; check: $35 = 2n + 3$ ---
|
|
two nibbles per byte of the $n = 16$-byte value, plus three);
|
|
message digest $m = 30$ bytes. In the verified repository these are not
|
|
documentation --- they are the const-generic arguments baked into the one
|
|
extracted entry point, \code{slh\_verify\_128s}, in
|
|
\code{gen/SlhVerify/Funs.lean} (except $w$ and $\log_2 w$, which ride
|
|
along as the crate constants \code{W} and \code{LGW} in the same file).
|
|
|
|
\section{Signatures with nothing but a hash}
|
|
|
|
Strip the idea to one bit. Pick a hash function $F$. Choose two secret
|
|
random values $s_0, s_1$; publish $F(s_0)$ and $F(s_1)$ as your public
|
|
key. To sign the bit $0$, reveal $s_0$; to sign $1$, reveal $s_1$. A
|
|
verifier hashes what you revealed and compares against your public key.
|
|
A forger must produce a preimage of a published hash --- the exact thing
|
|
hash functions exist to prevent. That is Lamport's 1979 scheme, and it
|
|
already contains the whole species: \emph{a signature is a controlled
|
|
leak of preimages; verification is re-hashing the leak}.
|
|
|
|
Two problems, and their fixes build the entire pyramid. First, one bit
|
|
per key pair is absurd; the fix is \emph{chains}. Second, every key is
|
|
one-time --- reveal twice and a forger can mix and match; the fix is
|
|
\emph{trees}. Chains first.
|
|
|
|
\textbf{Winternitz chains (WOTS\textsuperscript{+}).} Instead of two
|
|
secrets per bit, keep one secret per hex \emph{nibble} and define a chain:
|
|
$c_0 = \text{secret}$, $c_{i+1} = F(c_i)$, sixteen positions
|
|
$c_0 \dots c_{15}$. Publish the chain \emph{end} $c_{15}$ (all
|
|
thirty-five chain ends will later be compressed to one value; the
|
|
compressor is the $T$ of the pricing box to come). To sign the nibble
|
|
value $v \in \{0,\dots,15\}$, reveal $c_v$
|
|
--- the chain value $v$ steps up. The verifier takes what you revealed
|
|
and \emph{walks the remaining steps}: $15 - v$ more applications of $F$
|
|
must land exactly on the published end.
|
|
|
|
\begin{worked}{one chain, resumed --- and the checksum see-saw}
|
|
Real parameters ($w = 16$), no napkin scaling needed. Say message nibble
|
|
$v = 11$ ($\mathtt{0xB}$). The signature reveals $c_{11}$. The verifier
|
|
computes $15 - 11 = 4$ applications of $F$ --- in the verified code the
|
|
hash \emph{address} is set to $11, 12, 13, 14$ in turn, one per step ---
|
|
and compares the result against the chain end. Four hashes; no secrets;
|
|
done.
|
|
|
|
Now attack it. $F$ is public, so anyone holding $c_{11}$ can compute
|
|
$c_{12} = F(c_{11})$ --- a valid signature for the nibble $12$! Walking
|
|
\emph{forward} is free; only walking \emph{backward} needs a preimage.
|
|
Forging ``11 $\to$ 12'' costs nothing... for that one chain. The fix is
|
|
bookkeeping, and you can run all of it by hand. Alongside the $32$
|
|
message nibbles, WOTS signs a \textbf{checksum}:
|
|
\[
|
|
\mathit{csum} \;=\; \sum_{i=0}^{31} \,(15 - \mathit{msg}_i),
|
|
\]
|
|
the total of all \emph{remaining} steps. Take the extreme case, all
|
|
nibbles zero: $\mathit{csum} = 32 \cdot 15 = 480 = \mathtt{0x1E0}$. The
|
|
code shifts left by $4$, flush against the top of two bytes, because the
|
|
digit extractor reads nibbles most-significant-first:
|
|
$\mathtt{0x1E00}$, bytes $[\mathtt{1E}, \mathtt{00}]$, whose
|
|
first three nibbles are the checksum digits $(1, 14, 0)$ --- signed with
|
|
three more chains, walked $15{-}1$, $15{-}14$, $15{-}0$ steps by the
|
|
verifier.
|
|
|
|
Now run the see-saw, on these numbers. A forger bumps one message nibble
|
|
$0 \to 1$ (walking that chain forward one step, free). The checksum
|
|
follows: $\mathit{csum} = 479 = \mathtt{0x1DF}$, shifted
|
|
$\mathtt{0x1DF0}$, digits $(1, 13, 15)$. Compare against $(1, 14, 0)$:
|
|
the \emph{third} digit rose $0 \to 15$ --- forward walk, free --- but
|
|
the \emph{second} fell $14 \to 13$, and the signature only revealed that
|
|
chain's value at position $14$. The forger needs position $13$: one step
|
|
\emph{earlier}. Walking backward. Inverting $F$. (In general: a strictly
|
|
smaller checksum must have a strictly smaller digit at the most
|
|
significant position where the two differ --- some chain always falls.)
|
|
The thirty-five chains hold each other hostage, and the
|
|
hostage-taking is pure arithmetic --- the kind a certificate can pin.
|
|
\end{worked}
|
|
|
|
In the verified repository that arithmetic \emph{is} pinned:
|
|
\lean{fips205.wots\_csum\_loop\_eq} certifies that the extracted checksum
|
|
loop computes exactly the fold
|
|
$\mathit{csum} \leftarrow \mathit{csum} + (W{-}1 - \mathit{msg}_i)$ ---
|
|
and its axiom cone is the bare kernel three, because summing nibbles
|
|
needs no hash at all. Keep that observation; it becomes a pattern.
|
|
|
|
\section{One virtual tree of $2^{63}$ keys}
|
|
|
|
Chains sign one message per key pair. The tree fix: make $2^{63}$
|
|
one-time WOTS key pairs, hash each down to a leaf, and build a Merkle
|
|
tree; the public key is the single root. A signature then contains the
|
|
one-time signature \emph{plus the authentication path} --- the $\log_2$
|
|
chain of sibling hashes from that leaf to the root. The verifier
|
|
recomputes the leaf from the WOTS signature, hashes its way up the path
|
|
--- at each level the current bit of the leaf index says whether the
|
|
running node is a left or right child, i.e.\ whether to compute
|
|
$H(\text{node} \,\Vert\, \text{sibling})$ or
|
|
$H(\text{sibling} \,\Vert\, \text{node})$ --- and compares the result to
|
|
the published root. You met this exact structure in
|
|
Chapter~\ref{ch:why}'s pyramid diagrams and will meet it once more in the
|
|
final chapter, holding this book's own proofs; here it holds signature
|
|
keys.
|
|
|
|
Except nobody can build that tree. Count it: $2^{63}$ leaves, each
|
|
requiring a full WOTS key generation ($35$ chains $\times$ $15$ steps
|
|
$+$ compression), plus the internal hashes --- on the order of $10^{21}$
|
|
hash evaluations --- roughly $5 \times 10^{21}$, about $2^{72}$ --- to
|
|
materialize once. The resolution is two-fold:
|
|
|
|
\begin{itemize}[leftmargin=1.4em]
|
|
\item \textbf{Hypertree}: don't build one tree of height $63$; build
|
|
$7$ layers of trees of height $9$, where each tree's root is signed by
|
|
a WOTS key of the tree \emph{above} it --- so nobody, signer included,
|
|
ever materializes more than a height-$9$ tree at a time. A signature
|
|
carries $7$ small WOTS signatures, one per layer.
|
|
\item \textbf{Virtuality}: secrets are generated pseudorandomly from a
|
|
seed, so any leaf can be \emph{recomputed on demand}. The full
|
|
structure exists only potentially. The signer touches one path of it
|
|
per signature; the verifier re-hashes exactly that path.
|
|
\end{itemize}
|
|
|
|
\begin{aha}
|
|
The hypertree is never built. A structure of $2^{63}$ keys --- more
|
|
one-time keys than there are seconds in two hundred billion years ---
|
|
exists only as the \emph{promise} that any requested piece can be
|
|
recomputed from a $16$-byte seed, and the root pins every piece of it at
|
|
once. This is the find/check asymmetry of Chapter~\ref{ch:prime} scaled
|
|
to its logical extreme: the object is astronomically too large to ever
|
|
exist, and verifying one path through it costs a few thousand hashes on
|
|
your laptop.
|
|
\end{aha}
|
|
|
|
One piece is missing: the hypertree's leaves are \emph{one-time} keys,
|
|
and the signer is stateless --- it cannot remember which leaves it has
|
|
used. The scheme picks the leaf pseudorandomly per message, and to
|
|
survive the rare event that two messages draw the same leaf, it does not
|
|
put the message directly under a WOTS key. Instead the message digest is
|
|
signed by \textbf{FORS} (Forest Of Random Subsets): $k = 14$ little
|
|
Merkle trees of height $a = 12$. Each tree hides $2^{12}$ secrets in its
|
|
leaves; the message digest names one leaf per tree; signing reveals
|
|
those $14$ secrets with their authentication paths, and the $14$
|
|
recomputed roots are compressed into one value which the hypertree then
|
|
certifies.
|
|
|
|
\begin{worked}{FORS on a napkin --- two trees, height two}
|
|
Shrink to $k = 2$, $a = 2$: two trees of four secrets each, eight
|
|
secrets total, digest length $k \cdot a = 4$ bits. Say the digest is
|
|
$\mathtt{1001}$. Split into $2$-bit indices: tree $0$ opens leaf
|
|
$\mathtt{10} = 2$, tree $1$ opens leaf $\mathtt{01} = 1$. The signature
|
|
reveals secret $2$ of tree $0$ with its two path siblings, and secret
|
|
$1$ of tree $1$ with its two; the verifier hashes each secret to a leaf,
|
|
climbs both paths, and compresses the two roots. Now reuse the key on a
|
|
second digest, $\mathtt{0111}$: it opens leaves $1$ and $3$ --- two
|
|
\emph{more} secrets leaked, four of eight now public. A forger wanting
|
|
to sign digest $\mathtt{1011}$ needs leaves $2$ and $3$: both already
|
|
leaked --- forgery! At napkin scale reuse is fatal in one collision. At
|
|
real scale the same arithmetic is the defense: two signatures leak at
|
|
most $28$ of $57{,}344$ secrets, and a target digest needs its
|
|
\emph{fourteen} indices --- each $12$ bits --- to land entirely inside
|
|
the leaked sliver: probability about $(28/57{,}344)^{14}$ per key, and
|
|
the pseudorandom leaf choice means the attacker cannot steer which
|
|
key pair collides. Graceful degradation is not a slogan; it is this
|
|
exponent.
|
|
\end{worked}
|
|
|
|
That degradation arithmetic --- a \emph{few} secrets out of
|
|
$14 \cdot 2^{12} = 57{,}344$, and fourteen simultaneous hits needed ---
|
|
is the reason the scheme can afford statelessness.
|
|
|
|
\begin{worked}{the digest split, to the bit --- and the price of everything}
|
|
The verifier's first act is one call to the message-digest oracle
|
|
$H_{\mathit{msg}}$, producing $m = 30$ bytes. Those $30$ bytes are then
|
|
split three ways by live arithmetic you can audit on paper:
|
|
\[
|
|
\Big\lceil \tfrac{k \cdot a}{8} \Big\rceil
|
|
= \Big\lceil \tfrac{14 \cdot 12}{8} \Big\rceil = 21 \text{ bytes}
|
|
\qquad
|
|
\Big\lceil \tfrac{h - h/d}{8} \Big\rceil
|
|
= \Big\lceil \tfrac{63 - 9}{8} \Big\rceil = 7 \text{ bytes}
|
|
\qquad
|
|
\Big\lceil \tfrac{h}{8d} \Big\rceil
|
|
= \Big\lceil \tfrac{63}{56} \Big\rceil = 2 \text{ bytes}
|
|
\]
|
|
--- and $21 + 7 + 2 = 30$ exactly, no slack byte anywhere. The first
|
|
$21$ bytes are the $14 \times 12 = 168$ bits of FORS leaf indices; the
|
|
next $7$ bytes, masked to $54$ bits, choose \emph{which} bottom-layer
|
|
tree of the $2^{54}$ signs this message; the last $2$ bytes, masked to
|
|
$9$ bits, choose the leaf within it. (The masks in the extracted code
|
|
are $\mathtt{U64.MAX} \ggg (64{-}54)$ and $\mathtt{U64.MAX} \ggg
|
|
(64{-}9)$, the shift amounts computed from $h$ and $d$ --- keep $54$,
|
|
keep $9$. And the hypertree spends the $54$ tree-index bits like a
|
|
meter: the bottom layer takes the $9$-bit leaf index directly, then each
|
|
of the six upper layers peels
|
|
$\mathit{idx\_leaf} = \mathit{idx\_tree} \bmod 2^9$ off the bottom and
|
|
shifts right by $9$; after the sixth peel, $54 - 6 \cdot 9 = 0$ bits
|
|
remain --- the budget is spent exactly at the top layer.)
|
|
|
|
Now price the whole verification, from the parameter card. Five oracles
|
|
do all the work --- $F$ steps chains and hashes leaves, $H$ hashes tree
|
|
nodes, two compressors $T$ squeeze the $35$ WOTS chain ends and the $14$
|
|
FORS roots down to single values, and $H_{\mathit{msg}}$ digests the
|
|
message --- and every one of them is countable. Input-independent first:
|
|
FORS paths $k \cdot a = 168$ calls to $H$, XMSS paths
|
|
$d \cdot h' = 63$ more --- $231$ exactly; compressions, one $T$ per WOTS
|
|
public key per layer ($7$) plus one for the FORS roots; the single
|
|
$H_{\mathit{msg}}$; and the $14$ $F$-calls hashing the revealed FORS
|
|
secrets to leaves: a fixed overhead of $254$ oracle calls. The chains
|
|
add the input-dependent part, and here the see-saw earns its keep a
|
|
second time: each chain walks at most $15$ steps, suggesting a cap of
|
|
$35 \cdot 15 = 525$ per layer --- but that cap is \emph{unreachable},
|
|
because message walks total exactly $\mathit{csum}$ while long message
|
|
walks force short checksum digits and vice versa. Run the numbers over
|
|
every possible checksum and the true per-layer maximum is $510$,
|
|
achieved precisely at the all-zero message you worked above. So: at
|
|
worst $7 \cdot 510 = 3{,}570$ chain steps, a grand total of $3{,}824$
|
|
oracle calls --- just under four thousand, typically nearer $2{,}000$.
|
|
Set the two numbers side by side: $\sim 2^{72}$ hash calls to
|
|
materialize the structure, $\sim 2^{12}$ to check one path through it.
|
|
The verifier touches a $2^{-60}$ sliver and trusts the rest to
|
|
arithmetic.
|
|
|
|
And the signature that steers those two thousand hashes? Its size is an
|
|
exercise in reading types: $n \cdot \big(1 + k(1+a) + d(\mathit{len} +
|
|
h')\big) = 16 \cdot (1 + 182 + 308) = \mathbf{7{,}856}$ bytes, against a
|
|
$32$-byte public key. (An Ed25519 signature is $64$ bytes. Post-quantum
|
|
insurance is not free; it is merely \emph{possible}.)
|
|
\end{worked}
|
|
|
|
\section{The same method, on alien terrain}
|
|
|
|
Now the experiment this chapter exists to report. The repository
|
|
\code{fips205-slhdsa-verified} runs the exact pipeline you learned in
|
|
Chapters~\ref{ch:rust}--\ref{ch:field}: pin a Rust source (a pure-Rust,
|
|
no-unsafe FIPS~205 implementation, pinned by commit), extract through
|
|
Charon and Aeneas into Lean --- starting from the one entry point
|
|
\code{slh\_verify\_128s}, with the five hash primitives marked
|
|
\emph{opaque} at the extraction boundary --- and prove certificates
|
|
about the extracted model under a memory-guarded button. Same tools,
|
|
same discipline, same \code{check.sh} skeleton with the same fail-closed
|
|
phases. What comes out the other side is \textbf{eleven certificates}:
|
|
ten loop theorems and one apex.
|
|
|
|
The ten are old friends wearing new clothes. Every one has the shape of
|
|
Chapter~\ref{ch:denotation}'s bridge: on the left, the loop the
|
|
extraction produced (chain walking, checksum accumulation, Merkle
|
|
ascent, hypertree layering); on the right, a hand-written structural
|
|
fold; between them, an equality theorem proved by unfolding one loop
|
|
turn and inducting on the trip count. You have met every one of these
|
|
folds already, as arithmetic. \lean{wots\_csum\_loop\_eq} pins the
|
|
see-saw sum. \lean{chain\_free\_loop\_eq} pins the resume-the-chain
|
|
walk, address sequence $v, v{+}1, \dots$ included.
|
|
\lean{xmss\_loop\_eq} pins the even-bit/odd-bit sibling order of the
|
|
Merkle ascent. And \lean{ht\_loop\_eq} pins the layer walk --- the
|
|
peel-nine-bits-and-shift meter you just ran on the digest split, as a
|
|
theorem.
|
|
|
|
But this terrain withholds something the first pyramid gave freely, and
|
|
the repository says so in bold type. In the field proofs, the right-hand
|
|
side of the bridge was \emph{independent mathematics} --- $\Zmod{p}$,
|
|
which Mathlib understands without ever seeing dalek's code --- so each
|
|
commuting square landed in a second semantics and meant something on its
|
|
own. The natural move here would be the same: restate SLH-DSA
|
|
verification as free-standing mathematics and prove the code equal to
|
|
\emph{that}. But what would the restatement be made of? Hash chains and
|
|
Merkle nodes --- the same oracles, the same folds; there is no
|
|
Mathlib theory of SLH-DSA waiting on the other side of the bridge the
|
|
way $\Zmod{p}$ was waiting for dalek. So the reference folds are built
|
|
from \emph{the same extracted hash oracles} the loops call, and each
|
|
certificate buys visibility rather than independent meaning: the
|
|
extracted chain loop is some forty lines of monadic, generated Lean; the
|
|
fold standing beside it in \lean{chain\_free\_loop\_eq} is fourteen you
|
|
can hold against the standard's algorithm over a coffee. That last
|
|
reading --- fold against FIPS~205 --- is a human step, named as such in
|
|
the trusted base. The repository's own phrase is the one to memorize:
|
|
read each loop certificate as \emph{``visible,'' not ``correct.''}
|
|
|
|
\begin{pitfall}
|
|
Chapter~\ref{ch:honesty} trained you to ask what a certificate does
|
|
\emph{not} say; this repository answers before you ask, and its ledger
|
|
is worth reading twice. The five hash oracles are \emph{axioms} ---
|
|
nothing relates them to FIPS~180-4's SHA-256, the same standing boundary
|
|
as SHA-512 in the Ed25519 apex. One inner loop (\code{base\_2b}, the
|
|
digit extractor that feeds both FORS indices and WOTS nibbles) is
|
|
threaded opaquely with \emph{no certificate} --- a defect there would
|
|
change the recomputed root while all eleven theorems held. The proved
|
|
subject is a monomorphic facade; its bridge to the deployed generic
|
|
verifier is a $137$-case differential test, \emph{evidence, not proof}.
|
|
Signing and key generation were never extracted at all. And everything
|
|
\emph{above} the extraction root is trusted base too --- the assembly of
|
|
the domain-separated message $M'$, including the single separator byte
|
|
that is all that distinguishes pure from prehash verification; get that
|
|
byte wrong and you have a cross-variant forgery primitive that no
|
|
certificate in the repository would notice. A trusted
|
|
base is not a weakness; an \emph{unstated} one is.
|
|
\end{pitfall}
|
|
|
|
The apex closes the pyramid the same way the Ed25519 apex did --- as an
|
|
acceptance characterization, lightly paraphrased:
|
|
|
|
\begin{lstlisting}[language=Lean]
|
|
theorem slh_verify_128s_accepts_iff (mprime sig pk) :
|
|
slh_verify_128s mprime sig pk
|
|
= do let root ← slhVerifyRoot mprime sig pk
|
|
ok (decide (root.val = pk.pk_root.val))
|
|
\end{lstlisting}
|
|
|
|
(The paraphrase elides only typography: the const-generic parameter
|
|
decorations and the \lean{Result}-monad plumbing. The verbatim statement
|
|
is theorem \lean{fips205.slh\_verify\_128s\_accepts\_iff} in
|
|
\code{verification/Proofs/ApexSpec.lean} --- diff this prose against
|
|
that source; the book taught you to distrust paraphrases, including its
|
|
own.) For \emph{every} message digest, \emph{every} signature,
|
|
\emph{every} public key at these parameters: the extracted verifier
|
|
accepts exactly when the recomputed hypertree root byte-equals the
|
|
public key's root. There is no other acceptance path --- no debug
|
|
backdoor, no unchecked branch, no early \lean{true}. Both summits end in
|
|
the same clean air: a quantified equation whose right-hand side a
|
|
stranger can read.
|
|
|
|
One more object from this repository deserves a place in your permanent
|
|
toolkit: the audit table of axiom cones, which turns
|
|
Chapter~\ref{ch:honesty}'s discipline into a picture of the pyramid
|
|
itself.
|
|
|
|
\begin{center}
|
|
\small
|
|
\begin{tabular}{@{}ll@{}}
|
|
\toprule
|
|
\textbf{Certificate layer} & \textbf{Cone beyond the kernel three} \\
|
|
\midrule
|
|
digit/byte plumbing (4 certs) & --- \emph{(pure arithmetic, no hash)} \\
|
|
chain, WOTS recompute & $F$ \\
|
|
XMSS ascent, FORS inner & $H$ \\
|
|
FORS outer & $F, H$ \\
|
|
hypertree walk & $F, H$, the WOTS compressor (axiom \code{t\_l}) \\
|
|
apex & all five oracles \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
The cone \emph{grows as you climb}: bit-shuffling needs no oracles at
|
|
all; each layer adds exactly the hash primitives it touches; the apex
|
|
carries all five. The audit enforces every row with exact equality ---
|
|
one axiom extra or one missing fails the build --- so this table is not
|
|
documentation, it is a \emph{theorem about the theorems}.
|
|
|
|
And the table carries a war story, told here so you inherit the scar
|
|
without the wound. During the audit, comparing what each compressor
|
|
axiom was \emph{used as} against what it was \emph{named} turned up a
|
|
mismatch: the axiom spelled \code{t\_l} is the one doing the standard's
|
|
$T_{\mathit{len}}$ job (compressing WOTS chain ends), while the axiom
|
|
spelled \code{t\_len} does the $T_k$ job (compressing FORS roots) ---
|
|
the source's names are \emph{inverted} against FIPS~205. Worse, both
|
|
axioms model what is a \emph{single} primitive in the Rust source. The
|
|
repository's response was not to quietly ``fix'' the names --- renaming
|
|
proves nothing --- but to document the inversion at the axiom
|
|
declarations and keep the two axioms separate anyway, assuming
|
|
\emph{less} (two possibly-different functions) rather than more. Had the
|
|
auditor trusted spelling, the cone table above would read cleanly and
|
|
lie. When you audit a stranger's certificate, judge every axiom by what
|
|
it is used as, never by what it is named. Names are spelling; cones are
|
|
property.
|
|
|
|
The experiment the chapter opened with can now be called. The pipeline
|
|
re-ran end to end on terrain its designers never saw: extraction, the
|
|
loop-to-fold bridge shape, the exact-cone audit, the fail-closed button,
|
|
the acceptance-characterization apex --- all of it transferred without a
|
|
structural change. Discipline, not trick. With one honest asterisk the
|
|
ledger already made you read: this terrain offered no second semantics,
|
|
so the bridges here are thinner than the field layer's --- visibility,
|
|
not independent meaning. A method that transfers \emph{and tells you
|
|
exactly how much thinner its guarantees just got} is rarer than either
|
|
half alone.
|
|
|
|
\section{The leaf, live}
|
|
|
|
The climb ends where the chapter began, but now you can read every field.
|
|
Leaf~18 of the log --- \code{entries/000018.json} in the mirror,
|
|
nineteenth and newest --- binds the repository at its pinned commit to
|
|
the eleven certificate names you now know, each with \code{status}
|
|
proven, each with its \emph{observed} axiom cone equal to its expected
|
|
cone: kernel-3 for the four plumbing certificates, the oracle sets of
|
|
the table above for the rest, all five for the apex. Its scope block
|
|
recites the trusted base you just read --- oracles assumed, signing out
|
|
of scope, \code{base\_2b} uncertified, differential bridge not a proof
|
|
--- because an attestation that hides its boundary is advertising, and
|
|
this log does not accept advertising.
|
|
|
|
``Leaf~18 landed, so the log went post-quantum'' --- plausible, and
|
|
wrong twice. First: leaf~18 is the first leaf \emph{about} a
|
|
post-quantum signature scheme, but its own issuance signature --- the
|
|
block inside \code{entries/000018.json}, like every leaf's --- is
|
|
Ed25519 only. Second: the log's \emph{tree heads} did start carrying an
|
|
additional SLH-DSA-SHA2-128s co-signature in the same publication batch
|
|
--- but the first dual-signed head is the size-\emph{14} head (leaf~18
|
|
arrives at size $19$, five appends later). Two separate facts, one lazy
|
|
conflation avoided; append-only history keeps them distinguishable
|
|
forever.
|
|
|
|
And here the estate quietly eats its own cooking --- with the books kept
|
|
scrupulously. The head's required Ed25519 signature is produced by the
|
|
dalek library attested at leaf~13 --- forty-four certificates,
|
|
self-inclusion verified inside the signature block itself: machinery
|
|
attested \emph{in the log}, signing \emph{for the log}. The additional
|
|
co-signature closes a different, looser loop: it uses parameter set
|
|
SHA2-128s --- \emph{exactly} the parameter set whose verification path
|
|
leaf~18 proves --- but the OpenSSL backend that actually produces and
|
|
checks that co-signature carries no certificate anywhere; what is
|
|
attested is the scheme's verification path in an independent, extracted
|
|
implementation, not the tool on duty. And for the \emph{signing}
|
|
operations, no loop closes at all --- no signing operation is proven for
|
|
either scheme, and the log says so. Read the three clauses again in
|
|
order: attested machinery, attested scheme, honest gap. Even the
|
|
self-reference keeps its books honest.
|
|
|
|
\begin{tryit}
|
|
Touch the second summit yourself; it costs one command. Clone
|
|
\code{github.com/saymrwulf/lean-transparency-log} and run
|
|
\code{python3 verify.py --receipt
|
|
receipts/fips205-slhdsa-verified.receipt.json} --- the receipt binds
|
|
leaf~18 by inclusion proof to the size-$19$ signed head. Then open
|
|
\code{entries/000018.json} and find, with your own eyes: the eleven
|
|
certificate names, the five oracle axioms in the apex's observed cone,
|
|
and the exclusions block naming \code{base\_2b}. You have just audited a
|
|
post-quantum verification claim --- boundary and all --- in the time it
|
|
takes to pour a coffee.
|
|
\end{tryit}
|
|
|
|
\section*{Exercises}
|
|
|
|
\exercise{(Paper) All thirty-two message nibbles equal $7$. Compute the
|
|
checksum, the shift, the two bytes, and the three checksum digits, as
|
|
the worked example did for the all-zero message. How many steps of $F$
|
|
does the verifier walk on each of the three checksum chains?}
|
|
|
|
\exercise{(Paper) Re-derive the digest split from the parameter card:
|
|
show $\lceil 168/8 \rceil = 21$, $\lceil 54/8 \rceil = 7$,
|
|
$\lceil 63/56 \rceil = 2$, and verify they exhaust $m = 30$. Then
|
|
explain in one sentence why the second slice is masked to exactly $54$
|
|
bits and what would go wrong with a $55$-bit mask.}
|
|
|
|
\exercise{(Paper) Derive the signature size from the types: $n$ bytes of
|
|
randomness, $k$ revealed FORS secrets plus $k$ auth paths of $a$ nodes,
|
|
and $d$ XMSS signatures of $\mathit{len} + h'$ hashes each. Show the
|
|
total is $16 \cdot 491 = 7{,}856$ and locate the single largest
|
|
contributor.}
|
|
|
|
\exercise{(Audit drill) State the see-saw precisely and prove it: if a
|
|
forger changes the message so that some nibble strictly increases and
|
|
none decreases, then the checksum strictly decreases, and at least one
|
|
checksum digit's chain must be evaluated at a position \emph{earlier}
|
|
than the one revealed. Why does ``earlier position'' mean ``invert
|
|
$F$''?}
|
|
|
|
\exercise{(Audit drill) The audit holds five oracle axioms but the Rust
|
|
source has four hash primitives: \code{t\_l} and \code{t\_len} both
|
|
delegate to one function. Explain why modeling one primitive as two
|
|
independent axioms is the \emph{conservative} choice --- what could a
|
|
proof using ``they are equal'' conclude that the current model cannot,
|
|
and why is refusing that conclusion safer?}
|
|
|
|
\exercise{(Discussion) The day a cryptographically relevant quantum
|
|
computer exists, which statements in this book's estate become false?
|
|
Work through: the four Ed25519 apex certificates; the Ed25519 signatures
|
|
on the log's tree heads; leaf~18's certificates; the SLH-DSA
|
|
co-signatures on the heads. Sort each into ``still true,'' ``true but no
|
|
longer load-bearing,'' and ``broken'' --- and defend the sorting.}
|
|
|
|
\section*{Solutions and pathways}
|
|
\solutionsintro
|
|
|
|
\solhead{13.1}
|
|
\pathway Same three moves as the worked box: sum the remaining steps,
|
|
align, extract nibbles.
|
|
\answer $\mathit{csum} = 32 \cdot (15 - 7) = 256 = \mathtt{0x100}$.
|
|
Shifted left $4$: $\mathtt{0x1000}$, bytes $[\mathtt{10}, \mathtt{00}]$,
|
|
first three nibbles $(1, 0, 0)$. The verifier walks $15 - 1 = 14$,
|
|
$15 - 0 = 15$, $15 - 0 = 15$ steps. (Sanity: middle-of-the-road message
|
|
digits still force near-full walks on checksum chains --- the checksum
|
|
digits are usually \emph{small}, so their remaining walks are long.)
|
|
|
|
\solhead{13.2}
|
|
\pathway The three ceilings are $\lceil ka/8\rceil$,
|
|
$\lceil (h - h/d)/8 \rceil$, $\lceil h/(8d) \rceil$ with the card's
|
|
values substituted.
|
|
\answer $\lceil 14 \cdot 12 / 8 \rceil = \lceil 21 \rceil = 21$;
|
|
$\lceil (63-9)/8 \rceil = \lceil 6.75 \rceil = 7$;
|
|
$\lceil 63/56 \rceil = 2$; and $21 + 7 + 2 = 30 = m$. The tree index
|
|
addresses one of $2^{h - h/d} = 2^{54}$ bottom-layer trees, so the mask
|
|
keeps exactly $54$ bits; a $55$-bit mask could name a tree that does not
|
|
exist --- the verifier would recompute a root of an out-of-range tree
|
|
and reject a valid signature (or, worse in a sloppier implementation,
|
|
wrap around silently). Ranges are part of correctness; that is why the
|
|
extracted masks are certificate-adjacent code, not comments.
|
|
|
|
\solhead{13.3}
|
|
\pathway Transcribe the type structure into a sum, then factor out $n$.
|
|
\answer $n\big(1 + k(1{+}a) + d(\mathit{len}{+}h')\big)
|
|
= 16\,(1 + 14 \cdot 13 + 7 \cdot 44) = 16\,(1 + 182 + 308)
|
|
= 16 \cdot 491 = 7856$. The hypertree's $308$ hash-values (seven WOTS
|
|
signatures of $35$ plus seven auth paths of $9$) dominate: $4{,}928$ of
|
|
the $7{,}856$ bytes --- the price of splitting one impossible tree into
|
|
seven possible ones.
|
|
|
|
\solhead{13.4}
|
|
\pathway Write the checksum as a function of the digit vector and
|
|
difference it.
|
|
\answer $\mathit{csum} = \sum_i (15 - \mathit{msg}_i)$, so if some
|
|
nibbles increase by a total of $\delta > 0$ and none decrease,
|
|
$\mathit{csum}$ drops by exactly $\delta$. The checksum digits are the
|
|
base-$16$ representation of the (aligned) checksum; a strictly smaller
|
|
value must have at least one strictly smaller digit at some position
|
|
(compare digits from the most significant end; the first difference is
|
|
a decrease). The signature revealed the chain value at the \emph{old}
|
|
digit's position; a forgery needs the value at a strictly earlier
|
|
position of that chain --- but the chain runs forward by $F$, so an
|
|
earlier value is a preimage of the revealed one under iterated $F$.
|
|
Producing it is inverting the hash --- the assumption the whole scheme
|
|
stands on.
|
|
|
|
\solhead{13.5}
|
|
\pathway Ask what each axiom is \emph{used for} in the proofs, then ask
|
|
what merging them would license.
|
|
\answer The model with two independent axioms proves everything the
|
|
certificates claim while assuming only ``there exist two functions with
|
|
these signatures.'' Adding \lean{t\_l = t\_len} would let a proof
|
|
conclude that a WOTS compression can be replayed as a FORS compression
|
|
(and vice versa) --- an interchangeability the standard does not grant
|
|
(the two uses differ in domain separation via the address argument) and
|
|
no certificate needs. Assuming less can only make the theorems
|
|
\emph{harder} to prove, never falsely stronger; if the theorems still go
|
|
through, the weaker assumption is strictly safer. The general habit:
|
|
when in doubt, split the axiom --- and let the cones show exactly which
|
|
theorem leaned on which half.
|
|
|
|
\solhead{13.6}
|
|
\pathway Separate ``the theorem is true'' from ``the assumption behind
|
|
the scheme's security is true'' for each artifact.
|
|
\answer The four Ed25519 apex certificates: \emph{still true} ---
|
|
kernel-checked equations about extracted code do not cite discrete-log
|
|
hardness anywhere. The Ed25519 head signatures: \emph{true but no longer
|
|
load-bearing} --- the signatures still verify, but a quantum forger
|
|
could now mint alternative heads, so their evidentiary weight collapses
|
|
(the certificates about the \emph{verifier} remain true; the
|
|
unforgeability of \emph{signing} was never proven and is now also not
|
|
believed). Leaf~18's certificates: \emph{still true}, and still
|
|
load-bearing --- nothing in their cones or their scheme depends on
|
|
Shor-vulnerable structure. The SLH-DSA head co-signatures: the reason
|
|
the slot exists --- \emph{load-bearing by design} on that day, resting
|
|
on hash preimage resistance, eroded only quadratically by Grover.
|
|
Moral, worth saying aloud: proofs of correctness are
|
|
quantum-proof; \emph{schemes} are not; and a log that dual-signs its
|
|
heads is planning for the difference.
|
|
|
|
\begin{checkpoint}
|
|
You should now be able to: explain why a correctness certificate
|
|
survives a quantum computer while the scheme it certifies may not;
|
|
run a WOTS chain-resume and the checksum see-saw by hand at $w = 16$;
|
|
price an SLH-DSA-SHA2-128s verification ($254$ fixed oracle calls plus
|
|
the chains, against $2^{72}$ to build what it checks); read the eleven
|
|
certificates and the cone-growth table as one object; and recite what
|
|
the second summit does \emph{not} claim --- oracles assumed,
|
|
\code{base\_2b} uncertified, the $M'$ assembly above the extraction
|
|
root, signing never in scope. Both pyramids now
|
|
stand, and both end the same way: a script prints \texttt{ALL GREEN}
|
|
and a leaf lands in a public log. Which leaves exactly one question in
|
|
this book, and it is the deepest one: that script, and that log --- who
|
|
checks \emph{them}? The final chapter has been waiting for you.
|
|
\end{checkpoint}
|