COLDCARD Entropy Incident (2026)

The largest hardware-wallet exploit on record: a five-year-old firmware regression routed COLDCARD seed generation through a deterministic software fallback, and attackers enumerated the seeds from public blockchain data — no device contact, no phishing, nothing an air gap could help with. Between 30 July and ~6 August 2026, one or more attackers swept bitcoin from wallets whose seeds had been generated on COLDCARD signing devices since March 2021. Galaxy Research’s high-confidence tally as of 14 August: 1,778.84 BTC (~$112.7M) from more than 8,600 addresses; the candidate-inclusive figure reaches 2,417 BTC (~$153M).1

The bug: two interacting defects, not one

The popular account — “a one-character bug” — undersells it. The failure chain, verified against the pinned source, has two distinct defects that compound:

  1. The macro was set to the wrong value. stm32/COLDCARD_MK4/mpconfigboard.h:78 sets #define MICROPY_HW_ENABLE_RNG (0), with the comment “We have our own version of this code.” COLDCARD did — a genuine STM32 hardware-RNG wrapper (rng_get_or_fault(), exported to Python as ckcc.rng_bytes) that reads the RNG peripheral with a 10ms timeout and hard-fails on error. But disabling MicroPython’s RNG did not do what the authors expected downstream.2
  2. The guard tested the wrong property. libngu’s ngu/random.c binds its entropy source with extern uint32_t rng_get(void); and guards it with #ifndef MICROPY_HW_ENABLE_RNG / #error "get a HW TRNG plz". #ifndef tests definedness, not value. The macro is defined (as zero), so the guard stays silent, the build passes, and the linker resolves rng_get() to MicroPython’s software fallback: a Yasmarang PRNG seeded exactly once from UID_low32 ^ SysTick->VAL, RTC->TR, and RTC->SSR. libngu then XORs that stream with a second Yasmarang initialized from public, hardcoded constants (pad=0x0a8ce26f, n=69, d=233) — XOR of two reproducible streams is reproducible.3

generate_seed() hashed 32 bytes of this with SHA256d and asserted only that more than 4 distinct byte values appear. The output is indistinguishable from a strong seed. That is the cruelty of entropy failures: they are invisible at the output.4

Effective search space. Coinkite’s post-mortem estimated ~40 bits (Mk3) and ~72 bits (Mk4/Q/Mk5, where a secure-element reseed mixed in additional entropy — but hashed 40 bytes down and passed only 4 to reseed(), capping the secure contribution at 2^32). Block’s stricter ceiling was 2^40.7 / 2^73.3. Wizardsardine’s deeper modeling — exploiting the fact that the STM32 unique-ID word encodes wafer coordinates, so most of its 32 bits carry little information — puts the practical Mk3 candidate space near 2^22 (about 4.5 million states), sweepable for ~$0.25 of rented GPU time, and shows the RTC contribution is worth zero on Mk2/Mk3 (the RTC points at an oscillator the firmware never starts).5

Blast radius beyond seeds. The same ngu.random stream fed paper-wallet keys, Seed XOR masks, ephemeral seeds, clone/USB/Key-Teleport ECDH keys, Web2FA secrets, and generated passwords — features broken even for users who generated their main seed safely with dice. Still sound: ECDSA nonces (RFC 6979), backup encryption, and the dice-only path. Users who supplied ≥50 independent dice rolls contributed ≥128 bits the software could not remove, and were protected.6

The timeline

  • 1 March 2021 — commit b18723dd (“First pass w/ libNgU”): 120 files, +2,766/−2,722 lines. It simultaneously deleted the GPLv3 COPYING file, removed the battle-tested trezor-crypto submodule, added switck/libngu, and moved seed generation onto it. The defect entered the code here — 1,978 days before the fix.7
  • 17 March 2021 — firmware 4.0.0 ships the regression to users.
  • May 2025 — James O’Beirne, auditing exactly this code, warns Coinkite; he is dismissed with “we would have known.”
  • May–June 2026 — Coinkite’s own AI-assisted review triages 85 candidate findings without catching the bug — 34 days before the sweep.
  • 29–30 July 2026 — Wave 1: 1,196 addresses fully drained in ~41 minutes for 1,082.65 BTC (~$70M), hardcoded 30 sat/vB fee, no change outputs, high-value wallets hit first (~$30M in the first ten minutes) — a prepared, ranked operation. Coinkite’s public advisory follows ~30 hours later.8
  • 30 July — Block publishes the first detailed technical analysis; Coinkite publishes its advisory and a candid technical backgrounder (“the bulk of randomness on the COLDCARD was coming from a PRNG that I didn’t know was actually in the source code base”).
  • 31 July–2 Aug — fixed firmware for every model and release track (Mk2/Mk3 4.2.0, Mk4/Mk5 5.6.0, Q 1.5.0Q, Edge 6.6.0X/QX); shipments halted and affected inventory destroyed. Waves 2 and 3 (~76 BTC, ~207.7 BTC) follow with different fingerprints — opportunists reproducing the enumeration after disclosure made the method public, aided by frontier AI models that developers used to reproduce the attack within minutes of publication.
  • ~6 August — confirmed attacker activity stops; the tally keeps climbing as victims come forward (200+ direct reports to Galaxy). As of 14 August, 1,531 BTC of the ≥1,778 stolen remained unmoved; of the ~246 BTC that moved, ~65% went into CoinJoin and ~35% onward via peel chains. Galaxy estimates at least 15 separate attackers by 4 August — after disclosure, the exploit became a commons.9

Response: operationally strong, epistemically evasive

The good. Within 48 hours Coinkite had fixed firmware for every model; it published a candid technical backgrounder admitting facts no lawyer would volunteer; it halted shipments and destroyed remaining affected inventory; its migration guidance (update first, new seed, verify fingerprint, test transaction, then move) is technically correct; NVK’s 31 July apology accepted “full responsibility” without hedging. Upper-quartile incident response by crypto standards.10

The bad. NVK’s first public reaction (later deleted) blamed users (“someone had loaded a bad seed… some five hundred keys”). The first advisory framed the issue as Mk3-only; the Mk4/Q/Mk5 scope emerged a day later under outside pressure from Block’s analysis. The deep-dive floats the theory that “someone used AI to review previous versions of our firmware and stumbled upon this issue” — while noting Coinkite’s own AI review missed it. The 7 August correction insisted there was “no weak-entropy fallback,” only a link-time error — hairsplitting, since the weaker of the two generators in the mix was Coinkite’s own constant-seeded Yasmarang. And Coinkite’s terms cap liability at purchase price with a class-action waiver; no compensation program exists.11

The root cause is institutional, not typographical. Five independent safeguards existed — the guard, the adjacent-word health check, the distinct-byte assert, the MicroPython #if, and a Dieharder test that ran on the simulator (where bytes come from the host PC) — and none tested the one property that mattered: which implementation the seed path actually reaches at link time. The regression entered inside a licensing-driven rewrite (shed GPL, block competitor forks) whose provenance was staged to look third-party: the switck and doc-hex accounts are both Coinkite CTO Peter D. Gray, proven by GPG key match, and an October 2020 issue shows Gray asking himself “PRs accepted?” to manufacture an arms-length origin for his own library. The defensible reading is negligence under licensing pressure, not sabotage — but a strategic rewrite of security-critical code, done without the review rigor its risk demands, is exactly how ecosystems get hurt.12

The community’s immune system worked, with a lag. Galaxy/Thorn ran a professional victim-intake and tracking operation; Block disclosed responsibly; Wizardsardine published the two best technical documents while guiding Liana users to safety; Optech #416 was fast and precise; the crowdsourced cc-vuln.org archive (518+ captured sources) and coldcard.rip chain tracker are genuinely novel public goods. The irrational tails were also vivid: instant vendor marketing, calls for NVK’s resignation within hours, phishing waves (ScreenConnect malware, lookalike advisories, physical scam letters), and an OP_RETURN message offering the thief laundering services for 7–10%. The under-discussed casualty is trust calibration: users renouncing self-custody entirely may be moving from a verifiable failure mode to an unverifiable one — the opposite of the lesson.13

What actually protected users

Architecture, not vigilance. The survivors were users with ≥50 dice rolls, strong passphrases, and above all diverse-vendor multisig — zero satoshis were documented stolen from multisig setups. The users who lost everything “did everything right” by the era’s best advice (buy the respected device, generate offline, guard the words) because that advice never included “verify which symbol the linker binds.” Removing a third-party custodian does not remove custody risk; it transfers that risk to the hardware, software, and key-generation processes on which the holder depends.14

Sources

Footnotes

  1. 2026 — Galaxy Coldcard Your Keys Not Your Coins

  2. 2026 — Block Predictable Rng Fallback Coldcard

  3. 2026 — Block Predictable Rng Fallback Coldcard

  4. 2026 — Generate 32 bytes of best-quality high entropy TRNG bytes.

  5. 2026 — Generate 32 bytes of best-quality high entropy TRNG bytes.

  6. 2026 — Block Predictable Rng Fallback Coldcard

  7. 2026 — Generate 32 bytes of best-quality high entropy TRNG bytes.

  8. 2026 — Galaxy Coldcard Your Keys Not Your Coins

  9. 2026 — Galaxy Coldcard Your Keys Not Your Coins

  10. 2026 — Block Predictable Rng Fallback Coldcard

  11. 2026 — Generate 32 bytes of best-quality high entropy TRNG bytes.

  12. 2026 — Generate 32 bytes of best-quality high entropy TRNG bytes.

  13. 2026 — Galaxy Coldcard Your Keys Not Your Coins

  14. 2026 — Galaxy Coldcard Your Keys Not Your Coins