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.

Plain language Updated 1 Aug 2026

What the developer community did

Four public incident-response proposals are tracked. In captures checked on 1 August 2026, all four were open and unmerged. A separate commit-history analysis is adjudicated claim by claim below.

No code. Written to be actionable.

The incident-response record includes work outside Coinkite's firmware repository. Three contributors proposed changes to the upstream libngu library, a companion firmware change followed, Bitcoin Optech distributed the advisory to its technical readership, and an independent developer published a commit-history reconstruction of how the defect entered the tree.

The upstream guard in the 1 August capture

The COLDCARD hotfix replaces the linked rng_get() implementation for the affected boards. It does not change libngu's original #ifndef MICROPY_HW_ENABLE_RNG guard. An integrator can encounter the same condition if it defines that macro as (0) and links a software rng_get() fallback.

Three pull requests were opened against switck/libngu, two on 31 July 2026 and one on 1 August 2026.

Upstream libngu pull requests opened after disclosure
PR Proposal Captured state on 1 August
#58btchip · patch Enforce a hardware TRNG. Make an unsupported board configuration fail at build time rather than use the software path. Open No maintainer reply appears in the 1 August capture.
#59jgmontoya · patch Fail-closed entropy backends and HMAC_DRBG. The branch includes tests against official NIST CAVP vectors, deterministic health-rule tests and negative compile gates. Open The maintainer replied "diff too big". The author offered a three-PR split.
#60ballance · patch Absorb a full-width seed. Accept a bytes-like reseed value, mix every byte into all Yasmarang state words, and retain integer input for compatibility. Open No review or maintainer reply appears in the 1 August capture.

For #59, the proposed split is: the DRBG and CAVP-vector tests, the entropy health rule, and a smaller final random.c rework. These are open proposals, not changes present in the upstream release.

COLDCARD firmware PR #691 is the companion to libngu #60. It proposes passing the complete SHA256d digest from mk4.rng_seeding() into the widened reseed API rather than unpacking only its first four bytes. The firmware PR depends on the libngu change and was also open without review in the 1 August capture. Its patch is held separately.

Independent commit-history analysis

On 1 August 2026, bitcoin++ Insider Edition published a guest analysis by Core Lightning developer Dustin Dettmer, announced the same day on X. It reconstructs the change from the firmware commit history and argues that the board override was aimed at a symbol the seed path never uses, while silencing the build condition that would have exposed the mistake. Its commit-level assertions are checkable against the repository clones held here, which is why they are graded individually below rather than adopted or dismissed as a block.

This piece also functions as a counterpoint in the separate argument about how the defect was found, since it is human commit archaeology reaching the faulty path without model assistance. That distinction is worked through on the AI page.

Dettmer's commit-level assertions against the pinned clones, checked 2 August 2026
Assertion What the pinned clones show Basis
A commit whose entire message is the word "runs" changed 1,534 lines. Confirmed to the line. Commit 37e4af54, 21 May 2021, message "runs", 1,534 insertions across 17 files, adding the stm32/COLDCARD_MK4/ board directory including its mpconfigboard.h, rng.c and rng.h. Verified
That commit is where the low-entropy defect was introduced. Not supported by the history held here. The comment "We have our own version of this code.", the #define MICROPY_HW_ENABLE_RNG (0) beneath it, the board's replacement rng.c defining pyb_rng_get_obj, and its #error "this code replaces normal RNG module" guard are all present for the Mk3 board in the first public firmware commit, 9f04ac1b, 24 July 2018. The 2021 "runs" commit carried that existing pattern into the new Mk4 board directory rather than originating it. Verified Contested
A second contributing commit has a one-character message, "x", and changes about 1,000 lines. No such commit was located. A search of every reachable commit message in the clone found no single-character message "x", and the Q board directory was added on 10 January 2023 under the message "pull basics from mk4". Unverified
The board's rng.h declares pyb_rng_get_obj and pyb_rng_get_bytes_obj, duplicating what the stock stm32 rng.c defines, so the two cannot be compiled together. Confirmed verbatim. Pre-hotfix stm32/COLDCARD/rng.h declares exactly those two objects and nothing else, the board rng.c defines pyb_rng_get_obj at line 110, and MicroPython's ports/stm32/rng.c defines the same object under #if MICROPY_HW_ENABLE_RNG. The board file's own #error guard states the mutual exclusion outright. Verified
Setting the macro to zero removed the stock hardware code, and with it the real rng_get(), which then resolved to MicroPython's Yasmarang fallback. Confirmed structurally. MicroPython's ports/stm32/rng.c guards both the hardware rng_get() and pyb_rng_get_obj behind the same #if, and its #else branch supplies an rng_get() whose body is a call to pyb_rng_yasmarang(). The pre-hotfix board file defined no rng_get() of its own. Dettmer cites line 112 for the fallback; it sits at lines 97 to 99 in the clone head held here, so the line number is version dependent while the mechanism is not. Verified
The override targets pyb_rng_get_obj, the Python-visible pyb.rng() callable, which is not what seed generation invokes. Version 4.0.0 calls random.bytes(32), which reaches rng_get() instead. Confirmed, including the quoted code. At tag v4.0.0, shared/seed.py:354 reads seed = random.bytes(32), and shared/random.py in the same tag binds bytes = ngu.random.bytes. The call therefore enters libngu and resolves through CHIP_TRNG_32() to rng_get(), never touching the overridden Python callable. Verified
The developer hit a duplicate-symbol error, silenced it by setting the macro to zero out of frustration, and shipped code he did not understand. The structural half is corroborated above. The account of what the developer experienced, intended or understood is the author's inference and cannot be established from a commit history. Coinkite's own backgrounder gives a different account of the macro's intent, which the archive records rather than reconciles. Reported
What the two datings together establish

The board configuration Dettmer identifies and the library change others identify are not competing explanations, and the history reconciles them. The (0) define and the missing rng_get() sat in the tree from July 2018 without consequence, because no seed-path caller reached that symbol. Commit b18723dd of 1 March 2021, titled "First pass w/ libNgU", moved seed generation onto a library whose CHIP_TRNG_32() resolves to exactly that symbol. An inert board configuration became a live defect when a second change routed the seed through it. The 31 July hotfix closes it from the board side, commit 45436299 adding rng_get() to the board file and a build rule that compiles an empty object in place of the fallback so it cannot be linked at all.

Where public discussion was found

Public technical-discussion record checked on 1 August 2026
VenueRecord as of 1 August 2026
Bitcoin Optech Newsletter #416 led with the incident and placed "Move funds secured by COLDCARD-generated keys" in its Action items section.
bitcoin-dev No thread was found in a search on 1 August. The list generally focuses on protocol and consensus topics.
Delving Bitcoin No thread was found in a search on 1 August.
MicroPython upstream No issue was found in a search on 1 August. MicroPython provides the software fallback; the ineffective guard was in libngu.

Two details from Optech

Coordination and anonymous researchers

Optech describes Block's analysis as "performed with anonymous researchers and disclosed in coordination with Coinkite". This is Optech's report of coordination and anonymous participation. It does not identify who originally found the defect or explain why the initial affected-model lists differed.

AI-assisted reproduction

Optech states that several developers "were able to immediately reproduce the attack with the assistance of frontier AI models" and advised treating the vulnerability as actively exploitable. This concerns reproduction after disclosure, not the unknown original discovery method, a distinction the AI page works through in full.

Downstream public reactions

The disclosure also prompted decisions and reviews in other public repositories. These records show what those projects did or proposed. They do not establish that another product shared COLDCARD's defect.

Selected high-signal repository reactions checked on 1 August 2026
Record Response Public state
Bitcoin.org #4905 Removed the COLDCARD and COLDCARD Q wallet listings under the site's criterion concerning evidence that users were harmed by a listed wallet issue. Merged 1 Aug at 02:13 UTC.
SatSigner #468 Reports an incident-prompted entropy audit. The author says the default OS-backed path was sound and proposes fixes for separate optional dice and coin paths. Open No review appears in the captured state.
SeedSigner #962 Proposed camera-entropy health checks, then withdrew them after the author concluded the thresholds did not measure sensor entropy and the synthetic tests were not valid evidence. Closed unmerged No SeedSigner product change resulted from this proposal.

Follow-up items as checked on 1 August 2026

  • All four tracked proposals were open in the 1 August captures. libngu #59 had an initial maintainer response and a proposed split. libngu #58 and #60 and COLDCARD #691 had no review in those captures.
  • Coinkite promised a technical postmortem. The captured statement does not give a publication date or detailed scope.
  • No captured source announces an independent audit of the fixed firmware. This is an archive limitation, not proof that no private review exists.
  • No incident-specific CVE record was identified. NVD keyword searches for COLDCARD and Coinkite returned only the earlier CVE-2019-14356 OLED side-channel record. A differently worded or unpublished request could be missed.
  • The two reproduction scripts described by Gregory Sanders (instagibbs) remain unavailable here. A dated search of his public GitHub repositories, code results and gist index found no matching publication, so the archive still cannot inspect the test conditions or method.
  • A fixed synthetic Mk3 regression vector was published in this archive on 1 August. It exercises one stated device-state path and cross-checks BIP39 and BIP32 reference vectors. It is not a real-device population measurement or attack benchmark.

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.