§ 02 · Cryptography
XMSS
Signatures made only of hashes, built to outlast quantum computers
On this page 7 sections
XMSSXMSS: A signature scheme built only from hash functions, believed to stay secure against quantum computers. Pearl's version allows 32 signatures per key, each 2,340 bytes. Using it is optional. is a way to sign messages using nothing but a hash function. No curves, no hard number puzzles: if the hash function is safe, the signatures are safe.
That matters because of quantum computers. A large enough one could break the elliptic-curve math behind Schnorr signatures, but it would barely dent a good hash function. Pearl adds XMSS as an optional, post-quantumPost-quantum: Describes cryptography believed to stay secure even against large quantum computers. In Pearl, that means XMSS signatures, which are optional. way to lock coins.
Why would hashes survive a quantum computer?
Shor’s algorithm lets a quantum computer solve the discrete-logarithm problem, which is what keeps a private key hidden behind its public key. That would break every elliptic-curve signature.
Hash functions have no such weakness. The best known quantum attack on them, Grover’s algorithm, only takes a square root off the work: a 256-bit hash still needs about 2¹²⁸ quantum steps to invert. So a signature that relies only on hashes stays secure.
XMSS is standardised in RFC 8391. It’s built in three layers.
Layer 1: how can a hash make a signature?
Start with a secret random value and hash it over and over, 15 times. You get a chain of 16 values. Publish only the last one.
To “sign” a number d between 0 and 15, reveal the value d steps along the chain. Anyone can check it: hash it 15 − d more times and see if it lands on the published end.
Hashing forward is easy. Hashing backward is impossible, because hashes are one-way. That asymmetry is the whole signature. This kind of scheme is a one-time signatureOne-time signature: A signature scheme built only from a hash function, where each key may sign just one message. Signing twice with the same key gives away enough secrets to forge.: once part of a chain is revealed, the key mustn’t sign again.
Layer 2: how do you sign a whole message?
Use many chains, one per digit. This is the WinternitzWinternitz signature (WOTS+): A one-time signature that signs a message digit by digit by walking along chains of repeated hashes. Pearl’s XMSS uses 67 chains of 16 values each. scheme (WOTS+). With chains of 16 values, each chain signs one hexadecimal digit.
A 32-byte digest has 64 hex digits, so it needs 64 chains. But there’s a catch: anyone holding a signature can hash further along any chain, raising that digit, and sign a different message. Three checksum digits stop that.
That gives 64 + 3 = 67 chains, and a one-time signature of 67 × 32 = 2,144 bytes.
Layer 3: how does one key sign 32 times?
One-time keys are awkward: you’d need a new public key for every signature. XMSS fixes this with a Merkle tree.
Make 32 one-time keys. Hash each one’s public part into a leaf, and build a tree of height 5 over the 32 leaves. The root becomes the long-term public key.
To sign, use the next unused one-time key, and attach its authentication pathMerkle proof: The handful of sibling hashes needed to rebuild a Merkle root from one leaf. It proves the leaf is in the tree without showing the other leaves.: the 5 sibling hashes from its leaf to the root. The verifier rebuilds the one-time public key from the signature, climbs the path, and checks the root.
So a full XMSS signature is: the leaf’s index, a random value r used to hash the message, the 67-chain one-time signature, and the authentication path.
What are Pearl’s exact parameters?
Pearl uses a configuration its code calls XMSS-SHAKE256_5_256, which is not one of RFC 8391’s named sets:
- Hash
- SHAKE256
- 32-byte outputs (n = 32)
- Winternitz w
- 16
- 67 chains (64 + 3)
- Tree height
- 5
- one layer (d = 1)
- Signatures per key
- 32
- 2⁵ leaves
- Public key
- 64 bytes
- root + public seed
- Signature
- 2,340 bytes
- 5 × 468 in a witness
The hash is SHAKE256SHAKE256: A hash function from the SHA-3 family that can produce output of any length. In Pearl it is used only inside XMSS signatures., from the SHA-3 family. The secret key is 128 bytes plus a 4-byte index that counts the signatures used.
How does Pearl use XMSS?
Through tapscript, Pearl’s script language for Taproot and P2MR outputs:
- The opcode.
OP_CHECKXMSSSIGtakes five 468-byte signature pieces and a 64-byte public key from the stack. Stack items are capped at 520 bytes, which is why the 2,340-byte signature is split in five. - What is signed. The same tapscript signature digest that Schnorr signatures sign (BIP-342), always with the default sighash type.
- Failure is final. An invalid XMSS signature makes the whole script fail; it never just pushes “false”. Each check also costs 1,000 units of the input’s script budget.
- The script leaf. The wallet’s leaf is
40 <64-byte public key> de: push 64 bytes, thenOP_CHECKXMSSSIG. 66 bytes in all.
The reference wallet (Oyster) adds such a leaf only when asked: getnewaddress with its pq flag set to true. The XMSS key comes from a separate branch of the wallet’s seed (purpose 222, which is 0xde, the opcode’s byte), stretched with HKDF-SHA256 into a 64-byte private seed and a 32-byte public seed.
The result is a normal-looking Taproot address whose script tree has one XMSS leaf. Here is a real one, made by Oyster from the public BIP-39 test words:
| Part | Value |
|---|---|
| XMSS public key (64 bytes) | 5e70da755554f576d008eb89bfa9ffa8965e08cfdcfa48aaf85d59499792497a4b9e933bfc2f4695395f43339d799434741e36742ce2f0df31994e54b25ee8f8 |
| Address | prl1ph89v46drqp3rd3cfaylqqwnynuu2cgykdgqd60094qxp4jl4sd7ssg6v5j |
What are the honest limits today?
XMSS in Pearl is real but early. As of v1.5.2:
- It’s opt-in. Default addresses, change addresses and everything the desktop wallet creates have no XMSS leaf. They are protected by Schnorr alone.
- The key path stays open. An XMSS address is a Taproot address, so it can still be spent with an ordinary Schnorr signature, and that’s how the wallet spends it. Someone who could break secp256k1 could use that path too. A P2MR output has no key path, but the wallet doesn’t create P2MR addresses.
- No wallet signs with it yet. The research found no production code that makes XMSS signatures; only a test calls the signer. The XMSS leaf is an emergency path for now.
- It’s big. An XMSS script-path spend’s witness is roughly 2,457 bytes, about 615 vbytes, against about 16.5 vbytes for a Schnorr key-path witness (an estimate from the research, not a measured transaction).
Why the opcode number is 0xde
0xde is 222. The wallet derives XMSS keys under BIP-32 purpose 222′, matching the opcode byte, in the same way
that Pearl’s normal keys live under purpose 86′ (BIP-86). The opcode is defined in
opcode.go (node/txscript/opcode.go:2020-2105 at v1.5.2, on GitHub), and the parameters in
xmss.cpp (xmss/src/xmss.cpp:11-31 at v1.5.2, on GitHub):
// config: XMSS-SHAKE256_5_256 (not in RFC 8391)
static constexpr xmss_params default_params = {
.func = XMSS_SHAKE256,
.n = 32, // hash input & output length
.wots_w = 16, // Winternitz parameter
.wots_len = 67, // len1 + len2,
.full_height = 5, // allows 32 signatures
.d = 1, // no MT recursion
.sig_bytes = 2340, // wots_sig_bytes + index_bytes + (height + 1) * n,
.pk_bytes = 64, // 2 * n
// ...
};