BrokenRNG: Entropy Boogaloo.
Did switck place a bug in the entropy generation for later retrieval? Was the bug really a feature written by CoinKites CTO under a nym?
Entropy Boogaloo
The Heist was forshaddowed 5 years ago.
I have spent enough years reading incident postmortems and this secendario usually plays out: The company draws a line between what we meant and what we shipped, then they ask you to treat the gap as something that “just happened” “totally unforeseable. And to be honest from the get go here sometimes their right. Crazy things happen. Chaos is real, “Murphy error” as one boss early in my career would often say. Entropy is free, tired engineers exist, and accidents happen. Although my grandfather used to say “Accidents don’t happen, they’re caused”.
So when a hardware vendor who advertizes their product as “ultra-secure” not only mentions an attack class in public, they give a detailed explanation in their own words, of what that attack is months after shipping the defect that matches it, it makes my spidey senses tingle a bit. This doesn’t seem like a freak random tornado that blew threw a datacenter leading to catasrophic losses, it seems more like a trick they played on us.
On 10 October 2021, somebody asked the official Coldcard account what a retirement attack was. @COLDCARD answered:
It’s when the project makers could have a “bug” in the entropy generation for later retrieval.
The Project Makers… The library that generated Coldcard seeds was written by Peter D. Gray, Coinkite’s co-founder and CTO, publishing under the GitHub name switck. Anyone can verify it: import his key from github.com/doc-hex.gpg, clone switck/libngu, run git verify-commit on any of the 129 commits. They come back signed by Peter D. Gray. The switck account has never published a key of its own. Outsiders spent years treating the repo as an unaudited stranger. Insiders knew there was no stranger.
…The “bug” in the entropy generation. Commit f19de05, 28 January 2021, commit message x, one character. It shipped a compile-time guard that reads #ifndef MICROPY_HW_ENABLE_RNG followed by #error "get a HW TRNG plz". Somebody wrote a safeguard whose entire purpose was to stop a build that lacked a hardware random number generator, and wrote it in the one form that cannot fire when the macro exists and is set to zero. Which is exactly how Coldcard’s board config sets it.
…for later retrieval. 1,082 BTC out of 1,195 addresses, forty-one minutes, 30 July 2026.
Every clause of the sentence the official @COLDCARD account on twitter wrote is now satisfied by Coinkite’s own product.
I’m not a State Prosecutor, and this isn’t a courtroom. I’m free to speculate, scrutinize, and ask the question: Did Peter Gray “Switck” plan a retirement attack? I’m not going to wave this question away with “oops, config flag.” I haven’t seen a serious attempt from Gray or anyone at Coinkite to answer this question. As a matter of fact - has anyone seen Peter Gray since this went down?
Before I continue, here is the chronology in one picture: 1,961 days from the commit that broke seed generation to the sweep that proved it. Code, words, warnings, loss, and the belated fix. The shaded band is the exposure window. Treat the diagram as a map of when; the sections below are exactly what and when happened. Credit and much thanks to @ChuckSRQ on Twitter for sharing his research.
What a planted backdoor would look like
Suppose you wanted to build a retirement attack into a hardware wallet and survive scrutiny. Work backwards from the constraints. I am not saying this is what happened (I kinda am). I am saying: if you were designing for that outcome, these are the constraints the design would have to clear.
You cannot simply write bad randomness into the firmware because the firmware is the thing the diehards will scrutinize. You need the weakness to live in a dependency-> one step removed: where reviewers assume somebody else has checked it.
You can’t have that dependency obviously belong to you because then it would inherit your review burden. It should look external.
You need a paper trail showing it was adopted at arm’s length.
You need the mitigation to exist and be optional, so that the record shows you offered protection while the default population stays harvestable.
You need the change to arrive inside a larger change, so no reviewer reads it in isolation.
And you need a reason for the whole migration that is commercially legible, so nobody asks why you rewrote working cryptography.
Now read the record.
The weakness lived in libngu, a dependency. The dependency belonged to Coinkite’s CTO under a name nobody connected to him for six years. On 21 October 2020 at 14:20 UTC, doc-hex opened issue #7 on switck/libngu asking “Would you accept code from us when we give it as PR?” and closed it at 14:28. Change made and eight minutes later:change approved. One guy with two accounts and a public record showing arm’s length adoption of an outside MIT library. Dice rolls were documented, recommended at ninety-nine, and made entirely optional. The seed path change arrived inside a 120-file commit that also deleted the GPL licence file. And the commercial reason was immaculate: Foundation had forked the GPL firmware for Passport in July 2020. Novak said two days later that he regretted the licence and by March 2021 the last GPL code was gone.
The artifacts produced by this sequence are indistinguishable from the artifacts a deliberate operation would produce. No part of the sequence had to come out this way. Incompetence could have still got us here. But there are a lot of “incompetence” and “coincidence” steps in this chain of events that eventually lead me to a stopping point. Where you have to start to wonder if the square shape goes in the square hole and the threat matches the threat model the brand itself named in a reply guy comment 5 years ago.
The parts that keep not making sense
The boring explanation is that hey, coders sometimes write #ifndef when they mean #if all the time. C programmers will tell you that idiom is muscle memory not some shady spycraft. It’s Friday, we’re supposed to ship, a macro already defined as zero is sittig on the board. A safeguard that never got triggered. The user story doesn’t need to be a meeting, oops typo gets shipped… Entropy is free.
Two details still refuse to sit still under that reading.
The safeguard was written and then defeated by one character. Nobody writes #error "get a HW TRNG plz" casually. That line exists because its author was thinking specifically about the failure mode of a board without hardware randomness. Then the guard was written in the form that cannot detect the case where the macro is present and false. The concern was live in the author’s head and the check was built inert. This is not a normal typo.
The macro was never corrected, even in an emergency. MICROPY_HW_ENABLE_RNG is still (0) today. The 31 July 2026 fix works by renaming MicroPython’s fallback symbol to error-do-not-want-this at compile time and skipping the file. That is a link-time barricade around a line nobody was willing to change. The charitable read is time pressure: flipping the flag mid-incident risks unknown build-side effects, so you wall off the bad path instead. Ok great to notice that after eighty eight million dollars left the wallets of users who trusted your brand. BTW the one character that would have made the original safeguard mean what it said still hasn’t been flipped.
Each of those is explicable on its own. Together in one codebase they describe a five year pattern of the entropy path being handled differently from everything else in the product.
Four warnings
Between the commit and the sweep four separate parties told Coinkite something was wrong with libngu or with the seeds it produced.
April 2021: five weeks after the commit a Telegram thread with Novak in it flagged that all crypto and BIP39 code had been swapped out of audited TrezorCrypto for a six-star repo with a bad commit history.
January and February 2024: four users reported being blocked by @nvk or @COLDCARDwallet after raising the entropy question.
April 2024: Foundation publicly declined to ship dice rolls at all stating “there was no easy way to assess seed entropy”.
May 2025: James O’Beirne traced the RNG into libngu, named the hardcoded yasmarang constants, told them to rip the whole thing out, and was told that if something were wrong they would already know about it by now.
A few days later a Signal group appeared called “LNGU Clean up.” Members shown as “n”, “Doc”, “andres”. Messages set to expire in four weeks. Nothing shipped for fourteen months.
Four chances and zero patches. One of the dismissals came from the man who wrote the code about a library nobody knew he wrote under a nym.
And when Coinkite published a twenty-three event disclosure chronology on 4 August 2026 presented as the record of security research and professional reviews affecting Coldcard the chronology ran from December 2024 straight to September 2025. O’Beirne’s report is in the gap. Chronologies are where organizations decide what counts as history. The gaps in their published chronologies are the real content.
About the state-sponsored theory
This theory has the right shape too: state supply chain work is patient, positioned years ahead, and would harvest only when the position was about to be lost.
The planning was professional, the targeting hit dormant wallets first, the seeds were precomputed rather than derived live. Bitcoin Asset Research reads the mismatch between that discipline and the sloppy consolidation as an actor who has no fear of law enforcement.
A state actor did not need an insider if the bug was findable from public source. And if you are already convinced that Coinkite’s CTO planted it intentionally, adding a foreign service is a second, separate claim with its own evidentiary burden and nothing carrying it.
I’m not convinced either way, I’m just asking questions. I definitely lean towards the insider threat vector, if you hadn’t noticed. There’s one thing I can say for sure:
What happened to Coldcard is a supply chain compromise. That is the category regardless of who introduced it or why. The defining property of a supply chain compromise is that the artifact looks the same whether the hand was hostile or careless. The people whose coins are gone were harmed identically either way.
What would settle it
I can’t entirely resolve this from outside, because I think every document that would resolve it belongs to Coinkite.
Here’s what I would ask them for:
*The “LNGU Clean up” thread, whatever survived four-week expiry. Whatever review followed O’Beirne’s May 2025 report and who read it.
*The commit history around anyone who touched rng.c after that report.
*Who made the decision, in the middle of an emergency with eighty-eight million dollars gone, to route around MICROPY_HW_ENABLE_RNG rather than set it to one.
Roughly six hundred attacker addresses are with federal investigators. Coinkite says it’s cooperating. Cooperation is cheap. To give them some credit they have apologised, published a postmortem, shipped a fix that needed a second round of patches within days, published a chronology that omits the warning that mattered. Oh yeah and they’ve offered everyone who bought a Coldcard… nothing.
Coinkite explained what retirement attack was to their fanboys in October 2021. Their own firmware contained one for seven months and there’s still no public account of who decided that was fine.
Do not attribute to a bad macro what is adequately explained by a product that matched the vendor’s own definition of an attack.
Incompetence remains a hypothesis. Run it against the receipts.