The construction of SHRINCS, a semi-stateful hash-based signature scheme
Originally published in pqc-research, where it is maintained. This page renders that document.
SHRINCS (Shrunken SPHINCS) is a cryptographic BIP draft posted to the bitcoin-dev mailing list on 27 August 2026. It demotes SLH-DSA, the hash-based signature scheme standardised in FIPS 205, to a fallback path, and puts a smaller stateful signature on the primary path. This note takes the scheme apart along the axes FIPS 205 uses for SLH-DSA: what is reused verbatim, what only changes parameters, what is newly constructed, and what the design gives up. The specification is at https://github.com/SHRINCS/shrincs-bip/blob/main/SHRINCS.md.
Status: first draft, marked "Do NOT use in production". Security proofs, test vectors and unit tests are all outstanding. A Rust implementation of the whole scheme, written to check the claims below, is at
AppliedPQC/shrincs-rs. The vocabulary is that of FIPS 205, and the section below fixes the terms before they are used.
Notation and terminology
The note uses the vocabulary of FIPS 205 throughout, together with two constructions the standard does not contain. The terms are given here in roughly the order they are needed.
| Term | Meaning |
|---|---|
| One-time signature, OTS | A scheme whose secret key is secure for a single message. A second signature under the same key reveals enough of the key that a third can be forged. WOTS+, WOTS-TW and WOTS+C are of this kind. |
| Few-time signature, FTS | A scheme that tolerates a small number of signatures under one key, with security degrading as that number grows. FORS is of this kind. |
| Winternitz chain | A sequence of iterated applications of a hash to a chain secret.
Publishing the d-th iterate encodes the index
d, and a verifier can advance the chain but not reverse
it. |
Winternitz parameter w |
The chain length, 16 throughout SHRINCS, so each chain carries
lg w = 4 bits of the digest. |
| Checksum chains | Extra chains encoding the complement of the sum of the message indexes, so that raising any message index forces the checksum index down, which would require inverting a chain. WOTS-TW appends three. |
| Constant-sum encoding | The alternative WOTS+C uses. No checksum is appended; instead only index vectors summing to a fixed constant are admitted, and the signer searches a counter until the digest yields one. Raising one index then requires lowering another, and the number of chain steps becomes fixed. Also called target-sum Winternitz. |
| Merkle tree | A binary tree whose leaves commit to the values being authenticated and whose internal nodes are hashes of their two children, so that the root commits to every leaf. |
| Authentication path | The sibling of each node along the path from a leaf to the root. With the leaf it determines the root, and it is what a signature carries to prove membership. |
| XMSS | A Merkle tree whose leaves are OTS public keys, evaluated under a tweakable hash at every node. Each signature consumes one leaf, which makes the scheme stateful. |
| Hypertree | A tree of XMSS trees, each layer signing the root of the layer below with one of its OTS keys. This reaches a large signature budget without any single tree of impractical height. |
| FORS | A few-time signature of k Merkle trees of height
a. The digest selects one leaf per tree, and the signature
reveals those leaves with their authentication paths. |
| Tweakable hash | A hash keyed by a public seed and by a position, so that the function applied at one position is independent of the function applied at any other. |
| ADRS | The address that encodes such a position. Giving every call a distinct ADRS is what makes those hashes independent. |
| Domain separation | Arranging that inputs playing different roles cannot collide, by making the function itself differ with the role. |
| Multi-target attack | A search for a preimage of any one of many published hashes at once. The advantage grows with the number of targets, which is why positions are separated and tree edges directed. |
| Stateful scheme | One whose signer must record which leaves have been used, since reuse breaks security. |
| Stateless scheme | One whose signer keeps no persistent record, at the cost of a larger signature. |
| Semi-stateful scheme | The arrangement SHRINCS introduces: one key pair carrying both, with the stateless component available whenever the state is not. |
| Signature budget | The number of signatures a key pair can produce before its leaves are exhausted. |
1. Relationship to SLH-DSA
| Layer | Content |
|---|---|
| Reused verbatim | The stateless path is SLH-DSA. slh_dsa_sign and
slh_dsa_verify correspond line for line to FIPS 205
Algorithms 22 and 24, differing only in that the signature randomiser is
supplied by the caller rather than generated internally. An SLH-DSA
implementation that accepts custom parameter sets needs only a thin
wrapper |
| Reparameterised | The stateless path uses a non-standard parameter set: the signature budget drops from 2⁶⁴ to 2⁴⁰, and the signature from 7,856 to 5,776 bytes |
| Newly constructed | The stateful path: FXMSS, a Merkle tree of variable shape, over WOTS+C, a constant-sum one-time signature. Neither derives from SPHINCS+ |
| Shared machinery | SHA-256 as the sole underlying hash; the ADRS tweakable-hash mechanism; all internal outputs truncated to 16 bytes |
SLH-DSA was designed to be stateless, because SP 800-208 judged XMSS and LMS "not suitable for general use". SHRINCS moves in the opposite direction: it reintroduces state in exchange for size, then uses a stateless fallback to reduce the consequence of losing that state from loss of funds to a larger signature.
2. Architecture
| Component | Structure | Path |
|---|---|---|
| FXMSS, a Merkle tree signature of variable shape | Hash chains over an arbitrarily shaped Merkle tree | Stateful (primary) |
| WOTS+C, the one-time signature FXMSS uses | Hash chains with constant-sum encoding | Stateful (primary) |
| SLH-DSA, the stateless fallback | Hypertree over FORS | Stateless (fallback) |
| WOTS-TW, the one-time signature inside SLH-DSA | Hash chains with a checksum | Stateless (fallback) |
| FORS, a few-time signature | Many small Merkle trees | Stateless (fallback) |
A signature from either path verifies. Signers use the stateful path routinely and fall back to the stateless path when state is lost, corrupted, or uncertain, as after a restore from a static backup.
Signing flow
Each path reduces to a root, and the two roots together form the public key. The two are not independent of one another; see the cross-binding in section 6.
Distinguishing the two paths
The first byte of a signature is the discriminator:
= 255(FXMSS_HEIGHT) selects the stateless path;0 ≤ x < 255selects the stateful path, and the byte is theleaf_heightof the leaf that signed.
255 is available as a tag because leaf_height = 255
corresponds to depth 0, and trees of depth 0 are forbidden from signing.
Length separates the two as well: the stateful maximum of 4,619 bytes is
below the fixed stateless 5,777, giving two independent
discriminators.
3. Keys
Key generation takes a 48-byte seed from the caller, split into three 16-byte parts:
seed(48) = sk_seed(16) ‖ sk_prf(16) ‖ pk_seed(16)
| Composition | Length | |
|---|---|---|
| Public key | pk_seed ‖ sl_root ‖ sf_root |
48 B |
| Secret key | sk_seed ‖ sk_prf ‖ pk_seed ‖ sl_root ‖ sf_structure ‖ sf_root |
82 B |
| Field | Role |
|---|---|
pk_seed |
Public parameter of every tweakable hash |
sk_seed |
Derives WOTS-TW, WOTS+C and FORS secret keys |
sk_prf |
Derives the message randomiser R |
sl_root |
The stateless component: the root of the top XMSS tree of the SLH-DSA hypertree |
sf_root |
The stateful component: the FXMSS root, at height 255 |
sf_structure |
2 bytes, (shape, depth), describing
the FXMSS tree; carried only in the secret key |
Both encodings are deliberate:
- truncating the last 16 bytes of the public key
(
sf_root) leaves a standard SLH-DSA public key for the stateless component under its parameter set; - truncating the last 18 bytes of the secret key
(
sf_structure ‖ sf_root) leaves a valid SLH-DSA secret key.
A SHRINCS key is thus a suffix extension of an SLH-DSA key.
4. Parameter sets
Stateful parameters
| Parameter | Value | Meaning |
|---|---|---|
WOTS_C_CHAIN_BITS |
4 | Bits encoded per Winternitz chain (w = 16) |
WOTS_C_CHAIN_COUNT |
32 | Number of Winternitz chains, with no checksum chains |
FXMSS_HEIGHT |
255 | The imaginary height of the FXMSS tree, and so the maximum leaf depth |
Derived: WOTS_C_CONSTANT_SUM = ⌈32 × 15 / 2⌉ = 240, the
target sum of the chain indexes, which is also their expected value.
Stateless parameters, against FIPS 205
| Parameter | FIPS 205 name | SHRINCS | SLH-DSA-SHA2-128s |
|---|---|---|---|
| Security parameter (bytes) | n |
16 | 16 |
| Winternitz | lg_w |
4 (w=16) | 4 (w=16) |
| Hypertree layers | d |
5 | 7 |
| XMSS height per layer | h' |
9 | 9 |
| Total hypertree height | h |
45 | 63 |
| FORS tree height | a |
13 | 12 |
| FORS tree count | k |
10 | 14 |
| Message digest length | m |
24 | 30 |
| Signature budget | 2⁴⁰ | 2⁶⁴ | |
| Signature length | 5,776 B | 7,856 B |
WOTS-TW chain counts: len1 = ⌈128/4⌉ = 32 and
len2 = ⌈bitlen(32×15)/4⌉ = ⌈9/4⌉ = 3, for
35 chains, three more than WOTS+C.
The 2,080-byte reduction decomposes exactly:
| Component | SLH-DSA-128s | SHRINCS | Difference |
|---|---|---|---|
| Randomiser R | 16 | 16 | 0 |
FORS signature, 16·k·(a+1) |
16×14×13 = 2,912 | 16×10×14 = 2,240 | −672 |
Hypertree signature, d × (35×16 + 16×h') |
7×704 = 4,928 | 5×704 = 3,520 | −1,408 |
| Total | 7,856 | 5,776 | −2,080 |
Two thirds of the saving comes from cutting the budget, which shortens the hypertree, and one third from reshaping FORS into fewer and taller trees.
The draft notes that SPHINCS+C or PORS+FP would give a further 15 percent at the same 2⁴⁰ budget, and declines both in order to keep FIPS 205 implementation reuse and the existing security analysis.
5. ADRS
As in SLH-DSA, every hash call must be made unique so that an input in one position cannot be replayed in another to yield the same output. The SHRINCS ADRS is 22 bytes, with one interpretation per path:
| Stateless ADRS | Size | Stateful ADRS | Size | |
|---|---|---|---|---|
layer |
1 B | node_height |
1 B | |
tree_address |
8 B | node_index |
8 B | |
type |
1 B | type |
1 B | |
payload |
12 B | payload |
12 B |
The type values separate the paths completely, with no
numeric overlap:
| Stateless | Value | Stateful | Value | |
|---|---|---|---|---|
SL_WOTS_TW_HASH |
0 | SF_WOTS_C_HASH |
16 | |
SL_WOTS_TW_PK |
1 | SF_WOTS_C_PK |
17 | |
SL_XMSS_TREE |
2 | SF_FXMSS_TREE |
18 | |
SL_FORS_TREE |
3 | SF_WOTS_C_PRF |
21 | |
SL_FORS_ROOTS |
4 | SF_WOTS_C_GRIND |
22 | |
SL_WOTS_TW_PRF |
5 | |||
SL_FORS_PRF |
6 |
The first two payload bytes under SF_WOTS_C_PRF hold
sf_structure. That is where the tree shape enters
secret-key derivation; see section 8.
6. Hash functions
All are built from SHA-256, in a common form:
sha256(pk_seed ‖ zeros(48) ‖ ADRS ‖ M)[:16]
The zeros(48) padding brings pk_seed (16
bytes) to 64 bytes, exactly one SHA-256 block, so the compression of
that block can be precomputed once and reused.
Tweakable hashes
| Function | Use | Path |
|---|---|---|
F |
One step of a hash chain | Both |
H |
Combines two Merkle children | Both |
T_sl |
Compresses 35 WOTS-TW chain ends into a public key | Stateless |
T_sf |
Compresses 32 WOTS+C chain ends into a public key | Stateful |
T_k |
Compresses k FORS roots | Stateless |
H_grind |
WOTS+C constant-sum grinding | Stateful |
H_grind is the one exception, consuming only
ADRS[:10]:
sha256(pk_seed ‖ zeros(48) ‖ ADRS[:10] ‖ digest ‖ zeros(4) ‖ counter)[:16]
This keeps the whole input within a single compression, which matters
because grinding runs up to 2¹⁶ times. One consequence is that the
signer holds sf_structure in ADRS[10:12] at
this point while the verifier leaves those bytes zero; since they fall
outside the consumed window, both sides compute the same value.
Pseudorandom functions
| Function | Definition | Role |
|---|---|---|
PRF(pk_seed, sk_seed, ADRS) |
sha256(pk_seed ‖ zeros(48) ‖ ADRS ‖ sk_seed)[:16] |
Derives WOTS and FORS secret keys. Consumes the full 22-byte ADRS |
PRF_msg_sl / PRF_msg_sf |
HMAC-SHA256 | Derives the message randomiser R |
Message digests and the cross-binding
The two message digest functions are structurally symmetric:
each takes its own root as a dedicated parameter and receives
the other path's root inside M. The two calls, as
the draft's reference implementation makes them:
# stateless: what is actually signed is sf_root ‖ message
# M = 0x00 ‖ len(ctx) ‖ ctx ‖ sf_root ‖ message
H_msg_sl(R, pk_seed, sl_root, M)
# stateful:
# M = 0x00 ‖ len(ctx) ‖ ctx ‖ sl_root ‖ message
H_msg_sf(R, pk_seed, sf_root, ADRS, M)The two components are therefore interlocked. Neither root can be lifted out of the public key and used as a key in its own right, and a signature under one path cannot be carried over to a different public key.
7. WOTS+C: constant-sum encoding in place of a checksum
Winternitz signatures, briefly
Both of the one-time signatures here are Winternitz, so it is worth having the base construction in view before the variation.
A chain is a hash applied repeatedly to a secret:
s, H(s), H²(s), and so on.
Publishing the d-th link signs the digit d,
because a verifier can walk the chain forward to the end but cannot walk
it back without inverting H. The end of the chain,
H^(w−1)(s), is the public value. With w the
Winternitz parameter, one chain carries lg w bits, and a
digest is signed by cutting it into digits and giving each its own
chain.
That much is forgeable on its own. An adversary holding
Hᴰ(s) can hash further to get Hᴰ′(s) for any
D′ > D, which is a valid signature on every larger
digit. The fix is a second kind of chain carrying the complement of the
digit sum, so that raising a message digit lowers a checksum digit, and
forging upwards means walking a checksum chain backwards.
That is WOTS+, and WOTS-TW is the same construction with the chaining
function replaced by a tweakable hash, which is what makes each position
in each chain a distinct function and what gives SPHINCS+ its tight
security proof. Everything above holds for it, with three checksum
chains over 32 message chains at w = 16.
WOTS+C takes a different route to the same end. It adds no checksum, and instead admits only digests whose chain indexes already sum to a constant.
requirement: Σ indexes == WOTS_C_CONSTANT_SUM = 240
On a single chain, the signature value σᵢ is the intermediate result of walking dᵢ steps along the chain from the secret key. A verifier can only continue forwards:
With 32 chains of 4 bits each, over 0–15, the expected index sum is exactly 32×15/2 = 240. The signer grinds a 16-bit counter until the sum lands on it:
pub fn wots_c_grind<S: HashSuite>(
pk_seed: &[u8],
digest: &[u8],
adrs: &mut Adrs,
) -> Option<(u16, Vec<u32>)> {
adrs.set_type(SF_WOTS_C_GRIND);
for i in 0..=u16::MAX {
let hashed = S::h_grind(pk_seed, adrs, digest, i);
let idx = base_2b(&hashed, WOTS_C_CHAIN_BITS, WOTS_C_CHAIN_COUNT);
if idx.iter().map(|&x| x as usize).sum::<usize>() == WOTS_C_CONSTANT_SUM {
return Some((i, idx));
}
}
None
}The counter is returned alongside the indexes because the signature carries it: a verifier cannot search for it, and recomputes once from the value it is given.
pub fn wots_c_map_digest<S: HashSuite>(
pk_seed: &[u8],
digest: &[u8],
adrs: &mut Adrs,
counter: u16,
) -> Option<Vec<u32>> {
adrs.set_type(SF_WOTS_C_GRIND);
let idx = base_2b(
&S::h_grind(pk_seed, adrs, digest, counter),
WOTS_C_CHAIN_BITS,
WOTS_C_CHAIN_COUNT,
);
(idx.iter().map(|&x| x as usize).sum::<usize>() == WOTS_C_CONSTANT_SUM).then_some(idx)
}Both are from AppliedPQC/shrincs-rs
unchanged. The None on the last line of
wots_c_grind is the case the draft puts below 2⁻¹⁴⁵⁰; the
None from wots_c_map_digest is a verifier
rejecting a counter whose indexes miss the sum, which is the check that
makes the encoding binding.
Grinding fails with probability below 2⁻¹⁴⁵⁰, which is the target the draft's parameters were chosen against. The verifier recomputes once from the counter in the signature and rejects if the sum is not 240.
Two consequences follow:
- Three chains fewer. 32 against 35 for WOTS-TW, saving 48 bytes per signature, or roughly 8 percent.
- Worst-case verification cost equals the average. Because the index sum is fixed, so is the total number of chain steps a verifier walks: Σ(15 − dᵢ) = 32×15 − 240 = 240, the same count the signer performs. A consensus verifier can therefore price the worst case exactly.
The second claim is the one worth checking rather than taking on
faith, and it is checked. verify_parameter_consistency in
the Rust implementation re-derives it from the defining equation rather
than restating it, and the known-answer tests exercise it on every
signature they cover. Running the specification's own
H_grind and base_2b over 300 messages gives a
mean of 64.4 counters ground, and a chain-step count on both sides that
is the single value 240 every time.
The same idea, Target-Sum Winternitz, appears in Ethereum's LeanSig, under a different motivation. LeanSig needs it because an aggregation circuit is sized for the worst case; SHRINCS needs it because consensus must price worst-case verification. The two share an author in Mikhail Kudinov.
8. FXMSS: an XMSS of signer-chosen shape
XMSS, briefly
A one-time key signs once, so a scheme that signs more than once needs many of them and a way to publish them all as one value. XMSS is the standard answer: put the hashes of the one-time public keys at the leaves of a Merkle tree and publish the root.
A signature then carries three things. The one-time signature itself; the index of the leaf that made it; and the authentication path, one sibling per level from that leaf to the root. A verifier recovers the leaf from the one-time signature and climbs, combining with each sibling in turn, and the index tells it which side to combine on. Reaching the published root is what verification means.
Each leaf may be used once, which is what makes XMSS stateful: the signer has to record which leaves are spent. That is the constraint FXMSS inherits and the state counter of section 9 manages, and it is why the scheme carries a stateless fallback at all.
Differences from XMSS
| XMSS, inside the stateless path | FXMSS, the stateful path | |
|---|---|---|
| Tree | Always balanced, at fixed height h' = 9 |
Any shape; leaves may sit at different depths |
| Leaf OTS | WOTS-TW, 35 chains | WOTS+C, 32 chains |
| What is signed | Internal to SLH-DSA: trusted messages the signer generated | A scheme in its own right, signing untrusted messages directly |
The last row is a security requirement the specification emphasises, and the reason the two have different interfaces.
The imaginary height of 255
The root is fixed at height 255, so
depth = 255 − height. A leaf sits at height 0 only at the
maximum depth of 255. The specification calls 255 imaginary because no
real tree can fill 2²⁵⁶ nodes.
Three properties follow:
- A leaf
heightoccupies one byte, is carried in the signature, and gives the verifier the depth directly. - The authentication path must be exactly
depth × 16bytes, which cross-checks against the declared height. - 255 never occurs on the stateful path, which frees it as the tag for stateless signatures.
Signature format and size
FXMSS signature = 2 (grind counter) + 512 (32 chains × 16 B) + 16 × depth
= 514 + 16·depth
SHRINCS stateful signature = 1 (leaf_height) + 16 (R)
+ ⌈min(depth,64)/8⌉ (leaf_index) + FXMSS signature
- depth = 1: 1+16+1+530 = 548 B, the minimum
- depth = 255: 1+16+8+4,594 = 4,619 B, the maximum
Each additional level adds 16 bytes, and the index field grows by one byte as depth crosses 8, 16, and so on up to 64. That is the origin of the draft's "16 or 17 bytes per signature" figure. Beyond depth 64 the index stays at 8 bytes and growth is a steady 16.
The two prescribed shapes
Shape is described by the two (shape, depth) bytes in
the secret key, and the leaf is selected by the state counter.
UXMSS, left-leaning and unbalanced,
shape = 0:
ctr < depth → (index=1, height=255−1−ctr) ⇒ leaf_depth = ctr+1
ctr == depth → (index=0, height=255−depth) ⇒ leaf_depth = depth
The budget is depth+1, with depths 1, 2, …, d, d,
the last two equal. Early signatures are very small and later ones grow
linearly. The spine descends on the left (index = 0) and
hangs one leaf to the right (index = 1) at each level; at
the bottom level both children are leaves.
BXMSS, balanced, shape = 1:
ctr < 2^depth → (index=ctr, height=255−depth)
The budget is 2^depth and every signature is the
same length: all leaves sit on one level, so the authentication path is
always depth siblings.
Circles are internal nodes, boxes are WOTS+C leaves, and each leaf carries the signature size it produces.
| Configuration | Signature size | Budget | Key generation (SHA-256 compressions) | Average signing |
|---|---|---|---|---|
| UXMSS d=255 | 548 → 4,619 B | 256 | 425,982 | 133,326 |
| BXMSS d=5 | 612 B | 32 | 309,054 | 16,522 |
| BXMSS d=8 | 660 B | 256 | 425,982 | 133,447 |
| BXMSS d=10 | 693 B | 1,024 | 826,878 | 534,341 |
| BXMSS d=20 | 854 B | 2²⁰ | 547,649,022 | 547,356,475 |
UXMSS at d=255 and BXMSS at d=8 cost the same to generate, both having 256 leaves. Shape does not change the cost at a given budget; it changes the size curve. UXMSS front-loads the small signatures, and BXMSS flattens size across all of them.
Shape-agnostic verification
The verifier never sees shape. It reads the first
signature byte, derives depth, and climbs that many levels.
The figure below sets out the whole scheme in the same three parts as
the ones above, and works the climb through on a UXMSS tree of depth 4
with the state counter at 2:
fxmss_pubkey_from_sig, again from the draft's reference
implementation, receives only
(leaf_index, leaf_height, signature):
assert len(xmss_auth) == leaf_depth * 16 # path length must match the depth
assert leaf_index < 2 ** min(64, leaf_depth) # index must fit at that depth
node = wots_c_pubkey_from_sig(...)
for k in range(leaf_depth): # direction comes from the index bits
if (leaf_index >> k) & 1: node = H(..., sibling + node)
else: node = H(..., node + sibling)No shape byte is referenced. Whether the tree is UXMSS, BXMSS, or some other shape, the verifier has a single code path, which bounds what consensus code has to review and optimise.
The claim that one code path covers every shape is likewise under
test rather than asserted. The crate's vectors cover 46 stateful
signatures across four tree shapes, UXMSS and BXMSS at two depths each,
and every one of them verifies through a single
fxmss_pubkey_from_sig that is never given the shape byte.
The sizes those vectors carry are the ones in the two figures above,
produced by the draft's reference implementation rather than copied from
it.
Where the shape is bound
Shape is bound into secret-key derivation rather than into verification:
fxmss_signplacessf_structureinADRS[10:12];wots_c_signandwots_c_pubkey_genuse it when callingPRF, andPRFconsumes the full 22-byte ADRS, so every chain secret depends on the shape;ADRS[10:14]is then zeroed, and chain iteration andT_sfboth run after zeroing, so the verifier does not need the shape.
The consequence is that one seed under different
(shape, depth) values yields entirely different secrets,
leaves and roots. This is domain separation: a wrong shape byte on key
import does not collide with the original tree's secrets, it yields an
unusable key.
Directed trees
The specification argues explicitly against directionless Merkle
trees of the kind Taproot uses. Without a left-right distinction, an
adversary holding two nodes at the same level can run a preimage search
against both targets at once, doubling the multi-target advantage, and
the advantage grows with the number of nodes. Direction is therefore
given explicitly by (leaf_index >> k) & 1.
9. State management
The stateful path selects its leaf by a state counter, the number of signatures issued. Reusing a counter signs two different messages under one WOTS+C key, and anyone who observes both signatures can forge.
The draft's MUST-level rules:
- the counter must not be backed up or restored, must not be exported or imported, and must not be used concurrently;
- it must be persisted before the signature is returned;
- when state is uncertain the stateful path must be refused, falling back to the stateless one.
Two mitigations distinguish this from XMSS and LMS:
- Every key pair carries a stateless fallback by construction. Losing state costs signature size, from 548 to 5,777 bytes, rather than funds.
- Leaf reuse is publicly detectable. Every stateful
signature exposes its
(index, height), so a repeated leaf under one public key can be spotted before the second transaction is broadcast.
10. Sizes and verification cost
| Item | Size |
|---|---|
| Public key | 48 B |
| Stateful signature | 548 – 4,619 B |
| Stateless signature | 5,777 B |
| Public key plus signature, minimum | 596 B |
For comparison, SLH-DSA-SHA2-128s gives 32 + 7,856 = 7,888 B, a factor of 13.23, and ML-DSA-44 gives 1,312 + 2,420 = 3,732 B, a factor of 6.26, though ML-DSA-44 is Level 2 where SHRINCS is Level 1.
Verification cost
| Compressions | Per byte | |
|---|---|---|
| Stateful | 255 – 509 | 0.465 |
| Stateless | 467 – 2,792 | 0.483 |
| BIP-340 Schnorr | ≈127 equivalent | 1.98 |
Per byte this is 4 to 16 times faster than Schnorr. The stateful parameters were tuned to put the two paths' per-byte costs close together, so that one witness discount rule can cover both. The draft argues from this for leaving room for a witness discount, without defining one.
The cost falls elsewhere: key generation runs 3.1×10⁵ to 5.5×10⁸ compressions, and stateless signing averages 1.7×10⁶.
11. Differences from SLH-DSA
| SLH-DSA-SHA2-128s | SHRINCS | |
|---|---|---|
| State | Stateless | Semi-stateful: stateful primary with stateless fallback |
| Public key | 32 B | 48 B, adding sf_root |
| Signature | 7,856 B | 548–4,619 B primary, 5,777 B fallback |
| One-time signature | WOTS-TW, 35 chains, checksum | WOTS+C, 32 chains, constant sum, plus WOTS-TW in the fallback |
| Tree structure | Fixed hypertree, d=7, h'=9 |
Variable-shape FXMSS, plus a d=5, h'=9
hypertree in the fallback |
| Signature budget | 2⁶⁴ | 2⁴⁰ fallback; 32–2²⁰ primary, depending on shape |
| Verification cost | Depends on signer choices | Worst case equals average on the primary path |
| Security level | Level 1 | Level 1: 128-bit classical, 64-bit quantum |
| Underlying hash | SHA-2 or SHA-3 | SHA-256 only, reusing the hash Bitcoin consensus already has |
| Standardisation | FIPS 205 | Draft, not submitted to the BIPs repository, no security proof |
12. Properties the scheme does not provide
The third drawback the draft lists for itself:
SHRINCS lacks any algebraic structure allowing for public key rerandomization, multisignature schemes (like MuSig), etc.
There is no counterpart to BIP-32 xpubs and no MuSig-style multisignature. This is common to hash-based signatures, which have no algebraic structure to aggregate over. For protocols whose custody rests on n-of-n MuSig2, such as the BitVM2 peg, a post-quantum replacement is therefore not within the scope of changing an output type.
13. Open problems
The draft is a first draft, and says so. What is outstanding falls into three kinds, and they are not equally hard.
Missing artefacts, which are work rather than research. There is no security proof; the draft records one as TODO and thanks Andreas Hülsing for input on it. There are no comprehensive test vectors, which the draft states are required to move the proposal from Draft to Complete. There are no unit tests and no optimised implementation, the reference being described by its own authors as naive, inefficient and non-constant-time. Each of these is known, scoped, and merely undone.
Deployment questions that belong to Bitcoin rather than to the scheme. The draft specifies cryptography decoupled from consensus, so at least one further BIP defining a new output type is needed and is not written. The verification cost per byte argues for a witness discount, and the draft makes that argument without defining one. The parameter set is a multi-dimensional trade between size and speed which the working group expects to be contested; the two components can be reparameterised independently, so the space is larger than a single choice.
What follows from the missing algebraic structure. Section 12 states the absence itself; what is open is what to do about it. A hybrid with BIP-340 works by simple concatenation, and the draft declines to define a dedicated combiner, so a deployment wanting strong unforgeability under partial compromise has that to design. Whether an HD-wallet arrangement can be built for a scheme with no rerandomisable public key is a question the draft raises and leaves open.
Alongside those, the state counter remains the sharp edge. The reference implementation performs no state management at all and takes the counter from its caller, which is honest about where the difficulty lives: the rules in section 9 are MUST-level and unenforceable by the scheme itself. How a wallet presents a signature that grows as a key is used, and how backup and recovery work when restoring state is forbidden, are user experience questions the draft raises and does not answer.
14. Where this sits in the BIP process
The Requires field of BIP-361 reads "TBD Post Quantum
Signature BIP". SHRINCS is the first public candidate for that missing
document, and it has not been submitted to the BIPs repository, so
BIP-361 itself remains the only post-quantum entry in the BIPs
index.
A reference implementation
The whole scheme is implemented in Rust at AppliedPQC/shrincs-rs,
following the draft's own structure. It is checked against two
authorities rather than against itself: the reference implementation the
draft ships, which fixes the vectors and which it also cross-verifies
with in both directions, and NIST's ACVP vectors, which the stateless
component reproduces once instantiated at the standard SLH-DSA parameter
sets. The second matters because the draft's central reuse claim is that
this component is FIPS 205, and vectors from the draft cannot test that
claim.
Writing it surfaced one behaviour this note had left implicit.
Exhausting the stateful budget is not an error:
shrincs_sign finds no leaf and takes the same branch as
when no counter was supplied at all, so the signature comes back from
the stateless component at 5,777 bytes and verifies. Running out of
leaves and losing the state file degrade identically.
Appendix: the working group
Mike Casey (OpenChain), Conduition (Brink), Ethan Heilman (Cloudflare, author of BIP-347 and BIP-360), Mikhail Kudinov (Blockstream), Oleksandr Kurbatov (Blockstream), Boris Nagaev (independent), Jonas Nick (Blockstream), remix7531 (OpenSats).
The scheme originates in earlier proposals by Jonas Nick and Mikhail Kudinov. Kudinov is also an author of Ethereum's LeanSig, which is why both lines adopt Target-Sum, or constant-sum, Winternitz encoding.






