DEV Community

Breach Protocol
Breach Protocol

Posted on • Originally published at groundtruth.day

A preprocessor typo cost a bitcoin wallet half its randomness

Coinkite has disclosed that COLDCARD hardware wallets spent years generating bitcoin seeds from MicroPython's ordinary software random number generator instead of the dedicated hardware chip the device was designed around, cutting the effective search space on older units from an intended 128 bits to roughly 40. The cause was not a cryptographic mistake but a build error: a preprocessor guard that tested whether a setting existed rather than what it was set to. Updating the firmware does not repair a seed that was already made.

Key facts

  • The number: an estimated 40 bits of effective search space on Mk2 and Mk3 units, about 72 bits on Mk4, Mk5, and Q, against a 128-bit design target.
  • When: disclosed July 30, 2026, updated August 1; the defect entered the seed path with a library migration in March 2021.
  • Who: Coinkite, maker of the COLDCARD wallet. TAPSIGNER, OPENDIME, and SATSCARD are unaffected - different codebases.
  • Primary source: Coinkite's Technical Deep Dive into the Entropy Issue and its security advisory.

A bitcoin wallet's seed is the whole wallet. Everything else - addresses, keys, signatures - is derived from that one starting number, so the only thing standing between an attacker and the funds is that the number was picked from a space too large to search. COLDCARD was built on exactly that premise, with a dedicated hardware true random number generator and, deliberately, no software fallback.

The fallback got linked in anyway. In 2021 Coinkite moved its elliptic-curve maths to the library Bitcoin Core uses, which meant pulling in a supporting MicroPython library called libNgU. Seed generation moved from one function to another, and the new path resolved a symbol named rng_get() to MicroPython's built-in general-purpose generator rather than COLDCARD's hardware implementation. Coinkite's summary of the migration is unsparing: "The cryptographic choice was sound. The integration was not."

The reason nobody caught it is the part every engineer should read twice. The guard meant to prevent this used #ifndef, which asks whether a macro is defined at all - and Coinkite had defined the macro, setting it to zero, believing that turned the software generator off. Because it was defined, the safety check never fired. Because both implementations had identical function signatures, the linker happily accepted the wrong one and the build succeeded. Reviews confirmed the correct hardware code was present in the binary; nobody checked which of the two the seed path actually reached. The founder's own account of finding it is blunt: "The bulk of randomness on the COLDCARD was coming from a PRNG that I didn't know was actually in the source code base... At the same time the carefully crafted TRNG code I wrote was being used, but just by chance, and only for less important things."

Picture a bank vault with a genuine hardened door installed and photographed for the brochure, while the doorway everyone actually walks through is a second, ordinary door a contractor fitted next to it. Every inspection verified the good door exists. None traced which one the customers used.

The AI angle is where this story gets uncomfortable, and it is important to state precisely what Coinkite claims. It does not claim to know an attacker used a model. It reasons from the fact that its firmware has always been public: "The COLDCARD source code has always been open and publicly available, so we have to assume that someone used AI to review previous versions of our firmware and stumbled upon this issue." Then it volunteers the detail that makes the story: "A few weeks ago, we used one of the best available AI models to review our code for security issues, and it did not find this bug or anything serious." Its conclusion: "Both attackers and defenders have the same AI tools, but today it did not help us, and only helped the bad guys."

That last line is the finding worth carrying forward, and it is a defensive one, not an attribution. Machine code review is now cheap enough to run across every published firmware image in a category - but the same review run by the vendor, weeks earlier, on the same code, returned nothing. Symmetric capability is not symmetric outcome. The attacker only needs one model, one pass, to get lucky on one target; the defender needs the review to be reliable across everything they ship. This is the same asymmetry that makes prompt injection so stubborn and that showed up when a red team broke agent deployments in the overwhelming majority of attempts - and, in reverse, when an attacker's own agent leaked his operation.

The honest caveat: this is a systems bug, not an AI story dressed as one. A link-time symbol collision between two same-signature functions is a class of defect that predates language models by decades and would have been found by an end-to-end reachability check, not by a smarter reviewer. Independent analyses have since been published - Block's engineering team has its own writeup - and the practical guidance is unchanged by any of it. Affected users must generate a new seed on fixed firmware and move their funds; the exception Coinkite grants is for seeds created with at least 50 independent private dice rolls, or protected by a strong, unique passphrase. Coinkite says it destroyed its remaining vulnerable inventory once the issue was confirmed.


Originally published on Ground Truth, where every claim is checked against the primary source.

Top comments (0)