Justin asked the industry to plan for bunker mode: move assets to fresh addresses whose public keys stay hidden behind a hash. We built a bunker for Solana, then made a film about it.
Solana Bunker is a research proof of concept for a Solana vault that keeps one receiving address while the cryptography that unlocks it is replaced, ending in XMSS hash-based signatures verified in a single transaction. The treasury is a program-derived address with no private key. The rule that authorizes a withdrawal lives in a separate state account, and we replaced that rule three times while the address stayed put.
Solana Bunker is built by Chaintail Labs, its code and white paper are public on GitHub, and the program runs on Solana devnet with no audit. On devnet, one XMSS-signed SOL withdrawal is a single confirmed transaction of 3,117 bytes at 518,509 compute units.
Three locks, one survivor
Solana Bunker tried three locks on the same treasury: an array of SHA-256 commitments to one-time secrets, the same commitments under one Merkle root, and XMSS signatures. Only the XMSS lock survives the timing attack.
Each lock is tested with native SOL and with a test token, Mock USDC. Only the lock changes; the treasury and its address stay put.
Version one stores a thousand SHA-256 commitments to one-time secrets. To withdraw, you reveal a secret, the program hashes it, finds the match and crosses it off. Version two commits the same secrets as a single Merkle root, so the vault holds 390 bytes instead of 33,126 at 1,024 leaves, and setup takes two transactions instead of 38. A withdrawal now carries the secret plus its path to the root.
The timing attack
Both versions lose to the same attack. A revealed secret is a password, and a password is a bearer credential: whoever presents it first is paid. As the film puts it, a password says nothing about where the money should go.
So anyone who sees your withdrawal before it lands, such as an RPC node or a validator passing it along, can copy the secret into a transaction of their own, put their address in as the destination, pay a bigger fee and land first. The program checks the secret, it matches, and it pays them. Your transaction then fails because that leaf is already spent. That is a timing attack: a race to land first, also known as front-running. It breaks no hash and forges no signature; the program simply accepts a withdrawal from whoever shows a valid secret. Solana’s fast blocks make the window short, but not closed.
Our tests script this race against both versions with both assets, and the attacker wins in all four cases. The tests set the ordering; they do not intercept real network traffic. The film shows the race once, on version two.
Version three replaces the password with a signature. XMSS, the hash-based signature scheme from RFC 8391 and NIST SP 800-208, is built from SHA-256 alone, and it signs the exact withdrawal: the program and vault it is for, the destination, the amount and asset, the epoch, the action nonce and the signing leaf. The fee payer and recent blockhash are left out on purpose, so an unchanged withdrawal can be retried. Copy the signature onto a different destination and verification fails. Copy it unchanged and it can only pay you, once.
Why a bigger witness is the better lock
Figure 2 can look as if the locks got worse: the witness a withdrawal carries grows from 36 bytes to 356, then to 2,500. That growth is the price of a credential that is not a bearer token. A secret only proves you know something, and 32 bytes is enough for that. A signature has to say something about the message, and a hash-based signature says it with hash chains.
The numbers behind figure 2
| Version | What is stored | State bytes at 1,024 leaves | Witness bytes | What the witness proves | Under attack |
|---|---|---|---|---|---|
| Array | 1,024 SHA-256 hashes | 33,126 | 36 | Someone knows secret #i | Robbed |
| Merkle | One Merkle root | 390 | 356 | Someone knows secret #i, and it is in the tree | Robbed |
| XMSS | Root and public seed | 422 | 2,500 | Leaf #i signed this exact withdrawal | Rejected |
XMSS signs with one-time WOTS+ keys. The hash of the withdrawal is cut into 67 digits, 64 for the message and 3 for a checksum, and each digit says how many times to hash a secret value along its own chain. The signature is the 67 chain values at those positions, 32 bytes each: 2,144 bytes. Change the destination and the hash changes, so at least one digit would need a value earlier in its chain than the one revealed, which means running SHA-256 backwards. The checksum is there so that moving any digit forward forces another one back. Add the 320-byte path proving the one-time key belongs to the tree, a 4-byte leaf index and a 32-byte randomizer, and you have 2,500 bytes.
The state stays small, 422 bytes for a root and a public seed. The bytes move into the transaction, where Solana v1 transactions have room for them. Bytes per withdrawal are what a withdrawal nobody can redirect costs.
Same address, fresh keys
Version three also rotates. A rotation signed by the current tree installs a fresh tree, with a new root and public seed, at the same address with the same balance. The epoch advances, the record of spent leaves starts empty, and the old root is refused afterwards. The new tree’s keys come from the same BIP39 seed phrase under a new generation number.
Rotation matters because XMSS is stateful. Each of the tree’s 1,024 leaves is a one-time key. The vault marks spent leaves in a bitmap and can burn doubtful ones, and the signer records each leaf in a local journal before it signs. The seed phrase recovers the keys, not that journal, so restoring from the phrase alone is not a safe way to resume signing.
Plan for the last leaf. Rotation is a signed action and spends a leaf like any withdrawal, and the program does not hold one back. If every leaf is spent or burned, no XMSS signature can rotate the tree, and only the trusted admin key this proof of concept retains could reset the vault. So rotate before you run out. A rotation that fails on chain does not spend its leaf, and the signer keeps the exact signed bytes so it can retry them. A signing attempt that is interrupted before the signature is saved retires its leaf for good. The table below keeps the last leaf for the rotation and counts 1,023 withdrawals per tree.
| Withdrawals a week | One tree lasts | Rotations a year |
|---|---|---|
| 1 | 1,023 weeks (19.6 years) | 0.05 |
| 5 | 205 weeks (3.9 years) | 0.26 |
| 20 | 51 weeks (11.8 months) | 1.0 |
| 100 | 10 weeks (2.4 months) | 5.1 |
Running out is a maintenance step, not a migration: rotation keeps the address and the balance, so nobody who sends you funds has to change anything.
One transaction
An XMSS signature is 2,500 bytes. With Solana v1 transactions, which allow 4,096 bytes, it fits in one transaction next to the full canonical intent. Every operation, SOL withdrawal, token withdrawal, leaf burn and root rotation, is one confirmed devnet transaction at roughly half a million compute units. The five-transaction staged path stays available as an explicit fallback.
| XMSS operation | Complete bytes | Confirmed CUs | Share of the 1.4M request |
|---|---|---|---|
| SOL withdrawal | 3,117 | 518,509 | 37.0% |
| Mock USDC withdrawal | 3,214 | 532,564 | 38.0% |
| Burn two target leaves | 3,086 | 529,687 | 37.8% |
| Rotate root and public seed | 3,146 | 483,319 | 34.5% |
| After rotation, from the new root | |||
| SOL withdrawal | 3,117 | 506,910 | 36.2% |
| Mock USDC withdrawal | 3,214 | 578,944 | 41.4% |
Inline also skips the staging account and its rent. The two runs used different signatures and fixtures, so read this as what each path creates and pays, not a paired benchmark.
| Per operation | Inline (v1) | Staged fallback |
|---|---|---|
| Transactions | 1 | 5 |
| Total bytes | 3,086 to 3,214 | 4,470 to 4,726 |
| Witness account | none | 2,625-byte stage account, 19,160,880 lamports of rent, not refunded |
| Fees | 5,000 lamports (local runs) | 30,000 lamports |
Every number on this page comes from the repository, and one script rebuilds them.
How the film was made
Solana Bunker, the film, runs 7 minutes 1 second in ten chapters, written by Liam C. and directed by Claude-do.
Every scene is a web page captured frame by frame in headless Chrome and assembled with ffmpeg. Liam plays all six parts, the news anchor, the scientist, the average Joe, the bunker engineer, the suit and the bandit devil, as a floating head cut from his own footage and set on a cartoon body. A blueprint draws itself as he talks, a whiteboard counts the bytes and stages the robbery, a rain-soaked speakeasy explains why a password is not a signature, and the transaction-size problem becomes an arcade game.




Most of the music is Suno, generated from Liam’s prompts to a cue sheet: a news bed for the desk and the sign-off, a conspiracy cue for the case file, a workshop cue for the blueprint and the whiteboard, noir for the speakeasy and the seminar, and a sponsor groove for the opening, the infomercial and the credits. Not all of it is Suno. The arcade has an original 8-bit score, written in code with our own chiptune tracker and timed to the scene’s beats, and the three news stings are CC0 jingles from Freesound.org. Every cue ducks under the voice and lifts in the gaps between lines.
The sound effects are CC0, from Freesound.org and Kenney.nl, and every source is credited in the closing roll. The announcer is Google’s Chirp 3 HD voice Enceladus, and the sponsor read is Google’s Studio C. The scene pages, capture scripts and cue sheets live in the same repository as the program.
Read more
- The white paper: research/whitepaper.md in chaintail/solana-bunker, with the measured results and the stated limits next to it.
- The program on devnet: HssAePJfZrERFjEAhm89nY9Tc9BWAobxp2GqGhefNK6V, with the XMSS SOL withdrawal and the root rotation open in the explorer.
- The post that started it: Justin Drake on bunker mode.
- Prior work: RFC 8391 for XMSS, NIST SP 800-208 for stateful hash-based signatures, and Dean Little’s Solana Winternitz Vault, which authenticates lamport operations with WOTS.
- More from this workshop: the Folio liquidity study, 42,824 quotes across four Solana routers; TxLINE Kit, a Solana SDK with Merkle proof verification; building a video studio in one night; and the claims gate, how we check facts before a post ships.
This is a research proof of concept. It assumes correct runtime execution and verifier code, retains an ordinary upgrade authority and a demo admin key, and claims no audit, formal verification or production guarantee. Mock USDC is a test token with no value.
Product of Chaintail Labs.
FAQ
What is Solana Bunker?
Solana Bunker is a research proof of concept from Chaintail Labs for a Solana vault that keeps one receiving address while the cryptography that unlocks it is replaced. The treasury is a program-derived address with no private key, and the withdrawal rule lives in a separate state account. It ends in XMSS hash-based signatures (RFC 8391, NIST SP 800-208) verified in a single transaction on devnet. It has had no audit and is not a production wallet.
Why do versions one and two lose to a timing attack?
Both authorize a withdrawal by revealing a one-time secret. A revealed secret is a bearer credential: it proves you know the secret but says nothing about where the money goes. Anyone who sees the pending transaction can copy the secret into their own transaction with a different destination and land first. Compressing the commitments into a Merkle root shrinks the state from 33,126 to 390 bytes at 1,024 leaves, but it does not bind the destination. Nothing in the attack breaks a hash.
Why is the XMSS witness so much bigger, and why is that better?
An XMSS signature is 2,500 bytes because it has to commit to the message: 67 WOTS+ hash-chain values of 32 bytes each, a 320-byte path to the tree root, a leaf index and a randomizer. It covers the program, vault, destination, amount, asset, epoch, action nonce and signing leaf, so a copied signature on an edited withdrawal fails verification. The extra bytes are the price of a credential that is not a bearer token. The on-chain state stays small at 422 bytes.
Does an XMSS signature fit in one Solana transaction?
Yes, with Solana v1 transactions. The XMSS signature is 2,500 bytes and fits in one 4,096-byte v1 transaction next to the exact canonical intent. On devnet a SOL withdrawal was one confirmed transaction of 3,117 bytes at 518,509 compute units, and every XMSS operation measured between 483,319 and 578,944 compute units against a 1,400,000 request. The five-transaction staged path remains as an explicit fallback.
What happens when the 1,024 one-time keys run out?
The vault rotates. A rotation signed by the current tree installs a fresh tree with a new root at the same address with the same balance, and the old root is refused afterwards. Rotation spends a leaf like any other signed action, and the program does not reserve one, so the owner must rotate before the last leaf is used; if every leaf is spent or burned, only the trusted admin key retained in this proof of concept can reset the vault. Keeping the last leaf for rotation, at five withdrawals a week one tree lasts about 3.9 years.