The bug is about as small as a bug can get. Coldcard's firmware asked whether a configuration macro existed rather than whether it was switched on. It existed. It was set to zero. So for five years the devices built recovery seeds from a fallback pseudo-random generator instead of the hardware randomness chip sitting right there on the board, and on July 30 somebody who had worked that out swept roughly 1,082.65 BTC, about $70.2 million, out of 1,196 addresses in 41 minutes.

Coinkite, which makes Coldcard, has taken responsibility and shipped emergency firmware. That firmware does not fix anything for people already holding a weak seed. If your seed was generated on an affected build, the key material is already public in the mathematical sense, and the only remedy is a new seed on patched firmware and a move of every coin.

RelatedFatFs Flaws Let a Rigged USB Take Over IoT Devices

What actually went wrong inside the firmware?

Coldcard runs MicroPython. MicroPython exposes a build-time constant, MICROPY_HW_ENABLE_RNG, that tells the rest of the tree whether the platform's hardware random number generator should be wired up. Coinkite supplied its own RNG wrapper, so it did the reasonable thing and set that constant to zero.

The cryptographic support library did not read the value. It tested whether the symbol was defined at all. A macro set to zero is still a defined macro, so the library concluded that hardware randomness was live, skipped its own initialisation path, and bound the seed generator to MicroPython's Yasmarang fallback PRNG.

Yasmarang is a small, fast, entirely non-cryptographic generator. In this configuration it was initialised from the STM32 chip's unique ID and its timer registers, and then it collected no fresh entropy afterwards. The chip ID is not secret. It is stamped into the silicon and readable by anyone holding the device. What was supposed to be 128 bits of unguessable randomness became a short walk through a small search space.

How a defined-but-zero macro rerouted Coldcard seed generation A diagram of two paths. The intended path runs from the STM32 hardware random number generator through Coinkite's own wrapper to a seed with 128 bits of entropy. The actual path begins with the macro MICROPY_HW_ENABLE_RNG set to zero, which the support library tests only for existence rather than value, so it concludes hardware randomness is active and binds instead to the Yasmarang fallback pseudo-random generator, seeded from the non-secret chip unique identifier and timer registers, producing roughly forty bits of effective entropy on Mark 3 devices and about seventy-two bits on Mark 4, Mark 5 and Q devices. SEED GENERATION PATH · FIRMWARE 4.0.0 ONWARD INTENDED STM32 hardware RNG true entropy source Coinkite RNG wrapper mixes and whitens Seed 128 bits WHAT SHIPPED MICROPY_HW_ENABLE_RNG defined, set to 0 library tests existence Yasmarang PRNG non-cryptographic no fresh entropy after init Guessable seed ~40 bits (Mk3) ~72 bits (Mk4/5/Q) Seeded from the chip unique ID and timer registers. Neither value is secret, and the chip ID is readable off the device. genztech.blog
Fig 1 A macro set to zero is still a defined macro. That distinction is the whole vulnerability.

How much randomness was actually left?

Coinkite published its own estimate, and it is grim in a very specific way. Effective entropy came out at roughly 40 bits on the Mk3 and about 72 bits on the Mk4, Mk5 and Q, against the 128 bits a properly generated seed should carry.

Those two numbers describe completely different situations. Forty bits is around a trillion possibilities, which is a weekend on rented hardware, and it explains why Mk3 holders were swept first and swept comprehensively. Seventy-two bits is roughly 4.7 sextillion, which is not something you brute-force on a whim, but it is also nowhere near the security margin anybody thought they were buying. It is the difference between "already gone" and "do not wait around."

DeviceSeeds at riskFixed inCoinkite entropy estimate
Coldcard Mk2 / Mk34.0.0 through 4.1.94.2.0~40 bits
Coldcard Mk4 / Mk5anything before 5.6.05.6.0~72 bits
Coldcard Qanything before 1.5.0Q1.5.0Q~72 bits
Edge builds (Mk4/Mk5)before 6.6.0X6.6.0X~72 bits
Edge builds (Q)before 6.6.0QX6.6.0QX~72 bits
Reference target128 bits

The version that matters is the one your seed was generated on, not the one currently installed. A device that was set up in 2022 and diligently updated ever since is still holding a weak seed, because updating firmware does not regenerate keys.

Why are there two different loss figures?

Coverage has split between two numbers, and both are real measurements of different things. CoinDesk reported roughly 594 BTC, about $38 million, taken from around 500 wallets in a 25-minute sweep. Galaxy Research, working the chain rather than the reports, mapped a larger operation: 1,196 addresses drained between 01:10 and 01:51 UTC, about 1,082.65 BTC, roughly $70.2 million at the time.

Galaxy attached a caveat worth repeating, because it is the honest limit of on-chain forensics. Their analysts noted that the pattern "identifies the operator, not the theft, because a sweep looks the same as if a coin owner chose to move coins." Some fraction of those 1,196 addresses may be people who read the advisory and evacuated their own funds. The lower figure is confirmed victim reports; the higher figure is total swept volume attributable to one operator. Expect the true number to settle somewhere between them, and expect it to keep drifting as more holders check.

  1. Mar 2021Firmware 4.0.0 ships the RNG fallback is introduced and goes unnoticed
  2. Jul 30The sweep 1,196 addresses, 1,082.65 BTC, 41 minutes, per Galaxy Research
  3. Jul 31Emergency firmware Coinkite discloses and takes responsibility
  4. Aug 1Scope keeps widening loss estimates move from $38M toward $70M as reports accumulate
  5. OngoingMigration window every affected seed remains guessable until its coins move

What should an affected holder do today?

Update the firmware, then treat the update as step one of two. Generate a fresh seed on the patched build, on a device you have just updated, and move every coin to addresses derived from it. Do not restore the old seed onto the new firmware, which changes nothing, and do not assume a passphrase saved you unless you are certain the passphrase was strong and independent of the seed.

RelatedCheap Android TV Boxes Are Running an Ad-Fraud Botnet

The uncomfortable part is that there is no way to check whether your particular seed is in the attacker's reach without either doing the search yourself or waiting to find out. Weak-key situations do not offer a clean self-test. Treat any seed generated on an affected build as compromised and act accordingly.

What it means for the market

Bitcoin closed out its worst month against a backdrop that already included a 3.25% daily drop to roughly $62,869, and this landed on top of it. The direct read is narrow, since Coinkite is private and the loss falls on individuals rather than a balance sheet, but the second-order read is the one to watch: this is an argument handed to the custody-as-a-service side of the trade.

CoinDesk framed it as a shove toward ETFs, and that is the plausible flow. Spot Bitcoin ETF issuers and regulated custodians are the structural beneficiaries of any event that makes self-custody feel like a specialist skill rather than a default. Changpeng Zhao's public advice after the sweep, to spread funds across multiple wallets, is itself a concession that single-vendor self-custody carries vendor risk that most holders never priced. The signal for investors is a possible shift in the self-custody-versus-custodial mix, visible in ETF inflows and in exchange reserve balances over the next few weeks, rather than anything that shows up in a single day's price.

Our take

The instinct after an event like this is to blame the specific mistake, and the specific mistake really is embarrassing. But #ifdef versus #if is a class of error that C has been quietly generating for forty years, and it is exactly the kind of thing that survives code review because the line reads correctly in isolation. Coinkite set the macro to zero deliberately and correctly. A different file, written by different people, asked the wrong question about it.

The real failure is that nobody was testing the output. Five years of devices shipped, and the property that mattered, that seeds are drawn from the hardware source with full entropy, was never asserted anywhere that would fail a build. A statistical test on generated seeds would not have caught this, because Yasmarang produces perfectly random-looking output. What would have caught it is a single assertion that the hardware RNG path was the one actually executing. Cryptographic correctness is not something you can inspect for; it has to be tested at the point where the entropy enters.

Give Coinkite credit for the disclosure. They took responsibility publicly, shipped a fix in a day, published their own entropy estimates including the worst-case Mk3 number, and did not try to minimise the window. That is the right behaviour and it is rarer than it should be. It also does not give anyone their coins back.

What to watch · next 90 days
  • The loss number keeps moving. The gap between the $38M confirmed-report figure and the $70M swept-volume figure closes from both ends as holders check their seeds.
  • A CVE assignment. None had been issued as of publication, which is odd for a flaw of this reach and worth tracking on our CVE watchlist.
  • Other MicroPython-based devices. The defective check lived in shared library code. Any embedded product that set the same macro to zero and expected the same behaviour deserves an audit.
  • Custody flows. If self-custody confidence is genuinely dented, it shows up in ETF inflows and exchange reserves before it shows up in anybody's commentary.
Primary sources

Original analysis by GenZTech. Figures attributed to their reporting sources above; the two loss estimates measure different things and are presented as such.