In September I hard-forked Blazecoin, a scrypt chain from 2014 (UTXO, now on a Bitcoin Core 28 base), to add a post-quantum output type. It went live at block 4,250,000 on 16 September 2026. Most of what I learned applies to any UTXO chain thinking about this, so here are the parts I would want to know before starting.
1. The signature is the easy part. Everything around it is the work.The consensus change was one opcode, a new output template and a relaxed push limit for that template only. Then every service that touches an address needed changes:
- the pool's coinbase builder (a 34-byte output instead of 25)
- the indexer (a new script type, and post-quantum supply as its own category)
- address verification on the site
- the payout service
- the desktop, web and Android wallets
- the nightly UTXO consolidation
That half was bigger than the fork itself. Plan for it.
2. Size is the real cost, not CPU | ECDSA P2PKH | ML-DSA-44 |
| Input size | 148 B | 3,782 B |
| Inputs per 1 MB block | ~6,700 | ~264 |
| Inputs per 100 KB standard tx | ~670 | 26 |
| Verify, one core | 254 µs | 132 µs |
ML-DSA-44 verifies faster than our managed ECDSA, and a full block of it verifies in about 35 ms. But a standard transaction now fits only 26 post-quantum inputs, so our consolidation batches had to drop from 150 inputs to 26. Find every place that assumes "inputs are small" before the fork, not after.
3. ML-DSA has no xpubThere is no public derivation, so you can't hand out an xpub and derive receive addresses watch-only. We derive keys from the HD seed with a tagged hash, cache the expanded public keys, and accept that a locked wallet can recognise its coins but can't top up its address pool. One trap we caught in testing: if receive and change derive from the same seed, they produce identical addresses. Use separate seeds.
4. Make one reference implementation the authorityThe daemon is C++ and the wallets are C#. Before writing either, I generated test vectors from a pure-Python FIPS 204 implementation and checked them byte-for-byte against the reference C code. Both codebases then had to match the same vectors (keys, signed transactions, 40 named pass/fail spend cases). Signing is deterministic only; randomised signing is a compile error in the daemon, so there is no RNG in the consensus path. Everything matched on the first run.
5. Choices we made and why- No ECDSA hybrid. The point is to stop depending on ECDSA. A hybrid adds about 100 bytes per input and buys nothing once ECDSA falls.
- Not SLH-DSA first. It rests on the most conservative assumptions, but signatures are ~7.9 KB and signing took ~835 ms each in our tests (10 s for a 12-input transaction). It's reserved as the fallback if lattices break.
- Not Falcon yet. It's much smaller, but FIPS 206 isn't final, and its floating-point signing is a known implementation hazard.
- Versioned algorithm IDs, so ML-DSA-65/87 can be added later without another address format.
6. Ship everything before the heightEvery node, wallet and service was on the fork build about a day before block 4,250,000. Post-quantum features were switched off by config until activation, and the wallets refused to pay to or spend from post-quantum addresses before the height. When the block arrived, nothing had to be deployed.
7. Your activation clock will be wrongI forecast the fork block for 05:40 and it arrived at 21:18. The 880 blocks between the fork and the first post-quantum coinbase should have taken about 7 hours and took 50 minutes. Hashrate had been off for stretches, difficulty decayed, and blocks came at 2 seconds during the catch-up until the per-block retarget pulled it back. Don't hang anything time-critical on a predicted fork time; hang it on the height.
8. The fork doesn't migrate anyoneA legacy address is only protected while its public key has never appeared on-chain. Every coin on a spent-from or reused address stays exposed until its owner moves it. The protocol can offer the safe output, but only holders can use it. The best lever we found was making it the default: the wallets now use post-quantum addresses for receive, change and sweeps, and we moved the project's own funds first.
Caveats. This is a small chain. I chose the activation height with about a day's notice, which only works when one operator runs most of the hashrate, so that part doesn't transfer to a large network.
I'm happy to go into any of it: the signature digest, the key derivation, the push-limit change, or the test vectors.
ANN thread with the full specs:
https://bitcointalk.org/index.php?topic=5597142.0