COLDCARD vulnerability what happened, and what to do
Informational only, and this site never asks for your recovery words. details

Informational only. This is independent analysis and an evidence-backed explainer, not financial, security or legal advice, and not a substitute for professional advice about your own situation. It is not affiliated with, endorsed by, or speaking for Coinkite, Block, or any other party named here. Published estimates are attributed, and differing scenarios are kept separate with their assumptions. Act on your own judgement. Editorial standards and corrections.

Do not disclose recovery material to a website, form, message or support account. This site never asks for it. Deliberate recovery on independently verified offline equipment is a separate operation. Seed-word safety.

Technical Updated 1 Aug 2026

Blast radius

A sound mnemonic does not make the device sound. Beyond seed generation, a set of COLDCARD features drew their own secrets from the affected generator, so dice-only and imported seeds are outside only the first of the two failure families below.

Firmware and cryptographic detail.

Two failure families, and only one of them is about your seed

Most incident guidance sorts users by how their mnemonic was produced, because that is what decides whether the wallet keys themselves are enumerable. That question is real and it is the urgent one. It is not the whole of the defect. A second family of features asks the same generator for its own bytes and never consults the mnemonic at all, so the answer to "where did my seed come from" does not reach them.

flowchart TB
  S["How was the mnemonic produced?"] --> D["Dice-only, imported,<br/>or made before the regression"]
  S --> A["Generated on affected firmware"]
  D --> DS["The mnemonic itself is<br/>outside this defect"]
  A --> AS["The mnemonic and everything<br/>derived from it inherit the defect"]
  DS --> F["Separately: device features that call<br/>the affected generator without<br/>consulting the mnemonic at all"]
  AS --> F

  classDef n-act stroke-width:1.5px
  classDef n-safe stroke-width:1.5px
  class A,AS,F n-act
  class D,DS n-safe

Dice-only, imported and pre-regression mnemonics sit outside the left branch and inside the shared one. The feature list is set out in the register below.

Who this second family reaches

Anyone who used the listed features on affected firmware, including people who rolled 50 or more fair dice, people who imported a mnemonic generated on another device, and people whose wallet keys were never at issue. The exposures differ sharply in consequence: a paper wallet's private key is the generator's output outright, while a randomised PIN keypad only matters to someone watching the screen. The register grades each one separately rather than presenting the list as uniform risk.

Two RNG call surfaces The source contains call sites using ngu.random.bytes() and others using ckcc.rng_bytes(). The affected symbol-resolution path sits behind the first surface. The second reaches the STM32 hardware RNG directly. This distinction identifies which features require further analysis; it does not by itself establish the full security consequence for every feature.
flowchart LR
  BR["ngu.random.bytes()"] --> M["libngu my_random_bytes"]
  M --> Y["MicroPython Yasmarang<br/>UID word, SysTick, RTC"]
  M --> N["libngu Yasmarang<br/>constants or at-most-32-bit reseed"]
  OK["ckcc.rng_bytes()"] --> T["STM32 hardware TRNG<br/>analogue transistor noise"]

  Y --> BL["Call sites requiring review:<br/>seeds, paper keys, passwords,<br/>session material and nonces"]
  N --> BL
  T --> GL["Backup password, 7z salt,<br/>HSM user secrets,<br/>SD overwrite blocks"]

  Y -. "the hotfix rebinds this<br/>to the same hardware" .-> T

  classDef n-act stroke-width:1.5px
  classDef n-safe stroke-width:1.5px
  class BR,M,Y,N,BL n-act
  class OK,T,GL n-safe

Both call surfaces were intended to incorporate the STM32 hardware source. Before the fix, only the direct ckcc.rng_bytes() surface did so. The hotfix supplies the hardware-backed symbol used by libngu, which is why the table distinguishes call paths.

Randomness call-site classification
Secret or materialSourceRNG path before the fixScope established here
Master wallet seedseed.py:605Affected APIConfirmed use of the affected generation path.
Ephemeral and temporary seedsseed.py:605Affected APICalls the same generation function; downstream use must be assessed by feature.
Paper-wallet private keyspaper.py:94Affected APIThe device-generated path uses the affected API. The separate pure-dice path at paper.py:191-202 supplies its own private key and does not.
Seed XOR split masksxor_seed.py:78Affected APIMask generation uses the affected API, and only in the optional random-split mode selected at xor_seed.py:49-52. The default deterministic mode does not call it. The information leaked by a particular share set requires separate analysis.
Key Teleport ECDH material and passwordteleport.py:79,204,229Affected APIRandom ECDH-related material reaches this path. The receiver password is five bytes, and the source comment at teleport.py:226 states the intended width as 40 bits. Protocol impact has not been independently demonstrated here.
Key Teleport co-signer derivation indexmultisig.py:1190Affected APIThe index used to derive a co-signer's receive key is drawn as ngu.random.uniform(1<<28), so its intended width is 28 bits before any generator weakness is considered.
Clone and USB session materialbackups.py:752,862
usb.py:715
Affected APISession-related values reach this path. Exploitability depends on transcript access and protocol use.
Web2FA secret and noncesweb2fa.py:25,110,145,148Affected APISecret or nonce generation reaches this path. The account-level consequence depends on what an attacker can observe.
HSM local confirmation codehsm.py:857Affected APIThe confirmation-code call site uses the affected API.
HSM policy-approval characterhsm_ux.py:58Affected APIThe character a user must press to enable a new HSM policy is chosen from a five-item set through the affected API. This is a presence prompt rather than key material.
MicroSD 2FA enrolment noncepwsave.py:318Affected APIThe eight-byte nonce written when enrolling an authorised card uses the affected API. What that means at the account level remains untested in this archive.
Secure Notes and Passwords random generatorsnotes.py:58-92Affected APIThe 12-word, 24-word and dense-password choices call generate_seed(). The mixed-format choice uses ngu.random through random.randbelow() and shuffle(), and its own source comment at notes.py:83 budgets it at 49 bits before this defect is considered. The separate BIP85 choice is classified below.
Co-signing policy key Cccc.py:899Affected APIThe 12 words the device retains to enforce a spending policy are produced by the same generate_seed() function as a wallet seed. The import paths at ccc.py:870-895 supply words from elsewhere instead.
Randomised PIN keypad layoutlogin.py:32-34Affected APIThe shuffle uses ngu.random through shared/random.py. This is shoulder-surfing and wear-pattern hardening, not key material, and any consequence requires physical observation.
BIP85-derived entropy, mnemonics and passwordsdrv_entro.py:57-112Deterministic derivationBIP85 derives child entropy from the active BIP32 root rather than calling the affected RNG again. If that root is recoverable, the same application and index paths can be rederived. An active BIP39 passphrase remains part of the root from which the child is derived.
Trick-PIN duress walletstrick_pins.py:14,80Deterministic derivationDuress wallet secrets come from bip85_derive, and the device's own prompt at trick_pins.py:597 describes indices 1 to 3. They follow the root rather than adding independent randomness.
MicroSD 2FA file encryption keypwsave.py:39-47Deterministic derivationThe key is derived from the wallet secret and a card-identity salt, so it follows the seed's provenance. This is separate from the enrolment nonce listed above.
MuSig2 session randomnesslibngu k1.c:836Affected APIThe session-randomness call site uses the affected API. Nonce reuse or predictability can be security-critical, but no incident-specific exploit is established here.
secp256k1 context blindinglibngu k1.c:110-112Affected APIThis is side-channel hardening rather than wallet-key generation. Residual protection is not assessed here.
Backup file password, 12 wordsbackups.py:337Intact pathUses ckcc.rng_bytes(), the hardware TRNG. The password path is separate from the provenance of the mnemonic inside the backup.
7z archive salt and IVcompat7z.py:23IntactHardware TRNG.
HSM per-user secretsusers.py:182IntactHardware TRNG, ten bytes. This is the one HSM secret on the intact path, which is why the HSM rows above are split rather than treated as one finding.
SD card overwrite blocksfiles.py:65,124IntactHardware TRNG.
What this establishes for an encrypted backup The 12-word backup password is generated through the direct STM32 hardware-RNG path, not the affected API. This review does not re-audit the backup encryption format or storage practices.

Restoring the backup restores the same mnemonic. If that mnemonic used affected firmware, installing the hotfix or retaining a correctly generated backup password does not change its generation history. A migration plan should account for both the live wallet and retained backup copies.

Adjudicating the Wizardsardine feature list

On 1 August 2026 Wizardsardine published a long-form post-mortem whose "broken regardless of your seed" section is the most complete public enumeration of this second family captured here. Its author, Kevin Loaec, separately stressed on X that imported and dice-generated seeds are still exposed through those features.

Competing interest and author's own caveat

Wizardsardine sells Liana, a wallet product that competes with COLDCARD's software surface, and the post-mortem's first section is directed at Liana users and closes by recommending the company's institutional offering. The author also describes the post as written quickly under stress by three people in parallel and invites corrections. Both facts are disclosed for the same reason this archive discloses Foundation Devices' competing interest around the OPENDIME test: a vendor's analysis of a rival's product is evidence of what the vendor asserts, and is checked before it is repeated.

Each asserted exposure is set against what this archive can itself establish from the pinned firmware and libngu clones. The result is that the mechanism Wizardsardine describes holds at every call site checked, that several of its specific numbers are quotable from the source comments, that one claim needs narrowing, and that its list is not exhaustive.

Per-claim adjudication of the asserted feature exposures, checked 2 August 2026
Asserted exposure What this archive establishes Basis assigned
Paper wallets. The private key is the generator's output and bears no relation to the seed, except where the dice option was used. Both halves confirmed. paper.py:94 constructs ngu.secp256k1.keypair() with no argument, and the libngu constructor at k1.c:485-487 fills the secret key from my_random_bytes() in exactly that case. The dice path at paper.py:191-202 passes its own private key in, so it never reaches that branch. Verified
Clone to another COLDCARD. The ephemeral SD-card key exchange is recomputable, so a clone-file holder can decrypt the transfer and recover the seed in clear. The call sites are confirmed: backups.py:752 and backups.py:862 both create argument-free keypairs, and the second derives the archive encryption key by ecdh_multiply. The onward step, that possession of the clone file is sufficient to reconstruct the session key and recover the mnemonic, is a protocol consequence this archive has not demonstrated. Verified Reported
Encrypted USB session. Same ephemeral key construction, described by the author as not significant. Confirmed at usb.py:715. The archive agrees the consequence is bounded by transcript access and does not extend it further. Verified
Co-signing key C. The 12 words the device keeps to enforce a spending policy come from the broken generator. Confirmed. ccc.py:899 calls the same generate_seed() used for a wallet mnemonic. A user who instead imported key C words at ccc.py:870-895 is outside this call site. Verified
Secure Notes password generator, two modes. The word mode shares the seed-generation function; the strict-requirements mode states a 49-bit budget in a code comment. Both confirmed, including the number. notes.py:66-78 builds the word and dense choices on generate_seed(), and the comment at notes.py:83 reads entropy: 11+11 + (3.8*3) + 16 = 49 bits. That budget is the design intent before this defect, not a measurement of the delivered strength. Verified
Key Teleport, both mechanisms. The seed-transfer password is 40 bits by design; the co-signer exchange relies on a 28-bit derivation index. Both numbers are quotable from source. teleport.py:226-229 carries the comment "pick an 40 bit password" above ngu.random.bytes(5), and multisig.py:1190 draws the co-signer index as ngu.random.uniform(1<<28). Whether either width is reachable in a live protocol run is not established here. Verified
HSM mode. The second factor's shared secret and the local confirmation codes come from the same generator, so the second factor no longer blocks an attacker and physical presence becomes predictable. Confirmed for three call sites and narrowed at a fourth. The Web2FA shared secret (web2fa.py:110), the local confirmation code (hsm.py:857) and the policy-approval character (hsm_ux.py:58) all use the affected API. But HSM per-user secrets at users.py:182 use ckcc.rng_bytes(), the intact hardware path. "HSM secrets came from the same generator" is therefore true of the second factor and the confirmation prompts, and not true of the per-user credentials. Verified Narrowed
Randomised PIN keypad. The layout meant to defeat shoulder surfing and screen wear can be reconstructed. The call site is confirmed at login.py:32-34, which shuffles through shared/random.py and therefore ngu.random.uniform. The author places this low on the priority list because it needs physical observation, and this archive agrees rather than testing reconstruction. Verified
Side-channel masking. The cryptography library's countermeasure is predictable and therefore useless. The call site is confirmed: ctx_randomize() at libngu k1.c:110-112 fills its 32 blinding bytes from my_random_bytes(). "Therefore useless" is a security conclusion about residual protection that this archive has not assessed, and the same call site is graded as hardening rather than key material in the register above. Verified Reported
Seed XOR, random split mode only. The optional mode calls the broken generator, and the screen presenting it says the split uses the TRNG. Confirmed including the irony. The prompt string at xor_seed.py:49 reads "If you would prefer a random split using the TRNG, press (2)", and the branch it selects at xor_seed.py:77-79 is the one calling ngu.random.bytes. The default deterministic mode does not. The author's own scoping, that this affects only unaffected seeds split in that mode, matches the code. Verified
Seed-derived material. BIP-85 children, trick-PIN duress wallets and microSD 2FA all fall with the seed. Confirmed as derivation facts. BIP-85 is set out in the section below. Duress wallets call bip85_derive at trick_pins.py:14,80. The microSD 2FA file key is derived from the wallet secret at pwsave.py:39-47. These follow the seed's provenance, so a sound mnemonic leaves them sound. Verified
Not asserted in the post-mortem. The list omits the MuSig2 session-randomness call site at libngu k1.c:836 and the microSD 2FA enrolment nonce at pwsave.py:318, both of which this archive classifies on the affected surface. The enumeration should be read as substantially correct rather than complete. Verified

BIP85 descendants follow the active root

BIP85 deterministically derives application entropy from a BIP32 master root. COLDCARD's implementation in shared/drv_entro.py derives the selected BIP85 path from the active wallet secret and then applies the specified HMAC-SHA512 transform. It does not generate an independent random root for the child wallet.

The consequence is conditional. If an affected active root can be recovered, its BIP85 children can be reproduced when their application and index paths are known or searched. If the active wallet included an undisclosed BIP39 passphrase, that passphrase remains an independent input to the root. A migration inventory should therefore include BIP85-derived wallets rather than treating their different mnemonic words as independent provenance. A separate passphrase later applied to a BIP85-derived mnemonic adds another input whose effect depends on its construction and attacker knowledge; it does not change the mnemonic's deterministic provenance.

Benma and Kevin Loaec each published incident-specific warnings about BIP85-derived wallets.

Where this sits in the record

The commit history behind the affected call surface, including an independent walkthrough by Dustin Dettmer and what this archive could and could not confirm of it, is set out on the developer-response page. The wider set of published analyses, with the commercial interests attached to each, is on the response page. For deciding what to do about a particular device or wallet, use the risk guide rather than this register.

Evidence and calculations are scoped beside the claims they support. Device-state attack-cost scenarios are compared in what an attack costs, with each source's assumptions written out. Where sources disagree, their scenarios are kept separate. If something here is wrong, say so.