What You Should Do

If your seed was generated on a Mk3 running firmware 4.0.1 or later without at least 50 independent, private dice rolls: Treat the seed as at risk and follow the dedicated Mk3 Security Advisory and migration instructions. Proceed calmly, verify every step, and send a small test transaction before moving the remaining funds.

If you added at least 50 fair, independent, private dice rolls when originally creating the seed, read the dice guidance in the advisory before migrating. We do not consider that seed at risk from this RNG issue alone.

If your seed was generated on affected Mk4, Mk5, or Q firmware without at least 50 independent, private dice rolls:

  1. Upgrade the firmware before generating any new seed: version 5.6.0 or later for Mk4 and Mk5, or version 1.5.0Q or later for Q.
  2. Generate a completely new seed on the updated COLDCARD.
  3. The fixed firmware’s device-generated seed is sufficient. Dice rolls are optional and are not required to address this issue. A BIP-39 passphrase is a separate wallet-security choice.
  4. Back up the new seed and any passphrase carefully. Store the passphrase separately from the seed words.
  5. Power-cycle the COLDCARD and verify the wallet fingerprint and a receive address.
  6. Send a small test transaction before moving the remaining funds.

Updating the firmware does not repair a seed that was generated by affected firmware. A new seed must be generated and the funds migrated to the new wallet.

A passphrase creates a different wallet. Every passphrase—including one containing a typo—produces a valid wallet, so verify the wallet fingerprint before depositing funds. Losing the passphrase means losing access to that wallet.

Read the COLDCARD passphrase instructions and dice-roll instructions before using either option.

TAPSIGNER, OPENDIME, and SATSCARD are not affected because they use different codebases.

Summary

A complex and subtle series of bugs prevented the hardware RNG from contributing randomness in certain versions of the firmware. We were unaware of the bug until today. Changes introduced for Mk4 added entropy from SE1 and SE2, which partially reduced the impact on later models but did not restore the intended 128-bit security target.

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. 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.

Both attackers and defenders have the same AI tools, but today it did not help us, and only helped the bad guys.

Technical Background

In 2021, we moved COLDCARD’s elliptic-curve operations to Bitcoin Core’s libsecp256k1, using the same implementation trusted by Bitcoin Core instead of maintaining a separate EC stack. That required adding libNgU, an embedded MicroPython library that exposes libsecp256k1 and other Bitcoin primitives.

The cryptographic choice was sound. The integration was not. During that migration, wallet seed generation moved from ckcc.rng_bytes() to ngu.random.bytes(). That path resolved rng_get() to MicroPython’s software fallback instead of COLDCARD’s hardware RNG implementation.

The bulk of randomness on the COLDCARD was coming from a PRNG that I didn’t know was actually in the source code base (it is from a submodule, Micropython). At the same time the carefully crafted TRNG code I wrote was being used, but just by chance, and only for less important things.

On Mk3, the active PRNG was seeded primarily from device and timing state. Under our current attack assumptions, we estimate the effective search space at about 40 bits. This is a preliminary estimate and may change as analysis continues.

During Mk4 development, we also mixed values from the TRNGs in SE1 and SE2 into the PRNG state as a backup to a backup. This additional entropy materially improves the situation for Mk4, Q and Mk5. Under the same current assumptions, we estimate the effective search space at about 72 bits.

Although Mk4, Q and Mk5 had additional secure-element entropy mixed into the PRNG state, they continued to draw most subsequent random values from the same MicroPython PRNG:

That file either builds PRNG code, or uses the STM32 hardware TRNG. Looking quickly at it, you’d think we got the TRNG version of get_rng() but in fact, I explicitly set MICROPY_HW_ENABLE_RNG to zero, thinking we didn’t need either version, but that’s not what it does. Because that code provided a PRNG with the same function signature as the desired code, the build completed without identifying the wrong implementation.

The MicroPython fallback was introduced upstream in May 2018. It did not enter COLDCARD wallet seed generation until the libNgU migration in March 2021. The eight-year figure therefore describes the age of the upstream fallback code, not the duration of affected COLDCARD seed generation.

Existing review confirmed that the intended TRNG implementation was present in the firmware binary, but did not verify which rng_get() implementation the wallet seed-generation path actually reached across the two submodules. No changes are needed in the COLDCARD firmware itself, just which code comes along for the ride.

Why Existing Review Did Not Catch It

Both RNG implementations had the same function signature, and the intended board-specific TRNG code was present in the binary. Existing review verified that code but did not verify end-to-end symbol resolution and call reachability from wallet seed generation.

The relevant preprocessor guard is visible in this section of code:

random.c:22-31

The guard used #ifndef, which tests whether MICROPY_HW_ENABLE_RNG is defined, rather than whether its value is nonzero. We defined that macro as zero, so the #error did not stop the build.

The hotfix now explicitly excludes MicroPython’s fallback PRNG object and adds a build-time RNG symbol check. The build fails unless the board-specific object defines the global rng_get() symbol and the upstream fallback object defines no symbols.

Next Steps

We have released an emergency hotfix for current products:

Version 5.6.0 (Mk4 and Mk5) and Version 1.5.0Q for Q1.

No new features but will generate entropy correctly.

At this point, many researchers and competitors have studied this bug and published their own analysis: