Lessons from the Coldcard incident
The Coldcard incident showed how a subtle firmware flaw turned supposedly unpredictable recovery seeds into something attackers could realistically guess and remained undetected for five years. The broader lesson is that security has to come from continuously reviewed, tested, verifiable engineering that fails safely when something goes wrong; it cannot rely on certifications, rituals, or users doing everything perfectly.
On July 30, 2026, bitcoin began moving out of wallets created on Coldcard hardware. Blockchain analysts at Galaxy Research mapped the first wave, roughly 1,200 addresses emptied in about forty-one minutes. Later waves took the total to around 1,367 BTC, some $88 million, across roughly 4,585 addresses (other estimates run even higher). Galaxy noted that the third wave may not share the same attacker with the first two.
Investigators at Block and elsewhere traced the cause to a firmware flaw that had been in the code since 2021. On affected devices, the recovery seed was created without using a hardware random number generator. Coinkite has published an advisory and has since released emergency firmware.
First things first: Trezor devices are not affected. We said so on the day the news broke and have since published a short FAQ covering the question in detail. One important caveat is that the flaw follows the wallet backup (recovery seed), not the device. If your seed was generated on an affected Coldcard, it remains at risk no matter where it lives now, including if you have since recovered it onto a Trezor. In that case, move your funds to a freshly generated wallet. If you own a Coldcard, follow Coinkite’s advisory.

This event has shaken the industry because the people who lost funds were careful. They bought a dedicated security device and did everything it asked of them. An incident like this always produces loud conclusions, and the loudest are rarely the right ones. The only way to take the right lessons from it is to understand precisely what happened. So this post does two things:
- It explains how a hardware wallet’s seed became guessable and why nothing caught it for five years.
- It draws the lessons about how security-critical software has to be built, and about what genuinely protects a wallet versus what merely looks like it does (security theatre).
How a wallet becomes guessable
What makes a recovery seed secure is that it is a single possibility out of a number so large that nobody can try them all.
A standard 12-word seed is one of 2¹²⁸ possibilities. To put that number into perspective, if the entire Bitcoin mining network were repurposed to guess seeds instead of mining blocks, and could somehow check seeds as fast as it computes hashes, which it could not come close to doing, working through 2¹²⁸ possibilities would still take it roughly the age of the universe.
Everything depends on the seed being genuinely random, and that is exactly what broke.
The story begins with a license… Coldcard’s early firmware was built on GPL-licensed open source cryptographic code originally written for Trezor, which Coinkite maintained in its own repository. By then that code was mature and widely used across the hardware wallet industry. The defining rule of the GPL license is that whoever builds on GPL code must publish their own derived code under the same free terms, so that improvements flow back to everyone. That rule is also what gives the code its security value, because every company shipping it has engineers reading it, testing it and fixing it. If an engineer at one company finds a vulnerability, it is disclosed to everyone who uses the same code and they coordinate on the fix. For years Coldcard benefited from exactly this arrangement. Then another company built a product on Coldcard’s published code, precisely as the license permits and intends. Coinkite decided to leave the arrangement and adopt a restrictive license. Leaving the GPL meant that every line of GPL-derived code had to be rewritten from scratch, including the cryptographic core.
The rewrite landed in the firmware on March 1, 2021. The software platform Coldcard’s firmware is built on, MicroPython, comes with its own driver for the chip’s hardware random number generator, together with a configuration setting that turns that driver on or off. Coldcard had turned it off back in 2018, because the firmware had its own driver for the same chip and that was the one meant to be used, but the choice carried a hidden cost. When the platform’s driver is turned off, the platform does not leave its standard randomness interface empty. It quietly substitutes a simple software formula, a routine suited to simulations and games, seeded from the chip’s fixed serial number and the clock. From 2018 on, the firmware carried a loaded trap. Any code that asked the platform for randomness in the standard way would receive numbers that only looked random. For three years the trap stayed dormant, because nothing that mattered asked. Wallet creation went through Coldcard’s own driver, straight to the hardware.
The new cryptographic library needed randomness, and instead of being pointed at Coldcard’s dedicated driver, it asked the platform. It even contained a safety check for exactly this situation. Here it is:
# ifndef MICROPY_HW_ENABLE_RNG
# error "get a HW TRNG plz"
# endif
In plain language, if the hardware random number generator setting is undefined, then stop the build with the message “get a HW TRNG plz”. But undefined is not the same as disabled. Coldcard’s build had the setting defined. However, it was defined as disabled. The check didn’t trigger an error, the build succeeded, and from that day the code that created wallets drew its randomness from the software formula using only the serial number and the clock. A highly reliable hardware random number generator was present and functional in every device. The seed generation code simply never asked it for a number.
The first affected firmware reached users as v4.0.1 in March 2021. The seeds it produced looked perfectly random. Every statistical test would pass. There was no error, no warning, and no way for a user, a reviewer or an auditor to notice anything from the outside. It stayed that way for five years.
The consequence was that instead of 2¹²⁸ possibilities, seeds generated on the affected Mk2 and Mk3 firmware were, under realistic assumptions, narrowed to a search space of at most a few trillion, and in the worst case, they were fully deterministic. A trillion sounds like a lot, but checking a trillion candidate seeds against the public blockchain is a weekend of work for ordinary computing hardware. That is the difference between “the age of the universe” and “done by Monday”. On later devices a small amount of genuine randomness was mixed back in, raising the ceiling but not to anything resembling the 2¹²⁸ the wallet was supposed to have. It became merely expensive to break instead of trivial.
Where were the safeguards?
A bug this destructive is supposed to be caught long before it spent five years in production. Modern engineering does not rely on programmers never making mistakes, it relies on layered safeguards. Mistakes are made every day, in every codebase, including ours. The real question is why every safeguard failed to detect it.
Changes nobody reviewed
The public commit history shows how the weak randomness code entered the firmware. The faulty compile-time check lived in a separate cryptographic library maintained in its own repository. The commits that introduced it there were pushed directly, with single-character commit messages and no pull request. A pull request is the mechanism by which a proposed change is put up for others to review before it is applied. That library was then wired into the firmware as part of the larger relicensing rewrite.
A paid audit that missed it
In March 2022, a year after the bug shipped, Coinkite commissioned a paid security audit covering, among other things, the new platform’s random number generation. If anyone in that loop had asked, where does this generator actually get its bytes, they would have found the software formula sitting underneath.
As an outcome of the audit, an improvement was nevertheless implemented where genuine randomness from the device’s secure elements would be fed into the random number generator at every boot. Had this improvement been implemented properly, it would have mitigated the bug. Unfortunately that was not the case. Real randomness went in, but the interface it went through could only ever overwrite a single 32-bit word of the generator’s internal state. Genuine high-quality randomness from two independent sources was sliced to four bytes. That is about four billion possibilities, a number a single computer can easily count through.
def rng_seeding():
# seed our RNG with entropy from secure elements
import callgate, ngu, ustruct
a = callgate.read_rng(1) # SE1
b = callgate.read_rng(2) # SE2
n = ngu.hash.sha256d(a+b)
n, = ustruct.unpack('I', n[0:4])
ngu.random.reseed(n)
Here, the process failed a second time. A fix born of an audit is new code, which needs the same scrutiny as the code it fixes, more if anything, because it changes the code the audit was concerned with. Nothing in the public record suggests this one ever went back in front of expert eyes, or indeed in front of anyone’s. The code snippet above is the strongest evidence that it did not. The fix reached users inside a pull request of more than a quarter of a million lines merged by its author without any publicly visible review. The line that does the slicing sat in plain sight the whole way.
A design without supervision
On a Coldcard, unless the user opted into dice rolls, all of the randomness came from inside the device, from sources under the control of the same firmware that failed. The problem was not the number of sources of randomness or their quality. A device can gather from three high-quality internal sources and still produce a weak seed if a code path quietly bypasses all of them, which is close to what happened here. The problem is that by default every source lived on the same side of the airgap as the bug. When the code failed, nothing outside the device could contribute to the result or check how it was made without involving user effort.
The pattern across all failed safeguards is the same...
- Reviews were skipped or thin at the moments that mattered.
- An audit came through once and moved on.
- Checks existed but did not run on their own.
Security is a process, not a promise
That is the standard we hold ourselves to at Trezor. It is also why we have never put much faith in security certifications. A certificate, like an audit, records what someone examined once, not what the code does after the next change. And it carries no guarantee of finding anything. Examinations of this kind work through catalogues of known vulnerability classes, and a flaw that does not look like anything in the catalogue slips through unnoticed.
Assume any part can fail, including our own randomness. Since 2014, every wallet created on a Trezor has been derived from at least two independent contributions of entropy, one from the device and one from your computer, combined so that either alone keeps the seed unpredictable. The device side has only grown stronger with each generation. Current models mix in randomness from secure elements as additional independent sources. But again, the property that speaks most directly to this incident is not the number of sources, but verifying that the sources are used. On a Trezor, the creation itself is checked from outside. The device cryptographically commits to its contribution before it ever sees the computer’s, and Trezor Suite then verifies that both made it into the wallet, automatically, in the background, on every setup. A manual version of this check has existed in Trezor from day one. Today it asks nothing of the user at all.
Inside the device there is deliberately nothing to fall back to. The production firmware contains a single path to randomness, straight to the hardware, and if any source fails to deliver, seed generation refuses to proceed. A Trezor that cannot reach its randomness sources does not generate a wallet. Our firmware is built on the same software platform as Coldcard’s, so we faced the same situation with a platform that offers its own randomness driver next to our own dedicated one. We resolved it the other way. The platform’s software randomness is compiled out of the build entirely, so the interface a library could mis-bind to does not exist in a Trezor.
Every change reviewed and tested. In the Trezor-firmware repository, every change is reviewed by someone other than its author before it can become part of the firmware, enforced technically, not by convention. Every proposed change must pass the full public test pipeline before it can be applied. Releases are built reproducibly, so anyone can confirm that the firmware their device installs corresponds exactly to the source code the world can read, and signing a release requires multiple keys held by different people, so no single person can ship firmware.
Studying this incident also changed our own checklist. We are adding further tests that assert the provenance of every byte of entropy used in seed generation, from the hardware peripheral to the protocol, so that a misrouted generator fails our pipeline instead of our users.
Pay the people who prove you wrong. Every codebase has bugs, ours included. The question that decides an incident like this one is never whether bugs exist. It is who is being paid to find them first. Our bug bounty program pays for valid findings scaled by severity. We have paid researchers for real findings for over a decade, including findings that were uncomfortable for us, and we publish what they found. We are proud of every scar in that history, because each one is a vulnerability that was fixed through disclosure instead of causing harm. Our terms exclude no one. There is no clause about competitors, no clause about professional labs, no judgment of the sender. The reward depends on the bug, not on who reports it.
That question leaves a public record on the other side, too. Researchers who disclosed serious Coldcard vulnerabilities have published their own accounts of what the process produced (2019, 2020, 2021).
Rituals and design
Rituals protect the people who perform them correctly. Solid design protects everyone.
Ritual makes seed generation look secure. Cut the cable to the computer, hand the user dice, let them roll fifty times, or ninety-nine for maximum strength, and type in the results. It feels like security, because it is effort you can see. In this incident, for the users who did it, it worked. It just so happens that their rolls were hashed together with the device’s output, and a strong contribution mixed with a weak one still yields a strong result, so their seeds were never guessable.
It is easy to take the wrong lesson from that. Dice did not beat electronics. The hardware generators in those devices worked, but the code that created those wallets simply never used them. A single sound source, actually used, would have produced a perfectly good seed. The dice feature’s real virtue is that its arithmetic is auditable, so anyone can confirm the device derived the seed from the numbers it was given. No number of additional sources helps if the code does not use them. Only verification catches that.
To check the dice arithmetic without endangering a real wallet, the user rehearses it with throwaway rolls, a throwaway seed and a verification script on a separate computer. For rolls that will actually be used, Coldcard’s documentation warns against checking actual dice rolls on a normal desktop system. The rehearsal itself is sound, since the device cannot know whether it is being tested, so firmware that mishandled the rolls would give itself away on the throwaway run. Our own entropy check rests on the same logic. The difference is that on one device, it was homework, performed by the minority who knew it existed, on the other, it happened automatically for every user including the ones who had never heard of it. Coldcard left it to the user whether enough rolls were made. The device warned about too few rolls and then proceeded, and the documentation acknowledged that weak seeds were possible if the warnings went unheeded. Users have lost funds exactly this way, years before this incident. In skilled hands, dice are a neat feature. In unskilled hands, they are an accident waiting to happen. Nobody should conclude that a good seed requires dice. What it requires is randomness whose use can be verified. Generating randomness should not have to fall to the user.
The same goes for the airgap. At the moment of seed creation, it protects the user from nothing, because a correctly designed protocol cannot be weakened by an untrusted computer. The computer’s contribution can only ever add randomness, never remove it. What the airgap can offer is a sliver of protection elsewhere, but what it costs is the independent contribution of randomness to the seed, the straightforward way of checking the device’s work from outside, and a great deal of usability.
None of this is to say that an airgap, or dice, cannot add something in the right hands. They can, and the benefits of features like these deserve to be weighed carefully against their limitations and complexity, which is exactly how we weigh them when considering what to build. What they cannot be is the foundation. The foundation must be solid engineering. Security at the expense of usability defeats itself, because every demand placed on the user is a chance for an unrecoverable mistake.
This is not a new idea. In 1883, the cryptographer Auguste Kerckhoffs formulated six design principles of secure systems. All security engineers remember the second principle, that a system must remain secure even when everything about it except the key is known. It is the principle that underwrites open design to this day, and Trezor swears by it. Our entire firmware stands in the open because of it. Fewer remember the last of the principles, which states that the system must be easy to use, demanding no mental strain and no long list of rules its users must know and follow. He wrote that more than half a century before the first working computer existed, let alone the first hardware wallet. Usability is not merely a convenience. It is a security property in and of itself, and it has been on the list from the beginning.
The strongest security is not the kind you perform. It’s the kind you can’t get wrong.
A note on open source
Coldcard’s code was publicly readable for the entire five years the flaw existed. It is fair to ask what openness is worth, if this could happen anyway. One can easily jump to the naive conclusion that the Coldcard incident is proof that open source fails to provide a security advantage. However, that conclusion is just as ridiculous as claiming after a fatal car accident, where the driver was wearing a seat belt, that seat belts fail to provide a security advantage. Nobody ever claimed that openness guarantees security, just as nobody claims a seat belt guarantees surviving every crash.
What openness provides is the possibility of unlimited verification, by anyone, without permission. Whether verification actually happens depends on machinery that comes in separate pieces, and a project can hold any combination of them:
- Reuse by other builders whose products exercise the code.
- Public review enforced before a code change is applied.
- Checks that run on their own.
- Independent audits.
- A bug bounty program where finding a serious flaw is worth a professional’s time.
What matters most is which pieces operate continuously, because scrutiny that arrives as occasional events can pass straight over a flaw for years, as it did for Coldcard, while attackers study the same code continuously. For them, it is their job.
In a closed design, the question this whole post is about, how is the random number generator actually wired to the seed, can only be answered by reverse engineering the compiled binaries. Honest reviewers rarely have a reason to spend weeks decompiling a product whose source code they cannot access. Attackers, on the other hand, have good reason. It is their business model, and modern tooling keeps lowering their cost. Closed source does not remove scrutiny, it filters it, discouraging the people who would tell you what they found and leaving the people who won’t.
Closing
If you own a Coldcard, follow Coinkite’s advisory. If you own a Trezor, your device is not affected, unless the seed on it was originally born on an affected Coldcard, in which case, move your funds to a freshly generated wallet. And if you are curious what your Trezor and Trezor Suite quietly verified when your wallet was born, read the entropy check article.
There is a larger reason all of this matters. If the industry accepts the narrative that complex rituals are prerequisites for holding one’s own keys safely, then self-custody becomes something so intimidating to do correctly that many will rather hand over that great gift of freedom and lay their keys at the feet of a custodian. That must not happen, and it does not have to. Done right, the strongest protections are the ones the user never has to think about. Self-custody stands at the heart of Bitcoin. Lose it, and Bitcoin degrades into just another fiat.



