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