The COLDCARD Entropy Failure: How a Silent RNG Fallback Cost Users $38 Million

AbbieAbbie
/Updated Aug 3, 2026
The COLDCARD Entropy Failure: How a Silent RNG Fallback Cost Users $38 Million

Key Takeaways

  • COLDCARD seed generation could silently fall back to a software PRNG after a 2021 build configuration issue.
  • Coinkite estimates about 40 bits of effective search space for Mk3 and about 72 bits for Mk4/Mk5/Q under current assumptions.
  • Users who generated affected seeds should create a new seed on a trusted path and migrate funds after backup and address verification.
  • OneKey hardware wallets are not affected by this bug.

In cryptography, “random” is a hard metric. It is something you can quantify, and something that has to hold up under real computing power.

A 12-word recovery phrase is backed by a 128-bit random number, encoded into human-readable words under the BIP-39 spec. How unpredictable that number is directly determines whether the wallet can withstand brute force. 128 bits means 2¹²⁸ possibilities. Put all the computing power on Earth together and you still couldn't finish enumerating them before the universe ends.

So in theory hardware wallets should be rigorous about this. There has to be a dedicated true random number generator (TRNG) that takes physical-world randomness from circuit noise, paired with an EAL-certified secure element (SE), with a mature algorithm stirring all of it together at the end.

On July 30, 2026, roughly 594 BTC (about $38 million) were swept out of nearly 500 COLDCARD wallets in under half an hour. The next day, manufacturer Coinkite confirmed the root cause in “Technical Deep Dive into the Entropy Issue” [1]: since a firmware change in March 2021, a build configuration error had been quietly causing the code to fall back to MicroPython's built-in software pseudorandom number generator (PRNG).

The consequences differ by model. Coinkite's preliminary estimate is that the Mk3 has only about 40 bits of effective search space left, and the Mk4, Mk5, and Q about 72, against a design target of 128. Forty bits is roughly 1.1 trillion possibilities, which still sounds astronomical, but for an attacker who can afford to rent a GPU cluster it has gone from “physically impossible” down to an engineering project that is viable in expectation. From 128 to 40, the search space shrank by a factor of 2⁸⁸, or 300 septillion.

This $38 million theft turned that expectation into an accomplished fact.

Note: OneKey users' funds are safe. None of OneKey's code touches this issue, and none of it uses the implementations or dependencies involved. Full statement: https://x.com/OneKeyHQ/status/2083106588022509974?s=20

TL;DR

  • Users who generated and used a seed phrase on a COLDCARD Mk3: regenerate a seed phrase on another wallet immediately, and migrate your funds to the new wallet.
  • Users who generated and used a seed phrase on a COLDCARD Mk4 or Mk5: if the firmware version that generated the original seed phrase was below 5.6.0, regenerate a seed phrase on another wallet immediately, and migrate your funds to the new wallet.
  • Users who generated and used a seed phrase on a COLDCARD Q: if the firmware version that generated the original seed phrase was below 1.5.0Q, regenerate a seed phrase on another wallet immediately, and migrate your funds to the new wallet.
  • Users of OneKey hardware wallets are not affected by this bug and can keep using them with confidence.

Now for the hardcore technical part 👇

Root Cause: A Silent Software Fallback

From the Hardware RNG Interface to libNgU

The 2021 crypto stack migration changed wallet seed generation from ckcc.rng_bytes() to ngu.random.bytes() [6]:

ngu.random.bytes() → libNgU rng_get() → the rng_get symbol it ultimately links to

The risk isn't in the function name or the output length. It's in which object file actually supplies that final rng_get().

#ifndef Only Checks Whether Something Is Defined, Not Its Value

The target board config defined MICROPY_HW_ENABLE_RNG as 0, meaning MicroPython's hardware RNG path was not enabled.

But at the time, libNgU used [8]:

#ifndef MICROPY_HW_ENABLE_RNG
#error ...
#endif

#ifndef only tests whether the macro exists. A macro defined as 0 is still “defined,” so the #error that was supposed to block a bad build never fired.

A Same-Named Symbol Let the Wrong Implementation Slip Into the Final Firmware

When the hardware RNG isn't enabled, MicroPython still provides an rng_get() under the same name, but it's the Yasmarang software PRNG fallback [9].

That produced the full failure chain:

macro exists, but its value is 0 → libNgU's build guard doesn't trigger → the firmware still compiles successfully → the same-named rng_get resolves to MicroPython's fallback → seed generation never receives the hardware RNG input it was supposed to

Taken one at a time, every link in this chain is just a small oversight. Strung together, they render the build system's gatekeeping meaningless: a secure implementation existing in the source or the binary does not mean the security-critical call actually reaches it.

Mixing Entropy Sources Can't Make Up for Missing Entropy

The fallback's initial state involves part of the UID, SysTick, RTC time, and sub-second state, after which Yasmarang advances it deterministically. libNgU also mixes it with a second Yasmarang stream that starts from a fixed initial state.

Two deterministic streams that can be reconstructed or enumerated gain no new physical unpredictability from being XORed or run through SHA-256. Hashing can condition the input, but it can't expand a finite candidate set into a genuine 2^256 unknown space.

What 40 Bits and 72 Bits Actually Mean

The effective search space estimates Coinkite uses are not measured, certified entropy values. The two numbers describe the size of the candidate state space an attacker has to work through. They are not a measurement of min-entropy, they don't convert into an average number of guesses, and they certainly don't yield a specific attack time or cost.

Real attack cost depends on a chain of preconditions: whether the attacker knows or can guess the device UID; whether they can narrow the range of RTC, SysTick, and boot time; how many RNG calls occurred before the seed was generated; whether they have an xpub, address, or public key on hand to validate candidates offline; and what BIP-39/BIP-32 derivation and parallel hardware cost to run.

So the accurate way to put it is this: under Coinkite's current preliminary attack assumptions, the Mk3's effective search space is estimated at about 40 bits, and the Mk4/Q/Mk5 at about 72. That is not the same as “every Mk3 wallet has exactly 40 bits of entropy,” nor “the newer models are proven to have 72-bit cryptographic security,” and certainly not “any ordinary computer can recover every wallet in a fixed amount of time.”

Why the Mk4, Mk5, and Q Are Affected Too

The two secure elements on the Mk5 and Q boards (source: coldcard.com)The two secure elements on the Mk5 and Q boards (source: coldcard.com)

The two secure elements on the Mk5 and Q boards (source: coldcard.com)

The Mk4, Mk5, and Q do draw random material from two secure elements, but the pre-fix implementation didn't feed all of that output into a cryptographic DRBG.

The public source shows that after hashing the secure element material, it takes only four bytes and passes them to ngu.random.reseed(), replacing just one 32-bit state word of Yasmarang.

So when the rest of the fallback state and the call trace are fixed:

number of output streams distinguishable by the secure element reseed ≤ 2³²

This does not contradict the official “about 72 bits.” The 72-bit model also counts uncertainty in state such as the UID, timers, RTC, and call history. Block Engineering's loose combined upper bound comes in below 2^73.3, and they state explicitly that this is not a certification of 73-bit cryptographic security [10].

In other words: the Mk3 is the worst case, because there's no secure reseed at all. The secure element input on the Mk4/Q/Mk5 lowers the risk, but a four-byte reseed doesn't restore the 128-bit security the design was aiming for.

The Official Advisory and the Actual Scope

Coinkite's current advisory starts at Mk3 4.0.1. But the immutable v4.0.0 source already contains the relevant entropy generation path [7], and there is no corresponding RNG fix between 4.0.0 and 4.0.1.

Block Engineering goes further, bringing Mk2/Mk3 v4.0.0–v4.1.9 into its source analysis as well [10].

So there are two levels to keep separate:

  • Coinkite's official scope: Mk3 4.0.1+;
  • The technical scope the source code supports: Mk2/Mk3 v4.0.0–v4.1.9.

You can't say Coinkite has officially confirmed Mk2 or v4.0.0. You also can't infer they're safe just because the advisory doesn't name them.

How the Fix Works

The hotfix in Mk4/Mk5 5.6.0 and Q 1.5.0Q did more than change one conditional expression. It added build-level enforcement [3][4][5]:

  • Exclude MicroPython's fallback RNG object;
  • Make sure the board-level object provides the global rng_get();
  • Use nm to inspect the symbols in the object files;
  • Fail the build outright if the fallback still exports the symbol, or if the board-level object doesn't properly provide rng_get().

What Users Should Do

Mk4, Mk5, and Q

  1. First update to 5.6.0 or later (Mk4/Mk5), or 1.5.0Q or later (Q).
  2. Generate a brand-new seed on the fixed firmware.
  3. Record the backup and complete a restore verification.
  4. Check the wallet fingerprint and receive address on the device screen.
  5. Send a small test transaction before migrating the rest of your funds.
  6. Keep the old backup until every transfer is confirmed, but stop receiving to the old wallet.

Mk3

There is currently no released fix firmware for the Mk3, and 4.1.9 is not a fix. Users shouldn't wait for firmware that may come later, because no new firmware can add entropy to an existing seed.

Follow Coinkite's current migration guidance: generate a new seed on a trusted path, verify the backup, the addresses, and a small transaction, then migrate your assets.

Two Different Dice Thresholds

There are two numbers for dice: 50 and 99. They serve two different goals and can't be used interchangeably.

50+ rolls added at original seed generation. If at least 50 fair, independent, private six-sided rolls that were never recorded, stored, or disclosed were mixed in when the seed was first generated, Coinkite says it does not consider that seed at risk from this RNG issue alone [11].

50 × log₂(6) ≈ 129.25 bits

That's the roughly 128-bit threshold for this specific incident, not an absolute proof of security for your whole operational process.

The Dice Roll Import flow on a blank Mk3. Coinkite also offers a different flow: on a blank Mk3 running 4.1.9, select Import Existing > Dice Rolls and enter at least 99 fair rolls. This dedicated path consumes the dice sequence directly and doesn't use the affected device generator.

99 × log₂(6) ≈ 255.91 bits

99 is the threshold in the official flow; working backward from a literal “at least 256 bits” standard, you'd need 100.

A Passphrase Is a Separate Barrier, Not a Fix

A strong, unique, random, secret BIP-39 passphrase derives a different wallet and can add an independent search factor; the device PIN only controls local access and takes no part in BIP-39 root key derivation [12].

But a passphrase can't make up for entropy the mnemonic never had. Short passphrases, famous quotes, predictable patterns, and reused passwords can still be guessed. A typo will also generate a wallet that looks valid but is completely different, so you have to verify the fingerprint, and keep a reliable backup you've tested a restore from.

Closing

The core problem this incident exposed is that the actual reachability of a security-critical implementation was never enforced by the build system. “Someone got a macro wrong” falls far short of describing it.

At a minimum, hardware wallets and other key generation systems should:

  • Verify which object and symbol a security API ultimately links to, not just check the function signature;
  • Have capability checks verify both the existence and the value of a macro;
  • Make security-critical fallbacks fail closed, never silently supplying an ordinary PRNG;
  • Have CI inspect object composition, link maps, symbol provenance, and end-to-end data flow;
  • Prove through testing that real hardware entropy reaches the final seed, rather than only checking output length, nonzero-ness, or lack of repetition;
  • Write advisories that clearly separate the version that generated the seed, the current firmware, the fixed version, and what to do with old seeds.

COLDCARD's open source is what let outside researchers reconstruct this call chain, and Coinkite has published both a formal technical report and a fixed release.

Claiming a brand is “absolutely secure” is worth nothing. What is worth something is making “where the randomness comes from, what it ultimately links to, where it goes on failure, and how to verify that in the shipped product” a system property you can keep re-checking.

References

[1] Coinkite: Technical Deep Dive into the Entropy Issue [2] Coinkite: Mk3 Security Advisory [3] Coldcard 5.6.0 / 1.5.0Q fix commit [4] Coldcard 5.6.0 release commit [5] Coldcard 1.5.0Q release commit [6] 2021 libNgU seed generation migration commit [7] Official v4.0.0 source snapshot [8] libNgU random.c [9] MicroPython commit introducing the fallback [10] Block Engineering technical analysis [11] Coldcard dice roll math [12] Coldcard passphrase documentation [13] OneKey trezorcrypto.random source branch [14] OneKey seed generation logic [15] OneKey standard Makefile [16] OneKey SCons config

Secure Your Crypto Journey with OneKey

View details for Shop OneKeyShop OneKey

Shop OneKey

The world's most advanced hardware wallet.

View details for Download AppDownload App

Download App

Trade global assets. Start with your email in minutes.

View details for OneKey SifuOneKey Sifu

OneKey Sifu

Crypto Clarity—One Call Away.

Keep Reading