Chapter 18: Hash-based signature cryptanalysis
Every hash-based signature in Part III reduces to three hash properties. Preimage resistance: given , find with . Second-preimage resistance: given , find with . Collision resistance: find any distinct with . Formally, the SPHINCS+/SLH-DSA proof packages these intuitions into stronger properties: pseudorandom-function security, distinct-function multi-target second-preimage resistance for the WOTS+, FORS, and Merkle hash calls, and interleaved target subset resilience for the FORS message mapping (Aumasson et al., 2020).
Each scheme in Part III reduces forgery to one of those properties, on a target population of its own:
| Scheme | What a forgery reduces to |
|---|---|
| Lamport (Chapter 14) | finding unrevealed preimages for the digest positions a message selects |
| WOTS+ (Chapter 15) | moving backward in at least one hash chain, a preimage problem for the chain function |
| FORS (Chapter 16) | obtaining the secret values for all leaf indices a new message digest selects |
| SLH-DSA (Chapter 17) | any of the above, on a hypertree whose every hash call is bound to a position by ADRS |
The question is: how many hash targets does a signature expose, what does an adversary gain from attacking any one of them, and do the FIPS 205 parameter sets absorb the resulting security reduction? The numbers below answer all three.
Multi-target preimage on SLH-DSA-128s
Section titled “Multi-target preimage on SLH-DSA-128s”A single SLH-DSA-SHA2-128s signature is a sequence of -byte strings. They are not all hash outputs. FORS contributes secret values (PRF outputs) and authentication-path Merkle nodes, a total of strings. The hypertree contributes WOTS+ signatures of chain values each, for strings. Here with message-digit chains and checksum chains. The checksum forces at least one chain backward, so a forged digest cannot shorten every chain (Chapter 15).
The signature also carries the XMSS authentication paths above the WOTS+ leaves: Merkle nodes in total, since each of the XMSS signatures contributes nodes. With the -byte randomizer , the full signature is -byte strings. That is bytes, the FIPS 205 signature size for SLH-DSA-SHA2-128s (National Institute of Standards and Technology, 2024).
The preimage-style attack surface is not the same as the signature-component list. WOTS+ signature elements are chain values. Forging one means moving backward in a chain, a preimage problem for the chain function. A FORS forgery either reuses an already-revealed FORS secret value or, for an unrevealed index, attacks the corresponding leaf hash (the verifier recomputes that leaf from the revealed secret, and the leaf hash is never transmitted). The FORS and XMSS authentication-path nodes are not preimage targets. They belong to the Merkle second-preimage surface, analyzed separately under attack-by-attack cost analysis. The FORS and XMSS authentication nodes therefore count toward signature size but not toward the preimage-target population below.
As an intuition model, count the FORS leaf hashes inside one FORS instance’s position space, , plus the WOTS+ revealed chain values the corresponding hypertree layer produces, . For 128s that is preimage-style targets.
Without ADRS domain separation, the multi-target preimage advantage against this intuition-model population is bits, which drops the effective preimage security from 128 bits to bits. With ADRS, each hash call uses a distinct tweaked function, so inverting one target provides no information about any other, and the multi-target term returns to the single-target preimage bound of 128 bits (Aumasson et al., 2020).
import math
n_bytes = 16n_bits = 8 * n_bytesk, a = 14, 12t = 2**aw = 16
lg_w = int(math.log2(w))ell_1 = math.ceil(8 * n_bytes / lg_w)mc = ell_1 * (w - 1)ell_2 = math.ceil((math.floor(math.log2(mc)) + 1) / lg_w)ell = ell_1 + ell_2d = 7
revealed_fors = k * (1 + a)revealed_wots = d * ellh = 63randomizer = 1sig_elements = randomizer + revealed_fors + revealed_wots + hsig_bytes = sig_elements * n_bytesfors_wots_elems = revealed_fors + revealed_wots
fors_instance_targets = k * twots_layer_targets = d * ellper_instance_total = fors_instance_targets + wots_layer_targets
adv = math.log2(per_instance_total)eff_no_adrs = n_bits - adv
print(f"FORS signature values (n-byte): {revealed_fors}")# ==> FORS signature values (n-byte): 182print(f"WOTS+ chain values (n-byte) : {revealed_wots}")# ==> WOTS+ chain values (n-byte) : 245print(f"XMSS auth-path nodes (n-byte) : {h}")# ==> XMSS auth-path nodes (n-byte) : 63print(f"randomizer R (n-byte) : {randomizer}")# ==> randomizer R (n-byte) : 1print(f"signature n-byte strings : {sig_elements}")# ==> signature n-byte strings : 491print(f"signature size (bytes) : {sig_bytes:,}")# ==> signature size (bytes) : 7,856print(f"FORS+WOTS+ signature elems : {fors_wots_elems}")# ==> FORS+WOTS+ signature elems : 427print(f"FORS targets per instance : {fors_instance_targets:,}")# ==> FORS targets per instance : 57,344print(f"WOTS+ targets per instance : {wots_layer_targets}")# ==> WOTS+ targets per instance : 245print(f"per-instance total : {per_instance_total:,}")# ==> per-instance total : 57,589print(f"multi-target advantage : {adv:.1f} bits")# ==> multi-target advantage : 15.8 bitsprint(f"effective security (no ADRS) : {eff_no_adrs:.1f} bits")# ==> effective security (no ADRS) : 112.2 bitsprint(f"single-target preimage bound : {n_bits} bits")# ==> single-target preimage bound : 128 bitsEvery Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch18/, one file per block. Appendix C covers the clone and the environment they run on.
The 15.8-bit reduction would, if every target were reachable through one untweaked hash function, drop category 1 below its 128-bit reference. ADRS removes that simple -target amplification: each tree node, chain step, and secret-value derivation carries the position it occupies, so an evaluation against one target is not also an evaluation against the others. The address names a position in the structure rather than counting calls, so a verifier recomputing a node uses the same address the signer did. At the algorithm level the address is 32 bytes. The SHA-2 parameter sets compress it to a 22-byte before it enters the hash functions (National Institute of Standards and Technology, 2024). This does not zero the full SPHINCS+/SLH-DSA proof loss. It removes the dominant shared-function multi-target term. The remaining signing-query and construction loss is carried by the SPHINCS+ parameter search, whose sets FIPS 205 approves (Aumasson et al., 2020, sec. 7.1.2; National Institute of Standards and Technology, 2024).
The diagram below labels three attack surfaces on a stylized SLH-DSA hypertree: multi-target preimage on FORS leaf hashes (bottom), second-preimage on internal Merkle nodes (middle), and WOTS+ chain endpoint inversion (at each layer boundary).
Multi-target advantage, birthday bound, and quantum cost formulas
Section titled “Multi-target advantage, birthday bound, and quantum cost formulas”Convention: in the formulas below, denotes the hash output length in bits. FIPS 205 uses for the security parameter in bytes. The relationship is . The code blocks use n_bytes and n_bits to distinguish the two.
A second convention: the “128-bit”, “192-bit”, and “256-bit” figures here are exponent shorthand. FIPS 205 does not claim a single-number bit security. Its category claim is comparative. Breaking a category 1, 3, or 5 set is claimed to need resources at least comparable to breaking a generic block cipher with a 128-, 192-, or 256-bit key, under realistic models of computation (National Institute of Standards and Technology, 2024).
Multi-target preimage advantage
Section titled “Multi-target preimage advantage”Model as an ideal -bit hash. That is the random-function heuristic Boneh and Shoup make explicit, and one-wayness is a property the model supplies rather than a consequence of the output width (Sections 8.10.2 and 8.11.1 in Boneh & Shoup, 2023). Given one target , a fresh query hits it with probability , so preimage search takes evaluations in expectation. When the adversary holds distinct target digests under the same function, a fresh query hits one of them with probability , so generic preimage search takes evaluations in expectation. The effective bit security drops from to .
For preimage-style attacks in this chapter, counts FORS leaf hashes and WOTS+ revealed-chain-value targets. Internal Merkle nodes form a related but distinct second-preimage target surface, treated separately below.
FORS forgery probability
Section titled “FORS forgery probability”FORS forgery does not require inverting a hash. The adversary who has observed signatures on the same FORS instance has seen index sets, each containing indices from . Forgery succeeds if the adversary finds a message whose FORS indices are all covered by previously revealed leaves. A given index in one tree is missed by all signatures with probability , so it is covered with probability . All trees are covered with probability , which reduces to only when (Aumasson et al., 2020). The approximation is loose for the “f” parameter sets, where is as small as 64.
This is the conditional coverage probability for a single fixed FORS instance reused times, against a fresh random candidate digest. It is not the full SLH-DSA key-level forgery probability. The SPHINCS+ interleaved-target-subset-resilience bound also weights the chance the digest selects that same instance out of , sums over how many of the prior signatures landed on it, and charges the adversary’s offline trials (Aumasson et al., 2020).
In SLH-DSA the hypertree leaf space is at least positions, and each position derives its own FORS instance. The birthday-paradox estimate for the expected number of FORS-instance collisions after signatures is intuition, not the FIPS lifetime limit. FIPS 205 designs its parameter sets to stay EUF-CMA secure for up to signatures per key (National Institute of Standards and Technology, 2024). At that design limit averages about two signatures per position. Position reuse is real and is absorbed by the FORS few-time bound (Chapter 16), not assumed away. The formula above bounds the worst case for a single instance: if one FORS instance were reused times, how large can grow before forgery becomes feasible?
Grover’s algorithm and hash preimage
Section titled “Grover’s algorithm and hash preimage”Grover’s algorithm reduces brute-force search from classical evaluations to quantum evaluations (Grover, 1996). For an -bit hash, quantum preimage search costs approximately quantum hash calls. NIST defines post-quantum security categories relative to the cost of breaking specific symmetric primitives: category 1 requires resources comparable to AES-128 key search, category 3 to AES-192, and category 5 to AES-256. The Grover bound is a first-order approximation. The actual quantum gate cost of each Grover iteration depends on the circuit depth of the hash or cipher evaluation (National Institute of Standards and Technology, 2024).
BHT quantum collision finding
Section titled “BHT quantum collision finding”The Brassard-Hoyer-Tapp (BHT) algorithm finds collisions in quantum evaluations using entries of quantum-accessible RAM (QRAM) (Brassard et al., 1998). For , this is approximately quantum evaluations, below the naive Grover exponent of . That does not directly set the NIST category: the categories compare full attack resources against reference primitives under realistic models of computation, not naive quantum query exponents.
The figure is a query-complexity result in an idealized black-box model with substantial quantum-accessible memory. It is not a gate-cost estimate. NIST-style category comparisons do not simply replace the collision exponent by from that query count alone. Circuit depth, memory access, and the cost of fault-tolerant quantum memory all matter (National Institute of Standards and Technology, 2016). BHT is worth explaining, but it is not the category-setting bottleneck for SLH-DSA in the simplified model used here. That bottleneck is preimage resistance under Grover, not collision resistance (National Institute of Standards and Technology, 2024).
ADRS domain separation
Section titled “ADRS domain separation”Without address binding, one hash evaluation can be read as an attempt against a large population of structurally identical targets, and for targets the generic preimage exponent drops by about . SLH-DSA’s tweakable hash construction , from the SPHINCS+ framework, feeds the public seed and a position address into every WOTS+, FORS, and Merkle-node hash call (Aumasson et al., 2020). The address encodes the hypertree layer, subtree index, key position, chain index, and step within the chain: 32 bytes at the algorithm level, a compressed 22-byte in the SHA-2 instantiation (National Institute of Standards and Technology, 2024). A query for one address is not a query against every other target, so the simple -target amplification does not apply.
This does not mean the proof has zero loss. Theorem 9.1 of the round-3 specification, stated in Section 9 and proved in Section 9.2, is the tight reduction that specification attempted, and it is not a statement about single-target hash security. The proof it came with did not survive, which the paragraph after the list takes up. The theorem assumes five things (Aumasson et al., 2020):
- , and meet the post-quantum distinct-function multi-target second-preimage notion.
- satisfies the specification’s equation (14): every image value has at least two preimages.
- and are post-quantum pseudorandom function families.
- The bitmask generator is modelled as a quantum-accessible random oracle.
- is post-quantum interleaved-target-subset resilient.
That proof was flawed. Kudinov, Kiktenko and Fedorov found the defect in 2020, in the security argument for the Winternitz one-time signature the construction is built on. It was never turned into an attack on SPHINCS+ (Hülsing & Kudinov, 2022). What survived was a non-tight reduction, which does not support the parameter sets the specification claims. Hülsing and Kudinov recovered a tight proof in 2022 without changing the scheme, at a loss of a factor against the originally claimed bound. It rests on a different hypothesis set. Theorem 3 of the paper keeps the two pseudorandom-function assumptions on and and interleaved-target-subset resilience for message compression, and asks the tweakable hash functions for target-collision resistance, decisional second-preimage resistance, preimage resistance and undetectability in the paper’s single-function multi-target form: the adversary’s targets carry distinct tweaks, each function is a member of the collection that shares its public seed, and the target counts are set by the tree and chain parameters. Their results cover both the simple and the robust tweakable-hash constructions, with the preimage bound for the simple one resting on a conjecture carried over from the round-3 submission (Hülsing & Kudinov, 2022). So the five hypotheses above are what the round-3 theorem asserted it needed, and not the assumptions a current tight bound for SLH-DSA rests on.
The first is the formal counterpart of the paragraph above. ADRS is what makes a distinct-tweak multi-target assumption the right one to state, and it is what removes the dominant shared-function multi-target term. Two steps then separate the theorem from a claim about deployed SLH-DSA. Reading its bound as a security estimate against a single-target hash instantiation is one. Carrying it across to the final construction is the other, and the fourth hypothesis is where that bites: bitmasks belong to the robust variant, and FIPS 205 approves only the simple instances (Appendix A in National Institute of Standards and Technology, 2024). The remaining construction loss is absorbed by the parameter search behind those approved sets rather than by enlarging beyond the NIST category target (Aumasson et al., 2020, sec. 7.1.2).
Attack-by-attack cost analysis
Section titled “Attack-by-attack cost analysis”Second-preimage attacks on Merkle trees
Section titled “Second-preimage attacks on Merkle trees”A Merkle tree internal node is vulnerable to second-preimage attack: find a different pair with . If the adversary succeeds at any internal node, the adversary can substitute an entire subtree without invalidating the root.
Without domain separation, the same untweaked compression function is reused across tree levels. A candidate child pair that matches a node value is not syntactically tied to one level, so a second preimage found for a node is not bound to where it sits. With ADRS, the position address is part of the function input, so the same child pair is a different hash query at a different level, and a match at one level does not transfer.
import hashlibimport struct
left = hashlib.sha256(b"node-left").digest()[:16]right = hashlib.sha256(b"node-right").digest()[:16]
h_level1 = hashlib.sha256(left + right).hexdigest()[:16]h_level2 = hashlib.sha256(left + right).hexdigest()[:16]print(f"without ADRS: level 1 == level 2 ? {h_level1 == h_level2}")# ==> without ADRS: level 1 == level 2 ? True
adrs1 = struct.pack(">I", 1) + b"\x00" * 28adrs2 = struct.pack(">I", 2) + b"\x00" * 28h_adrs1 = hashlib.sha256(adrs1 + left + right).hexdigest()[:16]h_adrs2 = hashlib.sha256(adrs2 + left + right).hexdigest()[:16]print(f"with ADRS: level 1 == level 2 ? {h_adrs1 == h_adrs2}")# ==> with ADRS: level 1 == level 2 ? FalseThe block is a toy demonstration of cross-position separation, not a second-preimage attack simulation. Without ADRS, identical inputs at different tree levels produce identical hashes, so a second preimage found at one level transfers to any other. With ADRS, the level byte makes the inputs distinct, and the outputs differ.
Multi-target preimage across all FIPS 205 parameter sets
Section titled “Multi-target preimage across all FIPS 205 parameter sets”The table below computes the multi-target preimage advantage for all six FIPS 205 SHA-2 parameter sets. For each set, the per-FORS-instance target count is (FORS leaf hashes) plus (WOTS+ revealed chain values). The “no_adrs” column shows the effective preimage security if all targets were attackable simultaneously. The “adrs” column shows the ADRS-defended security (single-target). The “cat” column is the NIST security category.
import math
def wots_ell(n_bytes, w): lg_w = int(math.log2(w)) ell_1 = math.ceil(8 * n_bytes / lg_w) mc = ell_1 * (w - 1) ell_2 = math.ceil((math.floor(math.log2(mc)) + 1) / lg_w) return ell_1 + ell_2
params = [ ("SLH-DSA-SHA2-128s", 16, 63, 7, 12, 14, 16), ("SLH-DSA-SHA2-128f", 16, 66, 22, 6, 33, 16), ("SLH-DSA-SHA2-192s", 24, 63, 7, 14, 17, 16), ("SLH-DSA-SHA2-192f", 24, 66, 22, 8, 33, 16), ("SLH-DSA-SHA2-256s", 32, 64, 8, 14, 22, 16), ("SLH-DSA-SHA2-256f", 32, 68, 17, 9, 35, 16),]
header = f"{'name':<22} {'n':>3} {'targets':>10} {'adv':>6} {'no_adrs':>8} {'adrs':>5} {'cat':>4}"print(header)for nm, n, h, d, a, k, w in params: t = 2**a ell = wots_ell(n, w) targets = k * t + d * ell adv = math.log2(targets) n_bits = 8 * n eff_no_adrs = n_bits - adv eff_adrs = n_bits cat_num = {128: 1, 192: 3, 256: 5}[n_bits] print(f"{nm:<22} {n_bits:>3} {targets:>10,} {adv:>6.1f} {eff_no_adrs:>8.1f} {eff_adrs:>5} {cat_num:>4}")# ==> name n targets adv no_adrs adrs cat# ==> SLH-DSA-SHA2-128s 128 57,589 15.8 112.2 128 1# ==> SLH-DSA-SHA2-128f 128 2,882 11.5 116.5 128 1# ==> SLH-DSA-SHA2-192s 192 278,885 18.1 173.9 192 3# ==> SLH-DSA-SHA2-192f 192 9,570 13.2 178.8 192 3# ==> SLH-DSA-SHA2-256s 256 360,984 18.5 237.5 256 5# ==> SLH-DSA-SHA2-256f 256 19,059 14.2 241.8 256 5The “s” (small signature) variants expose more FORS targets than the “f” (fast) variants, but not because they use more trees. The “s” variants use larger FORS trees ( to , so from to ) and fewer of them ( to ). The “f” variants use small trees ( to , to ) and more of them ( to ). The per-instance target count is larger for the “s” sets because grows faster than shrinks.
If every target were attackable through one untweaked hash function, every parameter set would fall below the preimage-query count its category is named for: the 128-bit sets to 112 or 116 bits, the 256-bit sets to 237 or 241 bits. Those are query counts on one hash rather than category verdicts, which NIST prices as attack costs under a depth limit against its reference problems (National Institute of Standards and Technology, 2016). ADRS removes this shared-function multi-target term, so each set returns to its single-target preimage bound. The residual SPHINCS+/SLH-DSA proof loss is then carried by the SPHINCS+ parameter search behind the approved sets, not by enlarging (Aumasson et al., 2020, sec. 7.1.2).
Grover and BHT quantum cost estimates
Section titled “Grover and BHT quantum cost estimates”The table below reports the classical and quantum attack costs for each hash output size used in FIPS 205. Classical preimage is . Quantum preimage (Grover) is . Classical collision (birthday) is . BHT quantum collision is (with large quantum memory) (Brassard et al., 1998; Grover, 1996).
import math
header = f"{'n_bits':>10} {'class_pre':>10} {'quant_pre':>10} {'class_coll':>11} {'bht_coll':>10} {'nist_cat':>9}"print(header)for n_bits, cat in [(128, 1), (192, 3), (256, 5)]: cp = n_bits qp = n_bits // 2 cc = n_bits // 2 bht = n_bits / 3 print(f"{n_bits:>10} {cp:>10} {qp:>10} {cc:>11} {bht:>10.1f} {cat:>9}")# ==> n_bits class_pre quant_pre class_coll bht_coll nist_cat# ==> 128 128 64 64 42.7 1# ==> 192 192 96 96 64.0 3# ==> 256 256 128 128 85.3 5The BHT collision column shows that quantum collision finding would be cheaper than quantum preimage search ( versus at ) under an unrestricted black-box memory model. BHT’s query exponent alone does not determine a NIST category: NIST measures an attack against several resource metrics rather than one, and a comparison needs a concrete circuit and memory cost model, which the figure does not supply (National Institute of Standards and Technology, 2016). SLH-DSA’s security argument reduces to preimage resistance of the individual hash calls, not collision resistance. Collision resistance matters only for the internal Merkle nodes, where the adversary must find a second preimage (not a free collision) to substitute a subtree. The binding constraint is quantum preimage at , which is the query count each category’s reference is named for. FIPS 205 assigns the categories themselves, on the full scheme rather than on this exponent (National Institute of Standards and Technology, 2024).
Chapter 36 (threat model) and Chapter 37 (signature surface) extend this analysis to L1 blockchain scale. ADRS removes the multi-target amplification inside a single signer’s keypair; a population of many independent signers adds a separate multi-key effect. FIPS 205 blunts it by adding PK.seed to PRF, a separate Appendix A change from adding and PK.seed to the SHA-2 input against long-message multi-target second-preimage attacks (National Institute of Standards and Technology, 2024). Those chapters treat per-signer ADRS and the multi-key population effect as distinct, not as one analysis carried over unchanged.
SHA-256/SHA-512 split rationale
Section titled “SHA-256/SHA-512 split rationale”This split belongs to the SHA2 family alone. Within it, FIPS 205 uses SHA-256 for the F and PRF functions at every category, and moves H and to SHA-512 when (security categories 3 and 5). The two message-hashing roles move as well, though not to a bare hash call: becomes a truncated HMAC-SHA-512, and an MGF1-SHA-512 expansion over a SHA-512 digest. The six SHAKE parameter sets do not split at all, instantiating all six roles with SHAKE256 at every category (National Institute of Standards and Technology, 2024).
A major reason is the chaining-value width. SHA-256 has a 256-bit internal state (chaining value). The H function in SLH-DSA hashes two -byte inputs (the left and right children of a Merkle node), which for is 64 bytes of payload. The tweakable hash construction prepends PK.seed and the compressed ADRS, so the total input spans multiple SHA-256 compression blocks. An adversary who targets the chaining value rather than the final hash output faces a multi-target second-preimage problem against the 256-bit chaining value, not the -bit hash output. For category 5 (, targeting 256-bit security), 256-bit chaining-value width provides zero margin.
FIPS 205 Appendix A records the SHA-512 substitution as a deliberate change from the SPHINCS+ submission, made after weaknesses were found in reaching category-5 security with SHA-256. A separate Appendix A change adds and PK.seed to the SHA-2 input, to blunt multi-target long-message second-preimage attacks (National Institute of Standards and Technology, 2024).
SHA-512 has a 512-bit chaining value. For , the chaining-value width (512 bits) exceeds the security target (256 bits) by 256 bits, which absorbs the multi-target reduction comfortably. The F function still uses SHA-256 at every security category. Its SHA-256 input is PK.seed padded with zeros to a full 64-byte block, then the 22-byte compressed ADRS and one -byte value. For that message-dependent part fits in one further SHA-256 block, so the multi-block chaining-value concern that pushes H and to SHA-512 does not bite F.
FORS forgery and implementation attacks
Section titled “FORS forgery and implementation attacks”FORS reuse thresholds across the parameter sets
Section titled “FORS reuse thresholds across the parameter sets”FORS forgery does not require breaking the hash function. The adversary exploits index coverage: after observing enough signatures, the adversary may have seen every index in every FORS tree and can forge for any message. The probability depends on (number of trees), (leaves per tree), and (number of signatures on the same FORS instance).
At (a single signature), the forgery probability is . The table below shows this probability for all six SHA-2 parameter sets. It then gives the exact first reuse count at which reaches and , found by binary search on the exact coverage formula, not the approximation.
import math
params_fors = [ ("SLH-DSA-128s", 14, 12), ("SLH-DSA-128f", 33, 6), ("SLH-DSA-192s", 17, 14), ("SLH-DSA-192f", 33, 8), ("SLH-DSA-256s", 22, 14), ("SLH-DSA-256f", 35, 9),]
def log2_fors_forgery(q, k, t): if q == 0: return float("-inf") log_miss = q * math.log1p(-1.0 / t) # ln (1 - 1/t)^q covered = -math.expm1(log_miss) # 1 - (1 - 1/t)^q return k * math.log2(covered)
def first_q_at_or_above(k, t, threshold_bits): lo, hi = 0, 1 while log2_fors_forgery(hi, k, t) < -threshold_bits: hi *= 2 while lo + 1 < hi: mid = (lo + hi) // 2 if log2_fors_forgery(mid, k, t) < -threshold_bits: lo = mid else: hi = mid return hi
print("single-signature forgery: log2(P) = -a*k")header = f"{'name':<16} {'k':>3} {'a':>3} {'t':>6} {'log2_P':>8}"print(header)for nm, k, a in params_fors: t = 2**a print(f"{nm:<16} {k:>3} {a:>3} {t:>6} {-a*k:>8}")print()print("exact first q with P >= 2^-128 and >= 2^-64")header2 = f"{'name':<16} {'k':>3} {'t':>6} {'q@2^-128':>9} {'q@2^-64':>9}"print(header2)for nm, k, a in params_fors: t = 2**a q128 = first_q_at_or_above(k, t, 128) q64 = first_q_at_or_above(k, t, 64) print(f"{nm:<16} {k:>3} {t:>6} {q128:>9} {q64:>9}")# ==> single-signature forgery: log2(P) = -a*k# ==> name k a t log2_P# ==> SLH-DSA-128s 14 12 4096 -168# ==> SLH-DSA-128f 33 6 64 -198# ==> SLH-DSA-192s 17 14 16384 -238# ==> SLH-DSA-192f 33 8 256 -264# ==> SLH-DSA-256s 22 14 16384 -308# ==> SLH-DSA-256f 35 9 512 -315# ==># ==> exact first q with P >= 2^-128 and >= 2^-64# ==> name k t q@2^-128 q@2^-64# ==> SLH-DSA-128s 14 4096 8 176# ==> SLH-DSA-128f 33 64 5 20# ==> SLH-DSA-192s 17 16384 89 1253# ==> SLH-DSA-192f 33 256 18 78# ==> SLH-DSA-256s 22 16384 293 2341# ==> SLH-DSA-256f 35 512 43 170The single-signature forgery probabilities are far below the security targets: for SLH-DSA-128s versus the target. The exact-threshold columns give the first integer for which is at or above the stated threshold. For SLH-DSA-128s, is still below while is already above it, and is the first to reach . The small- “f” sets cross sooner: for SLH-DSA-128f at .
These are reuses of one fixed FORS instance, a local reuse intuition, not a key lifetime bound. The key-level forgery probability is the SPHINCS+ interleaved-target-subset-resilience bound, not this single-instance figure (Aumasson et al., 2020). What separates the two is the position distribution set out where this chapter first derived the coverage formula. Randomized message hashing spreads signatures across positions, so the -signature design limit averages about two per position rather than concentrating them on one.
(k, t) parameter tradeoffs
Section titled “(k, t) parameter tradeoffs”Larger (more leaves per FORS tree) exponentially reduces forgery probability at a given , because each tree has more slots the adversary must cover. Larger (more trees) also exponentially reduces forgery probability, because the adversary must match all trees simultaneously. Both increase signature size: each FORS tree contributes bytes (one leaf value plus authentication-path nodes), and there are trees. The tradeoff is visible in the “s” versus “f” split: the “s” variants use large and moderate for smaller signatures; the “f” variants use small and large for faster tree construction (fewer authentication-path nodes per tree).
Fault injection and timing side channels
Section titled “Fault injection and timing side channels”Fault injection during WOTS+ chain computation can expose an earlier chain value. A chain runs to step during key-generation and public-key recomputation, and to the message digit during signing. If a fault returns the value at step instead, the adversary hashes forward from it to reach any later step . That forges a signature for any message whose digit at that position lies in the range. The countermeasure is verify-after-sign: after computing the WOTS+ signature, re-derive the public key from the signature using the verification algorithm and compare it against the stored public key. Any fault changes the intermediate value, which propagates to a different public key, and the check fails.
Fault attacks fall outside the ordinary EUF-CMA model. They are an implementation concern, and FIPS 205 explicitly directs implementers to protect SLH-DSA against side-channel and fault attacks (National Institute of Standards and Technology, 2024). Verify-after-sign catches simple local computation faults only when the recomputation is independent and covers the public-root path. It is not a complete defence: in the grafting attack below a faulted signature still verifies, so a real implementation also needs redundancy, duplicated or independently recomputed tree computations, or fault sensors.
Timing side channels arise because WOTS+ signing hashes chain exactly times, where is the -th digit of the message encoding, including the checksum digits. The number of hash evaluations varies with the message digest and checksum, not with SK.seed, so the leak is about message-digest structure rather than the private key. In many signature APIs the message is already public, which limits the impact. It still matters when the signing input is confidential, when timing composes with a fault attack, or on shared hardware, so hardened implementations hash every chain times regardless and select the correct intermediate value. The cost is hash evaluations per signature instead of the sum of the message digits, roughly doubling the signing time at .
Grafting: fault attacks on the deterministic randomizer
Section titled “Grafting: fault attacks on the deterministic randomizer”The fault attack above targets one WOTS+ chain. A grafting attack targets the hypertree. SLH-DSA derives the signing position from , where is a per-message randomizer. FIPS 205 sets in one of two ways (FIPS 205 §9.2) (National Institute of Standards and Technology, 2024). The hedged variant makes also depend on fresh per-signature randomness. The deterministic variant makes a pseudorandom function of the message and the secret key alone.
Castelnovi, Martinelli, and Prest showed the deterministic variant is fragile under fault injection (Castelnovi et al., 2018). The adversary requests two signatures on the same message. Determinism forces the same hypertree path each time, so one WOTS+ key signs the same subtree root on both runs. A fault injected into the upper-tree computation of the second run makes that key sign a different root. Two WOTS+ signatures on different roots under one key expose enough chain material to partially compromise that WOTS+ key, the same WOTS+ reuse weakness as Chapter 15. That gives the adversary WOTS+ signing capacity for a non-negligible fraction of candidate roots. Searching over attacker-built subtree roots turns that partial capacity into a universal forgery, and what it costs depends on the parameter set and on where the fault lands. Genêt’s analysis of SPHINCS+ prices the grafting search between and hash evaluations when the adversary holds one faulted signature alongside one valid one (Genêt, 2023). It falls to between and once thirty-two signatures under that key are available, which is the trade the original attack already offered.
SLH-DSA’s randomized index derivation does not by itself stop the attack. The hedged variant removes the simplest deterministic precondition: signing the same message twice no longer intentionally reuses one hypertree path, which makes the original deterministic attack path harder to stage. It is not a complete countermeasure. Genêt adapts the attack to SPHINCS+ with randomized signing, shows the faulty signatures can still verify (so the forgery is stealthy), and shows caching-based WOTS+ countermeasures can be bypassed with a feasible number of faulted queries (Genêt, 2023).
Both attacks above assume the adversary can reach the device. Boy, Purnal, Pätschke, Wilke, and Eisenbarth remove that assumption. Their SLasH-DSA paper, published in the proceedings of the second Microarchitecture Security Conference (uASC 2026) on 3 February 2026, stages the grafting attack entirely in software: Rowhammer-induced bit flips corrupt the signer’s internal state (Boy et al., 2026). The demonstration ran against OpenSSL 3.5.1 on a commodity desktop with a single DDR4-2133 DIMM, and the authors leave the attack’s effect on newer memory and its Rowhammer defences open. They report end-to-end universal forgery for SHAKE-128f in deterministic mode after about one hour of hammering and 151 seconds of post-processing, and for SHA2-128s and SHAKE-192f in randomized mode after about eight hours. Most of the remaining parameter sets get complexity estimates, and three set-and-mode combinations yielded too few faulted signatures to attack at all.
Two limits are worth stating plainly. This is an implementation-fault result, not a break of SLH-DSA’s EUF-CMA claim. OpenSSL’s answer to the disclosure was that fault attacks sit outside its threat model. And the forgeries under randomized signing are what the paragraph above predicts: hedging raised the cost of staging the fault from about one hour to about eight, and did not remove the attack. What changes is who the attacker has to be, from someone holding the device to someone running code on the same machine.
FIPS 205 makes the hedged variant the default and directs implementers to protect SLH-DSA against side-channel and fault attacks (National Institute of Standards and Technology, 2024). The practical lesson is stronger than hedging alone: pair the hedged variant with fault-detection redundancy or recomputation around the tree computations. Verify-after-sign helps only if it recomputes and compares the whole hypertree, not a single WOTS+ public key. Hedging is necessary hardening, not a full fault-attack defence (Genêt, 2023).
Caching is the redundancy that avoids recomputing the top layers at all, and the bypass above does not rule it out. Genêt’s bypass targets fixed-size branch caching, where cache misses force WOTS+ recomputation anyway. Caching whole layers is more effective the more layers it holds, and the binding cost is memory (Boy et al., 2026; Genêt, 2023). Azouaoui, Schneider, and Verbakel (preprint, April 2026) shrink that cost. Instead of storing each cached WOTS+ signature, they store a -byte truncated hash of it and recompute-and-compare at signing time (Azouaoui et al., 2026). That trades certain detection for a probability tunable in , and buys back enough memory to cache more layers, outperforming standard caching below roughly 256 kB of caching memory. Note the scope: this targets physical fault models on constrained devices, not the software-only threat above.
Security comparison across Part III
Section titled “Security comparison across Part III”header = f"{'scheme':<24} {'pre_cl':>7} {'pre_qu':>7} {'coll_cl':>8} {'coll_qu':>8} {'state':>10}"print(header)rows = [ ("Lamport+Merkle (n=32)", 256, 128, 128, 85, "stateful"), ("WOTS+/XMSS (n=32)", 256, 128, 128, 85, "stateful"), ("SLH-DSA-128s", 128, 64, 64, 42, "stateless"), ("SLH-DSA-192s", 192, 96, 96, 64, "stateless"), ("SLH-DSA-256s", 256, 128, 128, 85, "stateless"),]for nm, pc, pq, cc, cq, st in rows: print(f"{nm:<24} {pc:>7} {pq:>7} {cc:>8} {cq:>8} {st:>10}")# ==> scheme pre_cl pre_qu coll_cl coll_qu state# ==> Lamport+Merkle (n=32) 256 128 128 85 stateful# ==> WOTS+/XMSS (n=32) 256 128 128 85 stateful# ==> SLH-DSA-128s 128 64 64 42 stateless# ==> SLH-DSA-192s 192 96 96 64 stateless# ==> SLH-DSA-256s 256 128 128 85 statelessLamport+Merkle and WOTS+/XMSS use bytes (256 bits) and are stateful. Their preimage security is 256 bits classical and 128 bits quantum. SLH-DSA trades smaller values (as low as 16 bytes for category 1) and larger signatures for statelessness.
The quantum column is uniformly half the classical preimage column (Grover’s square-root speedup). This is simpler than the lattice case, where the gap between classical and quantum sieving costs is narrower and depends on the specific sieving algorithm. In the generic query model, Grover search halves the preimage-security exponent. No sub-exponential quantum algorithm is known for hash inversion.
Collision resistance is not the bottleneck. The ADRS construction ensures that collision attacks on internal nodes require second-preimage (not free collision), and second-preimage resistance matches preimage resistance at classical and quantum. In the simplified generic-attack model used in this chapter, the dominant category intuition is quantum preimage cost. The full SLH-DSA category claim also depends on the FORS few-time analysis, the hypertree position space, signing-query limits, and the concrete parameter search (Aumasson et al., 2020, sec. 7.1.2).
Where Part III ends and Part IV picks up
Section titled “Where Part III ends and Part IV picks up”Part III built one family end to end and then attacked it. Chapter 14 started from Lamport’s one-time signature. Chapter 15 reached WOTS+ and XMSS, trading chain length against signature size, and then paid the state-management cost. Chapter 16 removed the state with FORS and a hypertree. Chapter 17 assembled the result as FIPS 205’s SLH-DSA, and this chapter priced the attacks against it. Every construction in those five chapters rests on hash-function properties and on nothing algebraic: the preimage resistance this chapter priced, plus the pseudorandom-function, multi-target second-preimage, and interleaved-target-subset-resilience properties named at the top of the chapter. There is no number-theoretic structure for an adversary to exploit, which is why the quantum column above is a clean square root rather than an open question.
Part IV asks what other assumptions buy. Chapter 19 introduces coding theory and the decoding problem behind it: easy with the code’s structure, exponential without it. Chapter 20 builds McEliece, proposed in 1978, the same year as RSA, and unbroken in its binary-Goppa line at suitable parameters ever since. Chapter 21 builds HQC, which NIST selected in March 2025 as an additional code-based KEM. The two spend the same budget in opposite directions: McEliece takes a 261 KB public key in exchange for a 96-byte ciphertext. Chapters 22 and 23 turn to isogenies, first the mathematics and then SQIsign, whose round-3 specification pairs an 83-byte public key with a 200-byte signature. Chapter 24 closes the survey with multivariate signatures. One warning crosses the boundary: the coding chapters, 19 through 21, write for the code length, where Part III wrote it for the hash output length, and Chapter 24 resets it again to the total number of variables in a multivariate system.
Read the two Parts as ends of a spectrum rather than as alternatives. Part III offers the most conservative assumption in the book at the largest signature sizes. Part IV offers sizes that hash-based constructions cannot reach, on assumptions with narrower deployment or shorter cryptanalytic exposure. Neither Part settles what to deploy. Part V does that work, taking SLH-DSA alongside ML-KEM and ML-DSA as given primitives and fitting them into inventories, protocols, and migration programs. The hash-based thread then resurfaces twice: Chapter 32 builds Merkle commitments as one of four commitment families for the zero-knowledge systems in Part VI, and Chapter 37 weighs SLH-DSA-128s against ML-DSA-65 for Layer-1 transaction signatures in Part VII.
Exercises
Section titled “Exercises”-
Multi-target advantage at custom parameters. Consider a hypothetical SLH-DSA-like scheme with bytes, , (), , and . Compute , , , the FORS target count, the WOTS+ target count, the total multi-target advantage in bits, and the effective preimage security without ADRS. Would this parameter set still clear a 128-bit classical preimage-query target without ADRS, and what would a NIST category verdict need beyond that arithmetic?
-
FORS forgery threshold. For SLH-DSA-SHA2-128f (, ), compute the number of FORS-instance reuses at which the forgery probability exceeds . Compare against SLH-DSA-SHA2-128s (, ). Which parameter set tolerates more reuses before the threshold, and by how much?
-
BHT collision and NIST categories. The BHT quantum collision-finding algorithm costs quantum evaluations for an -bit hash. For , this is , well below the idealized Grover query count for category 1. Explain in one paragraph why NIST does not reduce the category 1 floor to . Your answer should address the quantum-memory (QRAM) model, circuit depth, and gate complexity.
-
Verify-after-sign cost. A WOTS+ implementation at and signs a message, then verifies the signature by hashing each chain value forward to the public key endpoint. If the message digits are , signing costs hash evaluations and verification costs evaluations. Show that verify-after-sign always costs exactly hash evaluations regardless of the message. Explain why this makes the combined sign-then-verify hash-call count constant with respect to the message. Note why a constant hash-call count is not full constant-time on its own: the implementation must also avoid releasing intermediate values or timing and must do the selection and comparison without data-dependent leakage.
Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 18. A separate track, for rebuilding rather than reading. The package exercises/ch18-hash-cryptanalysis implements every number this chapter prints, because the chapter prints it. What it stubs is the five results stated in prose and never coded: the approximation to FORS coverage, the birthday estimate for repeated positions, the quantum form of the multi-target reduction, and Exercise 4’s two component costs. Run PQC_IMPL=exercises pytest tests/ch18 to grade your version against the suite that proves the reference one.
References
Section titled “References”Last updated: