Entropy Is Everything: What the Coldcard Seed Bug Teaches About Trusting Your Wallet
Open-source hardware wallet. Bitcoin, Ethereum, Solana, TON, TRON, Cosmos, and all EVM chains. Your keys never leave the device.
Open-source hardware wallet. Bitcoin, Ethereum, Solana, TON, TRON, Cosmos, and all EVM chains. Your keys never leave the device.
Disclosure: published by KeepKey, which makes a competing hardware wallet. Every claim about our code below is citable. Verify it against the source rather than taking our word for it.
If you are reading this because a headline scared you, the next three sections explain it properly in under two minutes. The technical detail comes after, for anyone who wants it.
Imagine you are a space pirate. You have gold, and you need somewhere to put it.
So you bury it on a planet. That is the entire system. Your "wallet" is not a container or a vault or an account — it is just the number of the planet. Whoever knows the number has the gold. There is no harbourmaster to appeal to, no registry, nobody who can be convinced the gold was really yours.
That sounds impossibly fragile until you notice one detail: there are more planets than anyone could ever visit. If you pick one truly at random, nobody finds it by searching. Not with a bigger fleet, not with more time. The list is too long to finish, and it always will be.
Your recovery phrase is simply that number, written out in words so a human can copy it down.
Which means the safety of everything rests on a single question: when your device picked the planet, did it really pick at random?
Coinkite is a respected Canadian company that makes COLDCARD, a Bitcoin-only hardware wallet popular with people who take self-custody seriously. On 30 July 2026 they disclosed a bug in their own firmware.
The part of the device that chooses your planet was supposed to draw from the whole sky, using a dedicated chip built for exactly that job. Because of a mistake in how the firmware was assembled, some COLDCARDs were not using that chip at all. They fell back to a cheap substitute that made its choice from things anyone can read off the outside of the hull — the device's serial number and its clock.
So the planets were not scattered across the galaxy. They sat in a small, mappable corner of it. Estimates put the search at somewhere around a trillion candidates. A trillion sounds enormous. It is a list a computer finishes.
Whoever did this worked out the candidate planets in advance, then waited — some of these wallets were created five years ago — and visited them all at once when the gold was worth taking. Roughly 594 BTC left around 500 wallets in about 25 minutes.
Notice what did not happen. Nobody boarded a ship, picked a lock, cracked a vault, guessed a PIN or installed malicious software. The gold had been sitting on a guessable planet since the day it was buried.
The vulnerability is confirmed. Attribution, victim count and total losses are still under investigation — Block published its analysis before finishing exploitability testing, because exploitation was already underway.
Credit where it is due: Coinkite disclosed this publicly, quickly, and in real technical detail. That is not universal in this industry, and it is the only reason anyone can write an article like this one.
If your hardware wallet is not a COLDCARD: no. This is a bug in one manufacturer's firmware, in code specific to their devices. It does not reach into wallets made by anyone else. There is nothing you need to do to a KeepKey, a Trezor, a Ledger or a BitBox because of this.
If you own a COLDCARD, it depends on the model and on which firmware version was running when your seed was first created — updating firmware afterwards does not repair a seed that already exists.
| Model | Affected firmware | Fixed in |
|---|---|---|
| Mk3 | 4.0.1 – 4.1.9 | 4.2.0+ |
| Mk4 / Mk5 (Standard) | multiple | 5.6.0+ |
| Mk4 / Mk5 (Edge) | multiple | 6.6.0X+ |
| Q (Standard) | multiple | 1.5.0Q+ |
| Q (Edge) | multiple | 6.6.0QX+ |
Note that widely-shared summaries claiming Mk4, Mk5 and Q are unaffected are wrong — Coinkite's own backgrounder lists them as affected, with partial mitigation from their secure elements. TAPSIGNER, OPENDIME and SATSCARD use a different codebase and are not affected.
If your seed was created on affected firmware, Coinkite's guidance is to treat the funds as at risk and move them to a newly generated seed on fixed firmware. Two things reportedly provided a barrier: a BIP-39 passphrase, and seeds built from at least 50 independent dice rolls. Read their advisory directly rather than a summary of it — including this one.
Everybody, regardless of brand: fake "support" accounts follow every incident like this into people's messages within hours. No legitimate manufacturer will ever ask for your recovery phrase — not to check whether you are affected, not to migrate you to safety, not for any reason at all.
Because of what the bug says about the assumptions almost everyone in this space repeats.
COLDCARD's firmware is open source. Anyone could read it. This bug sat in public view for five years, across dozens of releases, in a file that anyone in the world could have opened — and the guard that failed was put there deliberately by someone trying to prevent exactly this.
So "it's open source" did not mean anyone had actually checked. That is the assumption worth retiring, whatever hardware you own. Publishing code makes review possible; it does not perform it. The review still has to be done, by someone, on purpose, on the parts where being wrong cannot be undone.
Which is a job the whole community failed at for five years, not one company — and it is a job worth pointing every tool we now have at, including AI. Not as an oracle that blesses a codebase, but as a force multiplier: something that lets a small number of people read a large amount of code deliberately, starting with the functions where a mistake is unrecoverable. That work is available to anyone reading this. The code is still public.
That is the part worth carrying away, whatever hardware you own.
A wallet's security is capped by the unpredictability of the number it started with. The curve, the secure element, the tamper mesh, the word count — all downstream. If the starting number is guessable, none of it matters.
That number is called entropy, and the rest of this article is about why it deserves more attention than it gets.
In March 2021, COLDCARD's firmware migrated to a new crypto library, and seed generation moved from ckcc.rng_bytes() — which read the STM32's hardware RNG — to ngu.random.bytes().
The new library tried to prevent exactly this. In libngu/ngu/random.c:
#ifndef MICROPY_HW_ENABLE_RNG
#error "get a HW TRNG plz"
#endifCorrect intent. The bug is one word.
MICROPY_HW_ENABLE_RNG is a build-time switch — an on/off setting the compiler reads while assembling the firmware. #ifndef asks "has anyone set this switch?" It does not ask "is the switch on?" And the board config had set it — to off:
// stm32/COLDCARD_MK4/mpconfigboard.h
#define MICROPY_HW_ENABLE_RNG (0)To be clear about what that zero is, since it is easy to misread: it does not mean the random number generator produced zeros. It means the hardware generator was switched off. The switch existed, so the guard was satisfied and the build proceeded without a word of complaint — with the hardware generator disabled.
Seed generation therefore fell through to the software stand-in it inherited from MicroPython — a pseudo-random generator whose starting state came from the chip's unique ID, the SysTick counter and the real-time clock:
pad = UID_low32 ^ SysTick->VAL;
n = RTC->TR;
d = RTC->SSR;A chip ID is a serial number. A clock reading is a timestamp. What is left unpredictable is roughly the microsecond timing of someone pressing buttons.
Later firmware reseeded that PRNG from the secure elements, but Block found the reseed truncated a SHA-256d digest to four bytes and updated a single word of PRNG state:
a = callgate.read_rng(1) # 32 bytes from secure element
b = callgate.read_rng(2) # 8 bytes from SE2
n = ngu.hash.sha256d(a + b)
n, = ustruct.unpack('I', n[0:4]) # only 4 bytes survive
ngu.random.reseed(n)Hashing does not create entropy. Feed 40 good bytes into something that keeps 32 bits and you have 32 bits.
Under Coinkite's stated attack assumptions the estimated candidate search space is roughly 40 bits for Mk3 and 72 bits for Mk4/Mk5/Q. These are model-dependent estimates, not measurements of uniform entropy — Block notes the secure-element reseed distinguishes at most 2³² streams for a fixed fallback state, with timing uncertainty possibly enlarging the practical search. Against the 128-bit target for a 12-word seed, a 40-bit space is not a weakened wallet. It is a list somebody can finish reading.

That is why keys could be precomputed years ahead and the addresses simply watched.
Ask which is safer and you will be told 24, everywhere.
A 12-word phrase encodes 128 bits; 24 words encodes 256. So it looks twice as strong. For an intact, correctly generated Bitcoin wallet it isn't, for a reason unrelated to BIP-39: the best known generic attack on secp256k1 is assumed to cost about 2^128 operations regardless of how the key was chosen. The second 128 bits has nowhere to go. And 2^128 is not "a lot of computers" — it is a quantity of computation with no plausible route to existing.
Not identical, to be precise: 24 words carry an 8-bit BIP-39 checksum against 12 words' 4-bit, some institutional and multisig policies require 24, and other curves or disclosure models can change the maths. What 24 words do not do is raise the ceiling on ordinary Bitcoin spend-key security.
And against a bad RNG, neither length helps at all.
The vulnerable generator was asked for a 32-byte intermediate — seed = random.bytes(32). Output length and unpredictability are separate properties; asking a weak generator for more bytes does not put more entropy in them. Whether an owner picked 12 words or 24, extra length could not manufacture randomness the generator never had. (Coinkite does not break losses down by seed length, so nobody outside the investigation can say what share were 24-word. The point stands without it.)

Back to the planet. The number of words is how many digits you use to write the planet's number down. Randomness is how much of the sky the ship actually searched before choosing it. Writing a short number with extra leading digits does not put the planet further away.
COLDCARD owners who chose 24 words did the more laborious thing, believing it bought them a bigger sky. The sky was the same size either way, because the bug was upstream of the writing-down.
Word count is the size of the bucket. Entropy is what is in it. The bucket is visible and trivial to compare; the contents are invisible and depend on implementation details almost nobody verifies.
Twelve more words transcribed — then again for the second copy, and the offsite copy. Twelve more checked against the screen in order, the check people find tedious enough to rush. Twelve more stamped into steel at double the plate cost. Twelve more re-entered at every practice recovery. Twelve more chances to write one down wrong and find out years later.
Multiply by a userbase: thousands of hours of careful, well-intentioned effort, spent to sit at a ceiling the shorter phrase already reached.
Then the part that is not about time. Effort feels like security, and "I used 24 words" becomes the sentence people say to themselves instead of "I set a passphrase." Only one of those describes work that reportedly helped COLDCARD owners.
A visible, measurable ritual standing in for an invisible property nobody can inspect. The ritual was performed perfectly. The property was absent the whole time.
Public repository, commit 856c966ef.
Hardware RNG, no runtime software fallback. lib/rand/rng.c reads the STM32F2 hardware RNG directly, checks the seed- and clock-error status bits on every read, resets the peripheral if it hangs, and discards the first value after enabling it per §20.3.1 of the STM32F205 reference manual. There is a software path in the file — fenced behind #ifndef EMULATOR, so a normal hardware build excludes it from the binary rather than choosing it at runtime. There is no live branch for a misconfiguration to steer into.
Which raises the fair question: if a build flag decides that, who checks the build? You do. KeepKey firmware builds reproducibly, and the procedure is in the repository README — check out the release tag, run the Docker release script, and compare the hash against the signed binary published on GitHub:
git checkout v6.2.0
git submodule update --init --recursive
./scripts/build/docker/device/release.sh
# your reproduced build:
tail -c +257 ./bin/firmware.keepkey.bin | shasum -a 256
# the signed release downloaded from GitHub:
tail -c +257 ./signed-firmware.keepkey.bin | shasum -a 256The two hashes should match. Note the offset is applied to both files, not just the download: since v6.1.0 the build prepends empty signature slots, so each binary carries a 256-byte metadata header that differs by design. Strip both, compare the payloads. The README documents the versions where reproducibility broke and links the issue, because a verification procedure you cannot audit the failures of is not a verification procedure.
The file header credits SatoshiLabs — LGPL code from the Trezor lineage, public and read for over a decade. In seed generation, that is worth considerably more than novelty.
The device generates 32 bytes (reset.c:86, after validating strength of 128/192/256 bits at line 50). Note the buffer is always 32 bytes whatever mnemonic length you asked for; strength is applied later as strength / 8. Buffer size and mnemonic length are not the same thing — the exact confusion the 12-versus-24 debate runs on.
The protocol also carries a display_random flag: set it, and the device prints those 32 bytes on its own screen before mixing (lines 88–105), which lets you recompute the mnemonic yourself and check the device's work. It is a firmware and protocol capability today rather than a switch in Vault's setup flow — the hdwallet reset path sends it as false. Worth knowing it is there in the protocol you can read.
Then it stops and demands entropy from you (reset.c:124) and hashes both together:
void reset_entropy(const uint8_t* ext_entropy, uint32_t len) { // line 130
sha256_Update(&ctx, int_entropy, 32); // device entropy
sha256_Update(&ctx, ext_entropy, len); // your entropy
sha256_Final(&ctx, int_entropy);
const char* temp_mnemonic = mnemonic_from_data(int_entropy, strength / 8);
The host supplies that from the OS CSPRNG — window.crypto.getRandomValues or crypto.randomBytes (hdwallet-keepkey/src/transport.ts:109-114, sent at 196–200). This is the path shipping Vault uses.
Stated precisely, because the loose version claims far more than the code supports: neither entropy source alone determines the seed under the specified protocol. Assuming the firmware executes it faithfully, either source preserves unpredictability if the other fails or is attacker-chosen. That removes a single RNG as the sole basis of security.
Be clear about the boundary, because it is the one every vendor glosses over. The device receives the host's contribution before deriving, so firmware that lies about running this protocol at all can ignore it, substitute a mnemonic, or leak the result. Two entropy sources defend against a broken generator, not a lying one. Defending against a lying one is what open firmware, a decade-old public codebase and a reproducible build you can hash yourself are for — and, if you want to remove the question entirely, the next section.
Applied here: this construction would not have repaired COLDCARD's faulty RNG. But a healthy, independent host contribution mixed correctly would have stopped that device-side failure alone from collapsing the seed — which is the entire argument for not letting one generator hold the whole of your security.
If you want no chip in the trust equation — a reasonable position, and this incident is the argument for it:
The device then holds a seed it did not generate. Its RNG leaves your threat model.
A BIP-39 passphrase adds an independent secret applied after generation, so it can protect you even if the seed was weakly generated. Its limits matter too: BIP-39 uses PBKDF2-HMAC-SHA512 at only 2,048 iterations, so a weak passphrase can be attacked offline. Malicious firmware can capture one entered on the device; a compromised host, one entered on the host. Every passphrase yields a valid wallet, so a typo silently lands you in an empty one and losing it loses the funds. Use one only if you back it up separately from the seed and have tested recovery more than once.
Return to the point from the top, now that you have seen the code.
The guard that failed was not an oversight by someone who did not care. It was written deliberately, by an engineer who understood this exact risk and put a build-time tripwire in the way of it. #error "get a HW TRNG plz" is what taking the problem seriously looks like. It shipped anyway, and stayed shipped, across five years and dozens of releases, in a file anyone in the world could open.
Look at what the bug actually is, because it explains why it survived. Nothing crashes. Nothing fails to compile. No type is wrong, no memory is misused, no test goes red. There are two functions with the same shape — one reading a hardware chip, one a software stand-in — and a build switch quietly selects the wrong one. Every automated check a project normally runs passes cleanly, because by every mechanical measure the code is fine. It is only wrong in what it means.
That is the hardest class of defect to catch with tooling of any kind, and the easiest for a human to skim past, because there is nothing to notice. The failure was not a lack of eyes, or of skill, or of good intentions. It was that nobody's attention landed on the one function where being wrong is unrecoverable.
That is the specific danger of entropy code. It is the highest-value target in a wallet and the least likely to be re-read, because it runs once, silently, in the first thirty seconds of a device's life, and it fails invisibly. Broken signing throws an error. Broken randomness produces a perfectly valid wallet that works flawlessly for years. Nobody files a bug report for a seed that came out wrong.
Which is why "someone would have noticed" is not a security property, and why the reasonable response is not to trust a different brand harder — it is to hold less of your safety in any single place. That is the rest of this article.
Now the honest proportion. This incident is a firmware failure, and firmware failures are rare. If you zoom out from the headlines to what actually empties wallets year after year, the list is not exotic:
None of that is a protocol weakness. Bitcoin did not fail. The device did not fail. The process around it did.
This is why the useful response to a firmware bug is not "buy a different brand." One vendor's mistake is replaced by another vendor's mistake, and the failure mode you actually face stays exactly where it was.
The structural answer to "what if my vendor ships a bad build?" is to stop having a single vendor. In a 2-of-3 multisig with keys on hardware from three different manufacturers, a seed-generation flaw in any one of them costs you nothing: the attacker holds one key and needs two.
The word doing the work there is multi-vendor. A 2-of-3 built from three devices by the same maker, on the same firmware, shares the same bugs three times over — this incident derived every affected seed the same way, so a quorum of affected COLDCARDs falls exactly as fast as one. Multisig spreads the key risk; only multiple vendors spread the firmware risk.
That genuinely removes the failure this article is about. It also adds the failure mode from the previous section, and we should say so plainly rather than sell it as free. Multisig has taken more coins through botched backups and lost descriptors than firmware bugs have taken through bad entropy. Complexity you cannot operate confidently is not security — it is a slower way to lose.
So the honest recommendation is conditional, not universal:
If you go that route, KeepKey works as one of the signers in a multi-vendor quorum through Sparrow or Electrum, and if you point Sparrow at your own Bitcoin Core or Knots node, no third party learns your addresses either. That combination — your keys on hardware from different vendors, your coordinator open-source, your chain data from your own node — is about as close to no-single-point-of-failure as self-custody gets today.
Keep it simple enough that you can still do it correctly in ten years, tired, under stress, or with someone else following your instructions after you cannot.
Entropy is the only part of a hardware wallet that cannot be patched later. Firmware updates, apps get replaced, transports get swapped. A seed generated from a guessable number stays guessable forever — quietly, until the day it holds enough to be worth taking.
Sources: Coinkite technical backgrounder · Block engineering analysis · KeepKey references from the public keepkey-firmware and hdwallet repositories at the commits cited.