Entropy Is a Systems Property
On July 30, 2026, Coinkite published a security advisory for COLDCARD wallets. The advisory concerns the first operation a wallet performs and the one it can never repeat for an existing wallet: generating its root entropy.
The immediate guidance belongs to Coinkite. As of July 31, the company identifies seeds generated by Mk3 firmware 4.0.1 through 4.1.9 as affected. It also identifies seeds generated by Mk4 and Mk5 before 5.6.0, and by Q before 1.5.0Q, as affected with lower severity. Updating firmware corrects future generation. It does not repair a seed that already exists.
Block’s Bitcoin Engineering and Security teams published a parallel source-level analysis. Their report describes an active theft scenario, while noting that full empirical validation was still in progress at publication. Coinkite’s formal technical review is also pending. Some facts may change as both investigations continue.
The code path already shows a durable engineering lesson. SHA-256, BIP-39, BIP-32, and secp256k1 remained intact. The failure occurred in the integration between a build-time configuration, a library abstraction, a fallback generator, and a reseed interface.
Every component appeared to be producing random-looking bytes. The system did not preserve the entropy that the wallet’s security claim required.
The failure began at a preprocessor boundary
COLDCARD firmware defined the MicroPython hardware-RNG feature flag as zero:
#define MICROPY_HW_ENABLE_RNG (0)
COLDCARD intentionally disabled that implementation because it had a separate wrapper for the STM32 hardware RNG. Another library, libngu, tested whether the macro existed. In simplified form:
#ifndef MICROPY_HW_ENABLE_RNG
#error
#endif
The test answers a narrow question: is the name defined? It does not answer the security question: is the hardware RNG implementation enabled?
Because the macro existed with a value of zero, the build succeeded. Libngu bound its rng_get() reference to MicroPython. MicroPython evaluated the value of the same macro, selected its software fallback, and compiled a Yasmarang pseudorandom generator instead of the STM32 hardware RNG path.
The relevant distinction is one token:
defined(MICROPY_HW_ENABLE_RNG) != value(MICROPY_HW_ENABLE_RNG)
A build guard intended to require a hardware source therefore certified a build in which that source was disabled.
The fallback looked random and remained predictable
MicroPython’s fallback initialized Yasmarang from values including the low 32 bits of the MCU identifier, SysTick, and real-time-clock registers. These values can vary. They are not cryptographic entropy.
The MCU identifier is fixed metadata. Timer registers are correlated with boot time and code execution. Once those values and the number of previous generator calls are known or constrained, the resulting stream is reproducible.
Libngu then XORed that output with a second Yasmarang stream initialized from public constants. XOR can combine independent secret entropy. It cannot manufacture entropy from two reproducible streams.
The health check did not expose the problem. It rejected adjacent duplicate words. A deterministic pseudorandom generator normally emits different adjacent values, so the fallback passed. Statistical variety was mistaken for unpredictability.
This distinction matters in every key generator. A sequence can look uniformly distributed and still come from a small, enumerable family of states.
The regression entered the wallet-generation path
Earlier Mk2 and Mk3 firmware generated wallet entropy through COLDCARD’s direct hardware wrapper. In March 2021, wallet generation moved from that path to ngu.random.bytes(32). The new abstraction made the call site simpler, but its implementation resolved to the fallback described above.
For Mk2 and Mk3 firmware on the affected branch, Block found no later contribution from a cryptographically secure source. Given the fallback state and call history, wallet generation was deterministic. Uncertainty about device identifiers and timing can enlarge an attacker’s search, but those values do not have the properties of independently sampled secret entropy.
Later COLDCARD generations added secure-element input at boot. The construction read secure-element values, hashed them, took four bytes of the digest, and passed one 32-bit integer into ngu.random.reseed().
The reseed operation replaced one 32-bit state word in the second Yasmarang generator. It did not initialize a cryptographic deterministic random bit generator. It did not reseed the MicroPython fallback. It did not retain the rest of the digest.
For a fixed fallback state and call history, the secure-element contribution can therefore distinguish at most 232 output streams. Coinkite currently characterizes affected Mk4, Mk5, and Q seeds as having about 72 bits of entropy rather than the expected 128 bits. Block’s analysis separates that estimate into a 32-bit secure contribution plus uncertainty from device and timer state, and cautions that timer uncertainty should not be treated as equivalent to cryptographic entropy.
Both descriptions point to the same engineering defect. High-quality source material entered the system, then an interface reduced it to four bytes before it could protect the generator’s output.
Hashing cannot restore discarded entropy
The affected wallet path hashed 32 generated bytes with double SHA-256 before using the result. That can make biased input look uniform. It cannot enlarge the set of possible inputs.
For a deterministic function f and a source X:
|support(f(X))| <= |support(X)|
If an attacker needs to enumerate at most 232 generator states, hashing every candidate produces at most 232 candidate hashes. The output strings may look like arbitrary 256-bit values, but they still belong to a family indexed by the smaller state space.
The BIP-39 checksum has the same property. It detects transcription errors. It adds no secret entropy.
An address, extended public key, or other derived public value supplies the validation oracle. An attacker can generate a candidate entropy value, derive the BIP-39 and BIP-32 wallet, calculate its public output, and compare. The search is offline and parallelizable. The attacker does not need repeated access to the device.
Why Cryptograph delegates entropy to Apple
Cryptograph does not implement a pseudorandom generator, collect user gestures, mix timer state, or maintain an application-level reseed schedule on Apple Watch. Wallet creation delegates the entropy request to Apple’s Security framework.
The production path is deliberately short:
create wallet on Apple Watch
-> request 32 bytes from SecRandomCopyBytes(kSecRandomDefault, ...)
-> encode all 256 bits as a 24-word BIP-39 mnemonic
-> validate the mnemonic
-> encrypt it under a Secure Enclave-owned wrapping key
-> store only the encrypted envelope
Our public wallet-core source provides the native random_buffer symbol used by BIP-39 generation. It calls SecRandomCopyBytes for the requested length. If the API returns anything other than errSecSuccess, wallet generation terminates. There is no application fallback and no partially initialized buffer that can continue into key generation.
The BIP-39 path requests 32 bytes and consumes all 32 bytes for a 256-bit mnemonic. The BIP-39 specification defines a checksum that expands those 256 entropy bits into 264 encoded bits, which map to 24 words. The checksum is an integrity aid, not part of the entropy budget.
This is often summarized as trusting Apple’s hardware RNG. The exact boundary is more useful.
Cryptograph trusts Apple’s platform random service. Apple documents SecRandomCopyBytes as returning cryptographically secure random bytes. Apple also documents a kernel cryptographic pseudorandom number generator on watchOS and its other operating systems. That generator is derived from Fortuna, targets a 256-bit security level, and aggregates multiple inputs over the life of the device. Documented inputs include the Secure Enclave hardware true random number generator, boot-time timing jitter, hardware-interrupt entropy, and a seed file that carries entropy across boots.
The Secure Enclave TRNG itself uses multiple ring oscillators with CTR_DRBG post-processing. Cryptograph does not read that raw hardware output directly. Apple does not publish every internal step between SecRandomCopyBytes and the kernel entropy machinery, so we do not describe the call as a direct TRNG read. We use the supported cryptographic API and rely on Apple’s documented platform design behind it.
That boundary has several properties we wanted.
One maintained entropy pool
The operating system can combine independent sources, preserve state across boots, and continuously incorporate new events. An application-local generator would need to reproduce those lifecycle properties correctly on a constrained wearable device.
No application PRNG state
Cryptograph has no seed file, generator state, call counter, timer-derived initialization, or reseed API of its own. There is no local state machine whose entropy can be truncated during a refactor.
A narrow audit surface
The wallet requests exactly 32 bytes from one public API. The source review can follow those bytes directly into BIP-39. There is no chain of wrappers that silently changes providers according to preprocessor state.
Fail-closed behavior
SecRandomCopyBytes returns a status code. We check it. Failure stops wallet generation. A cryptographic RNG failure is not an availability problem that should trigger a compatibility fallback. It is a condition under which no wallet should be created.
Platform ownership of hardware variation
Apple owns the relationship between watchOS, each supported system-on-chip, the Secure Enclave generation, boot behavior, interrupts, and future hardware revisions. Cryptograph consumes the same stable contract across those devices. We do not maintain a board-specific matrix of entropy implementations.
Why we do not add our own entropy mixer
Combining independent entropy sources can be sound. A carefully analyzed construction can preserve the security of one good secret source while hedging against another source’s failure. The important words are independent, secret, and carefully analyzed.
Cryptograph does not have a second source on Apple Watch with stronger properties than the platform service. Touch timing, Digital Crown movement, boot timing, device identifiers, sensor readings, and scheduler behavior may vary. Variation is not a defensible entropy estimate. Most of those signals are observable, correlated, affected by the operating system, or controlled by the same device already inside the Apple trust boundary.
User-supplied dice can be independent, but it creates a different product and security ceremony. The implementation must enforce enough fair rolls, preserve their order, prevent recording or disclosure, make the mixing rule verifiable, handle cancellation and resumption safely, and explain which final wallet corresponds to which completed ceremony. A few optional interactions create a reassuring animation and very little measurable entropy.
Adding a local generator after SecRandomCopyBytes would add still more state: initialization, reseeding, locking, persistence, fork behavior, health checks, and error handling. If the Apple output is already secure, that machinery does not improve the practical brute-force margin of a 256-bit BIP-39 wallet. If the Apple output is compromised, inputs gathered through the same operating system should not automatically be counted as independent.
The choice is therefore explicit. Apple supplies the entropy. Cryptograph consumes exactly 256 bits, checks the API result, and performs no application-level fallback. A future design that adds an independent source would need its own threat model, entropy budget, combiner analysis, and end-to-end tests. It would not be described as free defense in depth.
What this choice does not prove
Delegating randomness does not eliminate trust. It moves the trust boundary.
Cryptograph’s side of that boundary is open source. The complete random_buffer implementation is five lines: call SecRandomCopyBytes for the requested length, compare the result with errSecSuccess, and abort on failure. Anyone can verify that there is no secondary generator, timer input, truncated reseed, or weak fallback behind our call site.
We trust Apple’s hardware implementation, operating-system entropy code, code signing, update process, and Security framework contract behind that open-source call. Those Apple components are not all open source, and Cryptograph cannot independently inspect the physical noise source on each watch. A compromise that lets an attacker control or intercept random output during wallet creation would still be serious.
The mnemonic is also not generated inside the Secure Enclave. It is generated in native application memory on Apple Watch from the 32 bytes returned by the platform service. Our native path then validates it, encrypts it using a P-256 key owned by the Secure Enclave, and clears the plaintext buffers. The encrypted envelope is stored in the watch Keychain with kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly and without synchronization.
These are separate controls:
- the platform random service supplies unpredictable wallet entropy;
- BIP-39 encodes that entropy for deterministic recovery;
- the Secure Enclave-owned key protects the mnemonic at rest;
- native buffer clearing reduces plaintext lifetime in memory;
- watch-side transaction decoding binds later approvals to the bytes being signed.
Strong randomness cannot compensate for a leaked mnemonic. Secure storage cannot compensate for a predictable mnemonic. A complete wallet needs both.
The test that matters is end to end
An RNG review should not stop after identifying a hardware block or finding a cryptographic function name. The useful review follows the entropy budget from physical source to final key material.
For each transition, the questions are concrete:
- Which implementation is selected in the production build?
- Does a feature test check a macro’s value or merely its presence?
- Can symbol resolution bind the caller to a different provider?
- What happens when the preferred source fails?
- How many secret bits enter each reseed interface?
- Does any truncation occur before key generation?
- Is the generator cryptographic, or only statistically varied?
- Can a health test pass for a deterministic stream?
- Does hashing whiten bias while leaving a small candidate set unchanged?
- Is there a public value that lets an attacker validate candidates offline?
The COLDCARD issue crossed several of these boundaries at once. None of the individual lines looked like a complete wallet compromise. Together they changed a purported 256-bit generation path into an enumerable family of wallets.
Our decision on Apple Watch was to own less of that path. We ask the platform for the full entropy budget in one call. We do not dilute it, supplement it, reseed it, or fall back from it. If the call fails, wallet creation fails with it.
That is a deliberate trust decision. In cryptographic engineering, a small and explicit trust boundary is usually more defensible than a larger construction assembled from individually plausible parts.
Technical note, July 31, 2026: This post reflects Coinkite’s advisory as updated at 12:39 p.m. EDT and Block’s source analysis published July 30. The investigation is ongoing. Affected users should follow Coinkite’s current migration guidance rather than relying on this engineering discussion.
The Cryptograph Team