In short

A hash-based signature uses one and only one cryptographic assumption: that the underlying hash function (SHA-256 or SHAKE-256) behaves like a random function — second-preimage resistant and collision-resistant. No factoring, no discrete log, no lattice hardness, no elliptic curves. The construction starts with Lamport's one-time signature (1979): the public key is the hashes of a bunch of random secrets, signing reveals half of them. You can only sign once per key — revealing twice leaks the private key. Merkle's tree trick (1979) stitches 2^h one-time keys into a single binary tree and publishes only the root, letting you sign 2^h messages from one small public key. SPHINCS+ (NIST FIPS 205, SLH-DSA) is the stateless industrial form: a tree of Merkle trees, with random leaf selection and a "few-time" signature at the bottom, producing signatures of 8–50 kB and verification times of milliseconds. The upside is unmatched: if SHA-256 survives, SPHINCS+ survives — no extra conjecture. The downside is size and speed. Use it where the secrecy horizon is measured in decades: government signatures, long-lived firmware, CCA root-of-trust ceremonies. India's CERT-In recommends SPHINCS+ for the most conservative national-infrastructure use cases.

Imagine you are about to sign a document. The cryptographic scheme in your HSM needs one of several possible "hard problems" to rest on. Factoring? Shor breaks it. Elliptic-curve discrete log? Shor again. Lattices? Probably safe — but the hardness is a conjecture supported by about fifteen years of serious cryptanalysis, not sixty. Codes? Similarly. The one thing every cryptographer deeply believes will remain hard — hard even on a quantum computer, hard even if tomorrow's mathematician walks in with a new idea — is inverting a cryptographic hash function. SHA-256 and SHAKE-256 are studied by everyone who does cryptanalysis, used by every blockchain and every code-signing chain on Earth, and every attempted attack against them has so far moved the goalposts by only a few bits.

So here is the question: can you build a digital signature scheme whose security rests on nothing but the hardness of inverting a hash? You cannot use modular exponentiation, cannot use elliptic curves, cannot use lattice problems. Only hashes. This chapter answers that question. The answer is yes — and the construction was first described by Leslie Lamport and Ralph Merkle, independently, in 1979. It was ignored for forty years because RSA was smaller and faster; now that RSA is ending, hash-based signatures have returned as the conservative anchor of the post-quantum portfolio. NIST standardised one of them, SPHINCS+, as FIPS 205 (SLH-DSA) in August 2024. Your HSM vendor is adding it to the firmware right now.

The setup: one hash function, one idea

A cryptographic hash function H takes a bit-string of any length and produces a fixed-length digest — say H : \{0,1\}^* \to \{0,1\}^{256}. You need two properties:

SHA-256 and SHAKE-256 are the standard choices. On a quantum computer, Grover's algorithm halves the preimage security to \sim 2^{128}, and the BHT algorithm drops collision-finding to \sim 2^{85}. Parameter sets account for this — SPHINCS+-128 targets classical-128/quantum-96 security, SPHINCS+-256 targets classical-256/quantum-128.

Everything below is built from hashing. No other primitive.

Lamport's one-time signature

Leslie Lamport's 1979 construction is the starting point. You can sign one message per key. The mechanics are embarrassingly simple.

Key generation (for a message of n bits; for SHA-256 take n = 256).

  1. For each bit position i \in \{1, \ldots, n\}, sample two random 256-bit secrets: x_i^0 and x_i^1. That is 2n random values in total.
  2. The private key is the full collection \{x_i^0, x_i^1\}_{i=1}^n.
  3. The public key is the hashes: \{H(x_i^0), H(x_i^1)\}_{i=1}^n.

Signing a message m. Let m = m_1 m_2 \ldots m_n be the bits of the message (or, more commonly, of H(m) — signing the hash of the message means the scheme works for messages of any length).

For each bit position i, reveal x_i^{m_i}. The signature is \sigma = (x_1^{m_1}, x_2^{m_2}, \ldots, x_n^{m_n}) — exactly n of the 2n secrets.

Verification. For each bit i, compute H(x_i^{m_i}) (from the signature) and check that it equals the public-key entry that was supposed to correspond to bit m_i at position i. If all n checks pass, accept.

Lamport's one-time signatureA diagram with three rows. Top row shows the private key as pairs of random values x i zero and x i one for i equals 1 to 4. Middle row shows the public key as the hashes of each value. Bottom row shows signing a 4 bit message 1 0 1 1 by revealing x 1 one, x 2 zero, x 3 one, x 4 one. Arrows indicate which private values are revealed.Lamport one-time signature (4-bit message, toy size)Private key (8 secrets)x₁⁰x₁¹x₂⁰x₂¹x₃⁰x₃¹x₄⁰x₄¹random 256-bitPublic key (8 hashes)H(x₁⁰)H(x₁¹)H(x₂⁰)H(x₂¹)H(x₃⁰)H(x₃¹)H(x₄⁰)H(x₄¹)publishedSigning m = 1011 (reveal 4 of 8)x₁¹x₂⁰x₃¹x₄¹verifier checks H(revealed) matches the correct public-key slot for each message bit
Lamport's one-time signature at 4-bit message size. The private key is 8 random secrets; the public key is the 8 hashes. To sign the message $1011$, reveal $x_1^1, x_2^0, x_3^1, x_4^1$ — one secret per bit, chosen by the bit's value. The verifier re-hashes each revealed secret and confirms it matches the public-key slot. Revealing *any* $x_i^b$ teaches the attacker a hash preimage — so signing a second message (with any differing bit) instantly leaks more preimages and breaks security.

Why Lamport works: to forge a signature on a new message m', the attacker must produce x_i^{m'_i} for every bit position i. For positions where m_i = m'_i, the secret has been revealed — so no work. For positions where they differ, the attacker needs a preimage of H(x_i^{m'_i}) — exactly 2^{256} work for SHA-256, or 2^{128} with Grover. As long as m' differs in at least one bit from m, forgery requires solving at least one preimage problem.

The catch: "one-time" is literal. Sign two different messages and, at every position where the bits disagree, the attacker learns both x_i^0 and x_i^1. Across enough different messages, the entire private key leaks. You must use each Lamport key pair exactly once.

Winternitz — reducing Lamport's key size

A Lamport signature has n (256) revealed values, and the public key is 2n hashes. Winternitz (1979, published anonymously in the same Merkle paper) compressed this. Instead of revealing one of two secrets per bit, hash a single secret many times and reveal an intermediate hash whose "depth" encodes a chunk of the message. Groups of w message bits collapse to one chunk; the signature size drops by roughly a factor of w / \log_2 w, at the cost of proportionally more hashing at sign and verify time. WOTS+ (the Winternitz variant used inside SPHINCS+ at the bottom of each Merkle leaf) is a more tightly-tuned version of the same idea, with a parameter w = 16 being standard.

Merkle's trick — 2^h messages from one public key

Lamport's one-time limit looks fatal for practice. You want to sign thousands of messages from one long-lived key pair — not run a fresh ceremony per signature. Ralph Merkle's 1979 paper solved this.

Build a binary tree of Lamport public keys. Generate 2^h independent Lamport key pairs. Each key pair's public key is a list of 2n hashes; hash the whole list into a single 256-bit value \text{pk}_i for i = 0, \ldots, 2^h - 1. These 2^h hashes are the leaves of a Merkle tree.

The internal nodes are pairwise hashes. For every two siblings at the bottom, the parent node is the hash of the two children concatenated. Recurse up: parent of two internal siblings is the hash of their concatenation. At the top, a single 256-bit value — the Merkle root — is the long-lived public key.

A height-3 Merkle treeA binary tree with 8 leaves at the bottom labelled pk 0 through pk 7, each a hashed Lamport public key. Four internal nodes at height 1, two at height 2, one root at top. The authentication path for leaf pk 3 is highlighted: its sibling pk 2, its uncle at height 2 which is the hash of pk 0 pk 1, and the root's other child at height 3. The signature is the Lamport signature at leaf 3 plus these three sibling hashes.Merkle tree: 8 Lamport keys collapse to one rootrooth₂,₀h₂,₁h₁,₀h₁,₁h₁,₂h₁,₃pk₀pk₁pk₂pk₃pk₄pk₅pk₆pk₇to sign from leaf pk₃: reveal sig, pk₃, sibling pk₂, uncle h₁,₂, uncle h₂,₁
A Merkle tree of height 3, storing 8 Lamport one-time public keys. The public key is the root only — 32 bytes. To produce a signature from leaf $\text{pk}_3$, transmit: (a) the Lamport signature on the message, (b) the leaf value $\text{pk}_3$, and (c) the **authentication path** — the sibling at each level on the way up: $\text{pk}_2$, $h_{1,2}$, $h_{2,1}$. The verifier hashes $\text{pk}_3$ together with each sibling, upward, and checks the final hash equals the published root.

Signing. To sign message m with leaf i:

  1. Use the ith Lamport one-time key to produce a Lamport signature \sigma_i of m.
  2. Emit the tuple (i, \sigma_i, \text{pk}_i, \text{auth-path}_i) where \text{auth-path}_i is the list of h sibling hashes on the way from leaf i to the root.
  3. Mark leaf i as used. Never use it again.

Verifying. The verifier:

  1. Recomputes the Lamport-public-key hash from \sigma_i and m and checks it equals \text{pk}_i.
  2. Hashes up the tree using the authentication path: at each level, combines the current value with the supplied sibling, in the correct order. At the top, compares against the published root.

Why one root covers 2^h signatures: each Merkle-tree path is a witness that a particular Lamport public key belongs in slot i. Because the attacker cannot forge a hash collision, they cannot fake a leaf's position without inventing a new Lamport public key whose hash collides with one the signer's tree. So the public key (the root) commits the signer to all 2^h Lamport keys at once, and any valid signature traces back to one of them.

The stateful problem

Merkle trees give you 2^h signatures from one public key — but you must keep track of which leaves you have already used. That is state. And state is a nightmare in production.

XMSS and LMS are the stateful hash-based signature schemes standardised in NIST SP 800-208 (2020). They work beautifully in highly controlled environments — code-signing keys on a single air-gapped workstation, firmware roots where the signer is a single ceremony — but they come with a state-management burden so severe that the standard literally warns against using them in any setting where duplicate HSMs or rollbacks are possible.

The stateful signing hazardA timeline showing two HSMs both with state leaf counter equals 42. Both receive a signing request for different messages. Both sign using leaf 42. Two different signatures are emitted using the same one time key. An attacker combines them and reconstructs the full Lamport private key at leaf 42. A warning triangle labels this scenario a catastrophic state collision.Why stateful hash-signatures are dangerous in replicated HSMsHSM Aleaf = 42signs m₁ → σ₁reveals x₁ values for m₁HSM B (replica)leaf = 42 (same!)signs m₂ ≠ m₁ → σ₂reveals x₂ values for m₂Attacker collects σ₁, σ₂same leaf → privatekey at leaf 42 leaksforges arbitrary messagesstate collision is a single-failure catastrophic risk — avoid entirely in production
The replicated-HSM state-collision hazard. Two HSMs independently signing with the same leaf index on different messages leak the private key. This is why XMSS and LMS are restricted by NIST SP 800-208 to environments where replication and rollback are impossible, and why the industry pushed NIST to standardise a *stateless* hash-based signature (SPHINCS+).

SPHINCS+ — statelessness by construction

SPHINCS+ solves the state problem. The core idea: if there are enough leaves, pick a leaf uniformly at random — the probability of colliding with any past choice is vanishing even after a large number of signatures. No state needed, no counters, no rollbacks to worry about.

But "enough leaves" is a lot. To make the collision probability negligible over 2^{64} signatures, you want a tree with roughly 2^{64} leaves. A Merkle tree of that height is impractical to build — computing the root would require 2^{64} Lamport key generations, each hashing a few thousand bytes. SPHINCS+ handles this by virtualising the tree.

The hypertree. Instead of one height-64 Merkle tree, SPHINCS+ stacks several smaller Merkle trees — say twelve layers of height 5 each, giving 2^{60} effective leaves. Each layer's "leaf" is actually the root of another Merkle tree (a WOTS+ one-time signature tree), and each root is signed by a Merkle signature at the layer above. Only the top-layer root of the topmost tree is the public key; the rest of the hypertree is generated on-demand from a seed.

The FORS few-time signature at the bottom. The very bottom layer does not use a WOTS+ one-time signature — it uses a FORS (Forest of Random Subsets) scheme, which is a few-times signature tolerant to more than one signing at the same FORS instance. This lets SPHINCS+ handle the birthday-style risk that random leaf selection will pick the same bottom-tree twice: the FORS at that instance can absorb a few re-uses without leaking the key.

SPHINCS+ hypertree structureA simplified diagram showing three layers. The top is a Merkle tree whose leaves are WOTS plus public keys. Each WOTS plus public key signs the root of a Merkle tree below. The bottom layer shows FORS few time signatures at the leaves. An arrow indicates that to sign a message a random leaf is chosen and a chain of authentication paths is revealed up to the top root.SPHINCS+ hypertree (simplified to 3 layers)public key = top rootLayer d−1wotswotswots (used)wotswotseach WOTS+ pk is the root of a lower Merkle tree — 2^h leaves per layerMerkle root of lower treeLayer 1FORSFORSFORSFORSFORSRandom leaf chosen from message hash → FORS signs → WOTS+ chain signs upward to top rootLayer 0signature = FORS sig + d-layer WOTS+ chain with authentication paths — 8 to 50 kB
SPHINCS+ hypertree, simplified. Upper layers are Merkle trees of WOTS+ one-time signatures; the bottom layer is FORS few-time signatures. A random leaf is chosen pseudorandomly from the message. The signature is the FORS signature at the bottom plus the WOTS+ signatures and authentication paths at every layer up to the top root — typically 8 kB for "small" parameters, 50 kB for "fast".

How a signature is produced.

  1. Derive a pseudorandom leaf index from H(m, r) where r is a per-signature randomiser.
  2. At the bottom layer, at that leaf, produce a FORS signature of H(m).
  3. The FORS public key at that leaf is the value you need to authenticate up the tree. Produce a WOTS+ signature of it at the layer above (this consumes the WOTS+ one-time key at the appropriate position).
  4. Produce a Merkle authentication path from that WOTS+ position to the Merkle root of its layer.
  5. Repeat steps 3–4 for each of the d layers. The final authentication path ends at the top root — which is the public key.

The signature is the concatenation of all these pieces. Verification reverses the process: check the FORS signature, climb upward through the hypertree, and confirm the final root matches the public key.

How "stateless" is this, really? The per-signature randomiser r is chosen freshly (per signature) and mixed into the leaf-index derivation. The probability that two signatures use the same bottom-leaf FORS instance in the same problematic way is exponentially small — small enough that you can sign 2^{64} messages under one key with negligible total failure probability. In practice you will never come close to that limit.

Parameter sets and sizes

SPHINCS+ (FIPS 205 SLH-DSA) is parameterised in two dimensions: security level and small-vs-fast (trades signature size for signing speed).

Parameter set Security Public key Signature Sign time Verify time
SLH-DSA-128s ("small") 128 bits 32 B 7856 B ~ 300 ms ~ 0.4 ms
SLH-DSA-128f ("fast") 128 bits 32 B 17088 B ~ 10 ms ~ 0.6 ms
SLH-DSA-192s 192 bits 48 B 16224 B ~ 600 ms ~ 0.6 ms
SLH-DSA-192f 192 bits 48 B 35664 B ~ 15 ms ~ 0.9 ms
SLH-DSA-256s 256 bits 64 B 29792 B ~ 600 ms ~ 0.7 ms
SLH-DSA-256f 256 bits 64 B 49856 B ~ 25 ms ~ 1.2 ms

Timings on a modern x86 core; much slower on embedded hardware.

Two observations are worth sitting with. First, the public key is tiny: 32–64 bytes, smaller than any other PQC scheme. Second, the signature is enormous: kilobytes at minimum, tens of kilobytes at worst. SPHINCS+ is brilliant for any use case where public keys must be distributed widely (firmware verification, certificate transparency logs) but signatures are rare; it is a poor fit for high-throughput signing.

Worked example — a Lamport one-time signature end to end

Example 1: sign "HI" (16 bits) with a Lamport key

Setup. Take a 16-bit message "HI" encoded as ASCII — bytes 0x48, 0x49, bits

m = 0100\,1000\;\;0100\,1001.

Generate a Lamport key pair with toy 4-bit "hashes" (in practice 256-bit; shrunk here for readability). The private key consists of 32 random 4-bit secrets; the public key consists of their 32 toy-hashed values.

Step 1 — the private key (16 bit-positions × 2 per position). Pick (pseudo-)randomly:

Position i x_i^0 x_i^1
1 5 12
2 3 9
3 7 1
4 14 11
5 2 8
6 6 13
7 15 4
8 10 0
9 11 6
10 4 14
11 9 2
12 8 5
13 13 7
14 0 10
15 1 3
16 12 15

(Real Lamport uses 256-bit secrets and SHA-256; the idea is identical.)

Step 2 — the public key. Apply a toy hash H(x) = (7x + 3) \bmod 16 to each secret:

Position i H(x_i^0) H(x_i^1)
1 H(5)=6 H(12)=7
2 H(3)=8 H(9)=2
3 H(7)=4 H(1)=10
4 H(14)=5 H(11)=0

…and so on for positions 5–16. The full table of 32 hashed values is the published public key.

Step 3 — signing "HI". Bits of m: position 1 is 0, position 2 is 1, position 3 is 0, position 4 is 0, position 5 is 1, position 6 is 0, position 7 is 0, position 8 is 0, position 9 is 0, position 10 is 1, position 11 is 0, position 12 is 0, position 13 is 1, position 14 is 0, position 15 is 0, position 16 is 1. For each i, reveal x_i^{m_i}:

\sigma = (5,\;9,\;7,\;14,\;8,\;6,\;15,\;10,\;11,\;14,\;9,\;8,\;7,\;0,\;1,\;15).

Send \sigma along with m to the verifier.

Step 4 — verification. For each i, hash the revealed value and check it equals the public-key entry at position i, column m_i.

  • Position 1: H(5) = 6 — matches public key's H(x_1^0) = 6. ✓
  • Position 2: H(9) = 2 — matches public key's H(x_2^1) = 2. ✓
  • Position 3: H(7) = 4 — matches H(x_3^0) = 4. ✓
  • (…all 16 positions check similarly…)

If any single check fails, reject. Otherwise, accept.

Step 5 — why signing twice leaks the key. Suppose you now try to sign a second message m' that differs from m at position 1 (so m'_1 = 1). You have to reveal x_1^1 = 12 — but from the first signature you had already revealed x_1^0 = 5. The verifier now sees both x_1^0 and x_1^1 — for position 1, the attacker now holds everything. Repeat this for more differing bits and the attacker will recover enough (x_i^0, x_i^1) pairs to forge arbitrary new messages. Why forging works: a forger constructs any target message m' and, for every bit position, the attacker already knows both possible secrets — they pick the one matching m'_i and a valid signature pops out. The one-time restriction is not a gentle recommendation; it is structural.

Result. The Lamport signature is correct, uses 16 hash evaluations per signature at 256-bit security (tiny compute), and is secure under exactly one assumption — that H is preimage-resistant. The mechanism for forgery from two signatures is constructive: the attacker does not even need to solve anything, they just collect the revealed secrets.

Lamport signing and verification flowA three-box diagram. Left box labelled signer shows the private key as pairs and the signing rule to reveal one per bit. Middle box shows the transmitted signature as the sequence of revealed secrets. Right box labelled verifier shows hashing each secret and checking against the public key slot for the message bit. Below, a warning box labels the two message catastrophe.Lamport signing and verificationSignerfor bit i = 1..n:reveal xᵢ^(mᵢ)(n of 2n secrets)Signature σ(x₁^(m₁), ..., xₙ^(mₙ))= n × 256 bitstransmitted with mVerifierfor each i:check H(σᵢ) = pkᵢ^(mᵢ)accept if all passOne-time only! Signing twice leaks both xᵢ⁰ and xᵢ¹ at every differing bit.
The Lamport flow. The signer reveals $n$ of the $2n$ secret values, chosen by the message bits. The verifier hashes each one and matches it against the correct slot of the public key. Simple, hash-only, one-time.

Worked example — a height-3 Merkle signature

Example 2: Merkle-authenticate one Lamport key out of 8

Setup. Build a tree of 8 Lamport one-time keys. Use a toy hash H(a, b) = (a + 3b) \bmod 256 for internal-node combination, and let the 8 leaf Lamport public keys (each would normally be a 256-bit hash) be the toy 8-bit values

\text{pk}_0=17,\;\text{pk}_1=41,\;\text{pk}_2=83,\;\text{pk}_3=119,\;\text{pk}_4=61,\;\text{pk}_5=97,\;\text{pk}_6=5,\;\text{pk}_7=149.

Sign a message using leaf 3 and compute the authentication path.

Step 1 — build the tree.

Level 1 (4 nodes, each H of a sibling pair):

h_{1,0} = H(17, 41) = (17 + 123) \bmod 256 = 140.
h_{1,1} = H(83, 119) = (83 + 357) \bmod 256 = 440 \bmod 256 = 184.
h_{1,2} = H(61, 97) = (61 + 291) \bmod 256 = 352 \bmod 256 = 96.
h_{1,3} = H(5, 149) = (5 + 447) \bmod 256 = 452 \bmod 256 = 196.

Level 2:

h_{2,0} = H(140, 184) = (140 + 552) \bmod 256 = 692 \bmod 256 = 180.
h_{2,1} = H(96, 196) = (96 + 588) \bmod 256 = 684 \bmod 256 = 172.

Level 3 (root):

\text{root} = H(180, 172) = (180 + 516) \bmod 256 = 696 \bmod 256 = 184.

Publish root = 184.

Step 2 — sign from leaf 3. Produce the Lamport signature \sigma_3 of m using the leaf-3 Lamport key (from Example 1's mechanics). Emit:

  • \sigma_3 (the Lamport signature)
  • \text{pk}_3 = 119 (the Lamport public key at leaf 3)
  • Authentication path: (\text{pk}_2, h_{1,0}, h_{2,1}) = (83, 140, 172).

Why those three sibling values: walking up from leaf 3, the sibling at level 0 is leaf 2 (\text{pk}_2); the sibling at level 1 is h_{1,0} (the parent subtree for leaves 0–1); the sibling at level 2 is h_{2,1} (the parent subtree for leaves 4–7). Combining leaf 3 with these three siblings reconstructs the root.

Step 3 — the verifier reconstructs.

  • Check \sigma_3 is a valid Lamport signature of m producing Lamport public key \text{pk}_3 = 119. ✓ (mechanics of Example 1)
  • Hash leaf-3 and its sibling (left-right order): H(\text{pk}_2, \text{pk}_3) = H(83, 119) = 184. ✓ (matches our h_{1,1})
  • Hash up: H(h_{1,0}, 184) = H(140, 184) = 180. ✓ (matches h_{2,0})
  • Hash up: H(180, h_{2,1}) = H(180, 172) = 184. ✓ (matches the published root)

All checks pass. Accept.

Step 4 — what if the attacker tries a different leaf? Forging a signature from leaf 5 would require producing \text{pk}_5 and an authentication path whose final hash equals the published root. The attacker can guess any \text{pk}' but the hash H(\text{pk}'_5, \ldots) winding up to 184 is a hash collision problem — 2^{128} work on SHA-256 (or 2^{85} with BHT on a quantum computer). Collision-resistance does the heavy lifting.

Step 5 — the total signature size. In real SPHINCS+ this tree is one layer of the hypertree; the full signature bundles a FORS signature plus d layers of (Lamport-style WOTS+ signature + authentication path). A typical SPHINCS+-128s signature is 7.8 kB — mostly the WOTS+ and authentication-path data for a 12-level hypertree.

Result. The Merkle tree lets you sign 2^h messages from one 32-byte public key (root), at the cost of carrying h sibling hashes per signature. Security rests on the collision-resistance of H — no number-theoretic or lattice assumption. The construction is robust, conservative, and correct by the same arithmetic you would do on paper.

Post-quantum signature size comparisonA horizontal bar chart comparing five schemes ECDSA P256 at 72 bytes, RSA 2048 at 256, Falcon 512 at 666, Dilithium 65 at 3293, SPHINCS plus 128s at 7856 and SPHINCS plus 256f at 49856. Each bar length is proportional to signature size. A highlighted bar colour separates hash-based schemes from lattice schemes. Annotations note the security trade-offs.Signature size (bytes): hash-based vs lattice vs classicalECDSA-P256 (72 B)RSA-2048 (256 B)Falcon-512 (666 B)Dilithium-65 (3293 B)SPHINCS+-128s (7856 B)SPHINCS+-192s (16.2 kB)SPHINCS+-256f (49.9 kB)SPHINCS+ trades size for minimal assumptions — hash-only security(chart is to scale; classical at left, lattice in middle, hash-based at right)
Signature sizes across the post-quantum zoo. SPHINCS+ signatures are 3–15× Dilithium's — uncomfortable for high-throughput signing, fine for occasional root-ceremony signatures, code signing, and DigiLocker-style document signatures that are produced rarely and verified often.

When to pick SPHINCS+ over Dilithium

SPHINCS+ is expensive. You pay in bytes (signatures 3–15× larger than Dilithium's) and in time (signing 10–100× slower). What you get is a security argument that depends on nothing but the hash function. Three situations where that is worth the cost:

Government long-lived signatures. A defence memo signed today must be verifiable in 2075. If Dilithium's lattice assumption is quietly broken in 2045, every Dilithium-signed document from 2026 becomes forge-able retroactively. SPHINCS+'s security depends only on the hash — and if SHA-256 falls, so does every other cryptographic system on Earth at the same moment. Correlated failure is tolerable; independent failure is not.

Certificate root of trust. The CCA (Controller of Certifying Authorities) root certificate in India is the anchor under which all DigiLocker, Income Tax, and e-Court signatures chain. The root is re-issued every 20 years; it signs a handful of intermediate CAs; verification happens billions of times. A SPHINCS+ signature at the root — big, slow, but cryptographically conservative — is exactly the right choice.

Firmware code-signing. A router firmware image is signed once and verified by millions of devices over a decade. The signer is a single HSM at the vendor's secure facility; the verifier is a small embedded chip. Signing cost (100 ms) is negligible amortised over the release; signature size (8 kB) is negligible next to the firmware image itself (100 MB); cryptographic conservatism matters because you cannot easily re-sign a decade of deployed firmware if an assumption breaks.

Where not to use SPHINCS+. Anywhere high-throughput signing matters. UPI transactions, TLS certificates, IoT telemetry — the signing cost and signature size eat the latency and bandwidth budget. Use Dilithium or Falcon here; keep SPHINCS+ for the slow, rare, deeply-trusted signatures.

Indian deployment context

CERT-In's draft 2026 PQC transition guideline calls out SPHINCS+ specifically for:

IIT Kharagpur's SETS lab (Mukhopadhyay group) has contributed side-channel-resistant FORS and WOTS+ hardware implementations to the open CRYSTALS/SPHINCS+ reference codebase. IIT Madras and IISc have published formal verification passes on the SHAKE-based SPHINCS+ parameter sets. CDAC's HSM product line is scheduled to ship SLH-DSA support in its 2027 firmware refresh, positioned as the "conservative" signature option alongside ML-DSA (Dilithium) as the default.

Common confusions

Going deeper

If you understand that Lamport's one-time signature reveals half the random secrets, that Merkle stitches 2^h one-time keys into one tree whose root is the public key, that stateful XMSS/LMS require the signer to track a counter and SPHINCS+ eliminates the state with random leaf selection over a hypertree with FORS at the bottom — you have chapter 160. The material below is for readers who want the Winternitz-WOTS+ construction details, the XMSS tree-hashing algorithm, SPHINCS+'s formal parameter-set derivation, and the NIST SP 800-208 guidance on stateful hash-based signatures.

Winternitz one-time signatures (WOTS+)

WOTS+ compresses Lamport. Fix a parameter w (typically 16). Encode the message H(m) in base w as \ell_1 digits, plus a small number of checksum digits to prevent truncation attacks. For each digit, the private key holds one random 256-bit secret x_j, and the public key holds its w-th iterated hash H^{w-1}(x_j).

To sign a digit d_j, release H^{d_j}(x_j) — the secret hashed d_j times. The verifier then hashes w - 1 - d_j more times and checks the result equals the public-key entry.

Why the checksum matters: without it, the attacker could take a signature on digit value d and use it as a signature on any larger value d' > d by simply hashing d' - d more times. The checksum, added at the end of the message encoding, is the complementary sum \sum (w - 1 - d_j) — it decreases as the message digits increase, preventing the attacker from truncating a signature into a forgery.

WOTS+'s signature for a 256-bit message is \ell = \ell_1 + \ell_2 values of 256 bits each — for w = 16 that is \ell \approx 67, about a quarter of Lamport's n = 256. Hence smaller signatures at the cost of a w-factor in hashing work.

XMSS tree hashing and its stateful shadow

XMSS (Extended Merkle Signature Scheme) is Merkle-tree-of-WOTS+. The tree has 2^h leaves, each a WOTS+ public key. The signer keeps a counter i and signs the i-th message with the i-th leaf, then increments. To avoid leaking the private key through reuse, the counter must be durably stored and atomically incremented before each signing.

The operational nightmare. If the HSM has a master-slave replica for availability, both replicas must share the counter — but atomic cross-HSM coordination is expensive and fragile. If the HSM is restored from a backup, the backup's counter is behind; the first signing after restore reuses a leaf. Airbus has published incident reports on exactly this failure mode. NIST SP 800-208 permits XMSS and LMS but essentially prohibits them in any setting where state could be rolled back or replicated — a very narrow permission.

LMS — Leighton-Micali Signatures

LMS is a parallel stateful construction to XMSS, with slightly different parameter choices and a somewhat simpler WOTS+ variant. Both XMSS and LMS are NIST-approved; the IETF RFC 8391 (XMSS) and RFC 8554 (LMS) specify the formats. For code-signing use cases with a strictly single-HSM signer (no replication, no rollback), LMS is common — it was the signature used for the 2019 Linux Foundation secure boot-chain proof-of-concept.

SPHINCS+ parameter derivation

SPHINCS+'s core parameters:

The "s" (small) and "f" (fast) variants differ in h/d/\log_2 t: "s" takes fewer larger trees, so each FORS instance is expensive but few are touched per signature; "f" takes more smaller trees, so signing is faster but signatures are bigger.

Security proofs

In the quantum random-oracle model (where the adversary can query the hash function in superposition), SPHINCS+'s existential unforgeability reduces to:

  1. The preimage resistance of H (protects WOTS+ chains).
  2. The collision resistance of H (protects Merkle tree authentication).
  3. The second-preimage resistance of H (protects FORS few-time signatures).

All three are standard hash-function assumptions. No other assumption is made. The concrete reduction loses a small polynomial factor in the security parameter — meaning 128-bit-security SPHINCS+ gives ~125 bits of quantum security in the worst case.

Stateful hash-based signatures in India's public sector

NIST SP 800-208 explicitly permits stateful hash-based signatures for use cases with a single dedicated signer and no replication:

CERT-In's 2026 draft steers general deployments toward SPHINCS+ precisely because the state-management burden is unmanageable in a typical enterprise. The exception is listed for narrowly defined high-assurance ceremonies.

Where this leads next

References

  1. Ralph Merkle, A Certified Digital Signature (CRYPTO '89, with the 1979 construction revived) — merkle.com/papers/Certified1979.pdf.
  2. NIST, FIPS 205 — Stateless Hash-Based Digital Signature Standard (SLH-DSA)csrc.nist.gov/pubs/fips/205.
  3. NIST, SP 800-208 — Recommendation for Stateful Hash-Based Signature Schemes (XMSS, LMS)csrc.nist.gov/pubs/sp/800/208/final.
  4. Wikipedia, Lamport signature.
  5. Wikipedia, Hash-based cryptography.
  6. John Preskill, Lecture Notes on Quantum Computation, Chapter 8 (Cryptography)theory.caltech.edu/~preskill/ph229.