BetterSWE DigestWhat is BetterSWE?

News · Issue 12

How a 2021 Firmware Bug Let Attackers Predict Coldcard Seeds and Steal $70M in 41 Minutes

One line changed in March 2021 quietly swapped hardware randomness for a predictable software PRNG. Five years later, someone noticed.

A hardware wallet being drained against a running clock

The event

1,196 addresses, drained in 41 minutes

A hardware wallet being drained against a running clock

On July 30, 2026, someone swept 1,196 Bitcoin addresses in a single 41-minute operation, moving 1,082.65 BTC (~$70.2M) out of Coldcard hardware wallets. Galaxy Research clustered the sweep by its fingerprint: every transaction used the same 30 sat/vB fee rate with no change output, and no other Bitcoin activity in the prior 30 days matched that pattern.

  1. Mar 2021 (flawed firmware ships)
  2. Jul 30, 2026 (41-min sweep, $70M gone)
  3. Jul 31, 2026 (emergency firmware)

The headline says 2021, and that is the twist. The theft happened in 2026, but the flaw it exploited was introduced by a commit in March 2021 and sat in public source for roughly five years before anyone weaponized it. Coinkite says it was unaware of the bug until the day of the sweep.

The commit

One line swapped hardware for software

A git diff swapping a hardware RNG call for a software one

Coldcard has its own hand-written wrapper that reads the STM32's hardware true-RNG peripheral. Commit b18723dd, dated March 1, 2021, changed seed generation to call random.bytes(32) instead, which routed through a new library, libngu, added that year when the device migrated its elliptic-curve math to libsecp256k1.

- entropy = ckcc.rng_bytes()   # STM32 hardware TRNG
+ entropy = random.bytes(32)    # -> ngu.random.bytes
The change that shipped in firmware v4.0.0 on March 17, 2021

Nobody meant to weaken anything. The intent was to use the shared library's randomness, and on paper random.bytes should have reached the hardware. It didn't, because of a subtle defect one layer down in how the library decided where its bytes came from.

The mechanism

A preprocessor guard that tested the wrong thing

A code path forking away from hardware randomness toward a software fallback

Coldcard's board config sets MICROPY_HW_ENABLE_RNG to 0 on purpose, because Coinkite supplies its own hardware wrapper. But libngu guarded its RNG choice with #ifndef MICROPY_HW_ENABLE_RNG, which only tests whether the macro is defined, not whether its value is nonzero. Defined as 0 still passes the guard, so the library bound to MicroPython's software path.

#ifndef MICROPY_HW_ENABLE_RNG   (macro is 0, but defined)
        │ guard PASSES
        ▼
  MicroPython rng_get()  ── value 0 ─▶ Yasmarang (software)
                                          │ seeded ONCE from:
                          MCU UID + SysTick + RTC regs
                                          │
                              XOR 2nd Yasmarang (public consts)
                                          ▼
                                 fully deterministic
The guard passed, so the seed came from a software PRNG

That path is MicroPython's Yasmarang fallback, seeded exactly once from the chip's fixed 32-bit UID, the SysTick down-counter, and RTC registers. On Mk3 the RTC oscillator was disabled and SysTick has at most ~80,000 values, so once you know the inputs the whole stream is reproducible. Coldcard even XORs in a second Yasmarang seeded from hardcoded public constants, but XOR of two deterministic streams is still deterministic.

The math

40 bits where 128 were promised

A comparison of intended versus actual seed entropy

A 12-word BIP-39 seed is supposed to carry 128 bits of entropy. Coinkite's own estimate puts the real figure at roughly 40 bits on Mk2/Mk3, and about 72 bits on the newer Mk4/Mk5/Q line. Forty bits is a search space small enough to reason about, not a wall of astronomical numbers.

≤240 RNG outputs  →SHA256d  ≤240 wallet seeds\le 2^{40}\ \text{RNG outputs} \;\xrightarrow{\text{SHA256d}}\; \le 2^{40}\ \text{wallet seeds}
Hashing a bounded input can't manufacture entropy that was never there

The seed bytes are hashed with SHA256d before use, but hashing a bounded input can't add entropy, so a small candidate set stays small. Block's independent analysis is careful here: it bounds the theoretical search space below ceilings like 2^40.7 and 2^73.3, stresses that 2^73.3 is not the same as 73-bit security, and publishes no end-to-end brute-force benchmark. Nobody has publicly reconstructed a victim's seed and matched it to a drained address.

The blast radius

Every random byte on the device came from the same well

One flawed randomness source feeding many different device secrets

Because every function that reached for randomness went through the same ngu.random path, the exposure runs past wallet seeds. Paper wallet private keys are the worst case: the RNG output becomes the secp256k1 key directly, and the public address is an instant oracle to check a guess against. Seed-XOR masks, device cloning keys, USB encryption keys, and TOTP secrets all drew from the same well.

Update firmware only

seed stays predictable

New seed on patched firmware

the actual fix

Coinkite shipped patched firmware for every affected model on July 31, 2026, but the fix is not just an update. Exposure is set by the firmware version at the time the seed was generated, so a compromised seed stays compromised even on new firmware. The real remedy is to generate a brand-new seed on patched firmware and move funds, and Coinkite notes that seeds made with 50+ private dice rolls were never at risk from this bug alone. Ledger's CTO drew the parallel to the 2022 Trust Wallet weak-RNG flaw, where public addresses alone were enough to brute-force wallets.

The takeaway

A quiet default that stayed quiet for five years

Why it matters

Hardware wallets exist to keep the one secret that matters unpredictable, and here a single misread preprocessor guard silently replaced true randomness with a PRNG seeded from public device values, unnoticed in open source for five years. Coinkite says it even ran a top AI model over the code weeks earlier and it found nothing, while speculating an attacker used the same tools to find it first. The lesson for anyone shipping crypto: #ifndef tests definition, not value, and the failure mode of a broken RNG is total and invisible until someone drains you.

The short version

  • A March 2021 commit routed Coldcard seed generation through MicroPython's deterministic **Yasmarang** PRNG instead of the STM32 hardware TRNG.
  • The cause was `#ifndef MICROPY_HW_ENABLE_RNG` passing because the macro was **defined as 0**, not because randomness was enabled.
  • Effective entropy dropped to about **40 bits** on Mk2/Mk3 versus the intended **128**, though no end-to-end brute force has been published.
  • On **July 30, 2026** a single operator swept **~1,082 BTC (~$70M)** from **1,196 addresses** in **41 minutes**.
  • Updating firmware does not fix an old seed. Affected users must generate a new seed on patched firmware and move funds.

Sources

  1. 2.Coldcard Firmware Vulnerability Leads to $70M Bitcoin Theft | Red Secure Tech Cybersecurity Blog | Red Secure Tech LTD.
  2. 3.Coldcard Hardware Wallet Hacked via Firmware Bug That Bypassed RNG ...
  3. 4.Coldcard Security Advisory
  4. 7.Predictable RNG Fallback and 32-Bit Reseed in COLDCARD Firmware
  5. 8.The Coldcard Incident: How Did This Happen?
  6. 10.Technical Deep Dive into the Entropy Issue
  7. 12.Coldcard Hardware Wallet Flaw Linked to $70 Million Bitcoin Theft in ...