TORONTO, 2 AUG 2026 — A single preprocessor guard in the firmware of Coinkite's Coldcard hardware wallet tested whether a configuration macro existed rather than what it was set to, and for five years the device generated its most important secrets from a non-cryptographic software generator instead of the hardware one it shipped with. Beginning at 01:31 UTC on 30 July, attackers swept 1,083 bitcoin from 1,196 addresses in 41 minutes. By 2 August the total across three waves stood at 1,367 bitcoin from 4,585 addresses, worth roughly $89 million, according to Galaxy Research figures reported by CoinDesk.

Figures re-checked 2 August 2026 and still moving. Every number below is attributed and dated.

For the incident itself — the sweeps, the running totals and Coinkite’s response — see our news report, Attackers sweep 1,367 bitcoin from Coldcard wallets after five-year firmware flaw. This piece examines how the defect came to exist.

The theft is the visible part. The interesting part, for anyone who builds software that depends on a compile-time switch, is that none of this required a cryptographic weakness. The cryptography was fine. The build configuration was not.

0the value of the macro that was supposed to disable a fallback
5 yearsbetween the vulnerable firmware shipping and its discovery
~40 bitsCoinkite's own estimate of Mk2/Mk3 seed entropy against a 128-bit target
1,367 BTCswept across three waves as reported on 2 August

What happened

Coldcard is a Bitcoin hardware wallet: an air-gapped device whose entire purpose is to generate and hold a private key that never touches a networked machine. The security of everything it does rests on one assumption — that the twelve or twenty-four words it produces at setup are drawn from genuine, unpredictable entropy.

On 30 July 2026 Coinkite published a preliminary advisory for the Mk3 device. Block's engineering team published a root-cause analysis the same day. Between 31 July and 1 August, fixed firmware appeared for every affected track and the scope widened from one model to most of the product line.

Coinkite chief executive Rodolfo Novak, who trades as NVK, publicly accepted full accountability for the firmware bug and acknowledged that the company's review process had not caught it. That is on the record. Vendors in his position rarely do.

Computed by RECATOOLS2 August 2026
DateEvent
28 Jan 2021The vulnerable guard exists in libngu
1 Mar 2021Commit b18723dd moves seed generation onto libngu
17 Mar 2021Firmware v4.0.0 ships the vulnerable path
11–14 Mar 2022A 32-bit reseed is added; Mk4 v5.0.0 carries it
30 Jul 2026, 01:31–01:56 UTCFirst sweep — 1,083 BTC from 1,196 addresses
30 Jul 2026Coinkite Mk3 advisory; Block publishes root-cause analysis
31 Jul – 1 Aug 2026Fixed firmware on all tracks; scope expands

Commit and firmware dates from Block Engineering's analysis and the referenced repositories. Sweep timing and volumes from Galaxy Research as reported by CoinDesk.

A macro that was defined, and set to zero

Coldcard does not want MicroPython's random number generator. It ships its own hardware RNG wrapper around the STM32 peripheral, so the board configuration sets MICROPY_HW_ENABLE_RNG to 0 to turn the interpreter's version off.

MicroPython, however, branches on the macro's value. Seeing zero, it does not omit rng_get(). It compiles a fallback instead — Yasmarang, a small non-cryptographic pseudorandom generator intended for the case where no hardware source exists.

The second half of the failure is the guard that was supposed to prevent exactly this. In libngu, the cryptographic library Coldcard depends on, the check reads #ifndef MICROPY_HW_ENABLE_RNG followed by an #error. That tests only whether the macro has been defined. It had been — as zero. The guard saw a defined macro, concluded the hardware path was live, and let the build proceed.

The third half, if a failure can have three halves, is the linker. Coldcard's own true random number generator exports random32() and random_buffer(). It does not export a global rng_get(). So libngu's external reference to rng_get() resolved to the only symbol of that name in the image: MicroPython's fallback. The signatures matched, so nothing complained.

This is the sentence worth carrying out of the story. Code present in the binary is not the same as code reached at runtime. The hardware RNG was compiled, linked and shipped. It was simply never called for this purpose. Every review that confirmed the hardware RNG existed was answering a question nobody needed answered.

Six safeguards, six failures

What makes this instructive is that it was not a single point of failure. Six independent mechanisms could each have caught it, and each was defeated by a different property of the same mistake.

Computed by RECATOOLS2 August 2026
SafeguardWhy it did not fire
The board config disabling MicroPython's RNGMicroPython branches on the value; zero selects the software fallback rather than omitting the function
libngu's #error build guard#ifndef tests definition, not value — the macro was defined, so the guard passed
The linkerColdcard's TRNG exports random32(), not rng_get(); the extern bound to MicroPython's fallback with matching signatures
The adjacent-repeat health checkA deterministic PRNG produces different adjacent outputs, so the check passes trivially
The "more than four distinct bytes" assertion on the seedA PRNG output satisfies it without difficulty
SHA256d over the collected entropyHashing cannot enlarge the input family; a small set of inputs stays a small set of outputs

Mechanisms and the reason each passed are drawn from Block Engineering's root-cause analysis. The framing as six independent layers is RECATOOLS' own.

The fallback seeds itself once, on the first call, from three values: the chip's unique identifier exclusive-ORed with a SysTick counter, the real-time clock's time register, and its sub-second register. None is cryptographic entropy. After that first call there is no reseeding at all — every later output is a deterministic step from a state fixed at boot.

libngu then combines that stream with a second Yasmarang generator of its own, initialised from constants written into the source: a pad of 0x0a8ce26f, an n of 69 and a d of 233. Combining two reproducible streams does not produce an unpredictable one. It produces a reproducible one.

Why cryptocurrency turns a weak generator into a total loss

A predictable PRNG is a serious defect in any system. Bitcoin converts it into something worse, because of a property that has nothing to do with the bug: every address derived from a key is published on a public ledger, permanently, along with its balance.

That gives an attacker something most systems never provide — an oracle that answers instantly, offline, at no cost, and without touching the target. There is no login attempt to rate-limit, no account to lock, no anomalous session to detect, and no victim-side telemetry at all. The first thing a Coldcard owner learned about any of this was that their balance was gone.

The search-space figures explain the rest. Block gives them by device and firmware, and is careful about what they do and do not mean.

Computed by RECATOOLS2 August 2026
Device and firmware at seed creationSearch spaceNote
Mk1, and Mk2/Mk3 through v3.2.2About 2^256Unaffected
Mk2/Mk3, v4.0.0 – v4.1.92^0 with known timersDeterministic; ceiling of about 2^40.7 where timer values remain hidden
Mk4/Q/Mk5 with a successful reseed≤ 2^32About 2^31 trials on average; loose ceiling of about 2^73.27

Figures published by Block Engineering. Block states explicitly that the 2^73.27 ceiling is NOT 73-bit cryptographic security: the timer fields are correlated, may occupy far smaller ranges, and can potentially be observed or reconstructed. Coinkite's own preliminary estimates are approximately 40 bits for Mk2/Mk3 and approximately 72 bits for Mk4/Q/Mk5, against a 128-bit design target.

The 2022 mitigation looks like a fix and is not one. Reseed material is taken from two secure elements, hashed with SHA256d — and then only the first four bytes are used, overwriting a single state word of the Yasmarang generator. No cryptographic DRBG is initialised, MicroPython's own fallback is never reseeded, and the remaining state words are left alone. Entropy input is capped at 32 bits regardless of how much the secure elements supplied.

Exposure follows the firmware that was running when the seed was created, not the device model and not the firmware installed today. Updating a Coldcard does not repair a seed it generated earlier, and a seed exported into a different wallet carries the weakness with it.

What else came out of the same generator

Coverage has concentrated on wallet seeds. Block's analysis enumerates a longer list, and this is the part most likely to be missed by someone who has already checked their balance and moved on. The same ngu.random stream produced:

  • New and ephemeral wallet seeds. Not only the seed created at setup.
  • Paper-wallet and secp256k1 private keys. These are used directly as the private key, with no BIP32 derivation step in between.
  • Seed XOR masks. A scheme whose whole purpose is splitting a secret into parts that are individually useless.
  • Cloning, USB, Key Teleport and Web2FA ECDH keys. The key-agreement material for moving secrets between devices.
  • Secure Notes passwords generated on the device.
  • HSM local-code material.

Seed XOR is the sharpest example of how a weak generator defeats a good design. The scheme is sound: split a seed into shares so that no single share reveals anything. If the masks come from a predictable stream, the shares stop being independent of each other, and a construction chosen specifically to reduce risk quietly stops doing so.

Who is exposed, and what actually helps

Coinkite has published fixed firmware for every affected track. Installing it protects seeds created from that point on; it does nothing for a seed already generated.

Computed by RECATOOLS2 August 2026
TrackFixed from
Mk2 / Mk3v4.2.0
Mk4 / Mk5 (standard)v5.6.0
Q (standard)v1.5.0Q
Mk4 / Mk5 (Edge)v6.6.0X
Q (Edge)v6.6.0QX

Versions as published by Coinkite. TAPSIGNER, OPENDIME and SATSCARD are stated by Coinkite to be unaffected because they use different codebases — a vendor self-assessment issued within days of disclosure, not an independent finding.

Coinkite names exactly two circumstances in which an existing seed is not considered at risk. Either one on its own is sufficient, and whichever applies had to be true at the moment the seed was made — neither can be added later.

  • At least 50 independent, private dice rolls contributed during seed generation.
  • A strong, unique BIP-39 passphrase, kept secret and stored separately from the seed backup. Coinkite explicitly excludes passphrases that are short, common, patterned, quoted, reused, exposed or uncertain.

Multisignature is worth a closer look, because it is the arrangement people adopt specifically to survive a single device failing. A quorum assembled entirely from affected devices inherits the weakness in full — the redundancy is nominal when every key came from the same generator. Meaningful protection requires enough unaffected devices to satisfy the quorum on their own.

If your interest here is the entropy question rather than the asset, our password generator and hash generator both run entirely in the browser using the platform's cryptographic random source, which is the behaviour this incident is a lesson in demanding.

This has happened before, repeatedly

Randstorm, Milk Sad (CVE-2023-39910), the Trust Wallet browser-extension flaw (CVE-2023-31290) and Coinspect's "Ill Bloom" disclosure in July 2026 are all the same failure with different surface details: a generator that looked adequate, produced output that passed casual inspection, and drew from a space small enough to enumerate.

The problem is not a failure to understand randomness. It is that no mandatory entropy-validation standard exists for BIP-39 implementations. Nothing compels a wallet vendor to demonstrate, to anyone, that the bytes behind a seed came from where the documentation claims. There is no equivalent of a FIPS validation or a Common Criteria evaluation that a buyer can look for, so the assurance a customer receives is the vendor's own account of its own build.

Statistical testing does not close the gap. A well-constructed PRNG passes randomness test suites comfortably — that is what it is designed to do. Those suites measure the shape of the output, not the size of the input space, and it is the input space that determines whether a key can be recovered.

What this looks like from Southeast Asia

Singapore's and Malaysia's exposure here is mostly indirect, and it is more interesting for the regulatory question it raises than for the holdings involved.

Under the Payment Services Act, digital payment token service providers in Singapore that hold customer assets operate under MAS custody requirements, and institutional custody in the market runs on hardware security modules and multi-party computation rather than consumer hardware wallets. The direct institutional exposure to this specific product is therefore limited. The individuals holding Coldcards across the region — Singapore and Malaysia both have substantial self-custody communities — have no regulator to appeal to and no counterparty to make them whole, which is the arrangement they chose and, in most cases, understood.

The transferable question for regional CISOs is not about cryptocurrency. Malaysia's National Cyber Security Agency and Singapore's CSA both operate frameworks for critical information infrastructure that require vendors to attest to secure development practice. This incident is a precise example of the limit of attestation: Coinkite's documentation was accurate, its hardware RNG was real, its intent was correct, and the shipped binary still did the wrong thing. A supplier questionnaire that asks whether a hardware random source is used would receive a truthful answer, and that answer would be useless.

Any organisation in the region procuring embedded devices — payment terminals, telemetry units, smart-metering endpoints — is buying the same class of assurance, and can ask for better: not whether a hardware RNG is present, but whether the supplier can demonstrate which code path actually reached it in the shipped image.

What engineering teams should take from this

Four practices would each have broken the chain, and none of them is exotic.

  1. Make build guards fail closed on value, not existence. A guard that tests #ifndef confirms that somebody remembered to define a macro. If the safety property depends on the value, test the value — and add the inverse assertion, so that a disabled feature that silently re-enables itself as a fallback stops the build.
  2. Verify symbol binding in the link map. The linker recorded exactly which rng_get() won. Nobody read it. For a small number of security-critical symbols, asserting in CI which object file resolved them is cheap and would have caught this precisely.
  3. Test reachability, not presence. Coverage tools and call-graph analysis answer a different question from "is the function in the binary". For the handful of paths that matter — entropy, key derivation, signature generation — assert in CI that the intended implementation is the one actually invoked.
  4. Test the generator by generating repeatedly. The health checks examined single outputs. Producing many values from a freshly booted device, across power cycles, and comparing across units would have exposed a state fixed at boot immediately. A generator that yields the same sequence on two devices is not a subtle finding.

On the claim that has attracted the most attention: Coinkite has suggested the flaw may have been surfaced with AI assistance, describing the episode as a reality of a new paradigm. That is the company's supposition; no evidence has established how the bug was found, and researchers have pointed out that a preprocessor guard testing definition instead of value is a documented bug class that conventional review finds. The exploitation itself involved no AI at all — it was brute-force computation against a public ledger. Coinkite has itself used AI tooling to scan its own code. Whether a model found this bug first or not, the defects sat reachable by ordinary review for five years.

Frequently asked questions

Does updating my Coldcard firmware fix a seed I already have?

No. Fixed firmware protects seeds generated after the update. A seed created under vulnerable firmware retains whatever entropy it was given at creation, and no later software change alters that. Exposure follows the firmware that was running when the seed was made, not the model and not the version installed now. A seed exported into a different wallet carries the same weakness into that wallet.

My device is a Mk4, not a Mk3. Am I safe?

Not automatically. The Mk4, Mk5 and Q added a reseed step in 2022 that raised the difficulty, but Block found it accepts only four bytes of hashed secure-element output into a single state word, capping entropy input at 32 bits. Coinkite's own preliminary estimate is roughly 72 bits against a 128-bit target. Block cautions that its looser 2^73.27 ceiling should not be read as 73-bit security.

I used a BIP-39 passphrase. Does that protect me?

Coinkite states that a strong, unique passphrase, kept secret and stored separately from the seed backup, is one of only two circumstances in which an existing seed is not considered at risk. The company explicitly excludes passphrases that are short, common, patterned, quoted, reused, exposed or uncertain. The other exemption is at least 50 independent, private dice rolls contributed during seed generation.

Does multisignature protect me if all my devices are Coldcards?

No. A quorum assembled entirely from affected devices inherits the weakness in full, because every key in the arrangement came from the same generator. Multisig protects against one device failing independently of the others, which is not the situation here. Meaningful protection requires enough unaffected devices to satisfy the quorum without the affected ones.

Was this bug found by AI?

Unproven. Coinkite has suggested AI may have been involved, and no evidence has established how the flaw was discovered. Researchers have noted that a preprocessor guard testing whether a macro is defined rather than what it holds is a well-documented bug class that conventional review can find. The exploitation was brute-force computation against public ledger data and involved no AI.