Favilla field notes · entropy · August 2026
We measured where the randomness in a hardware wallet actually comes from. Two of the things we believed turned out to be wrong, and the tests that would have caught the Coldcard failure are not the ones anyone runs.
In July, Coldcard disclosed that five years of devices had been generating seeds from a software pseudo-random generator instead of the hardware chip they were built around. The output looked perfect. It passed every uniformity test. It was also guessable, and roughly 1,367 BTC left those wallets in about a week.
That failure has an uncomfortable property: nothing about the output revealed it. So we stopped asking "is our randomness good" and started asking three separate questions, because they have different answers and they trade off against each other.
A source can ace one and fail the others. Coldcard's chip was fine on custody and hardening; it failed on the third question so completely that nobody could tell it had failed the first. Here is what we found when we pointed instruments at our own hardware.
"Shake your device to add your own randomness" is a feature several wallets offer. We were going to headline it. Here is the raw X-axis from our BMA400, on one scale — someone shaking the device vigorously, and the same device sitting untouched on a desk.
Obviously different. Now the same two recordings, but showing only the bottom bit of each sample — the part that carries the noise.
You cannot tell them apart, and neither could our estimators. Measured min-entropy came out at 2.855 and 2.860 bits per sample for two deliberately different shakes, and 2.725 bits for the device lying still. A 4.6% difference, against motion statistics that differed by 16–75%.
The motion rides in the upper bits, where it is smooth and useless. The bottom bit is the sensor's thermal noise, and it does not care whether a human is in the room.
So the accelerometer is not a user-entropy source. It is a sensor-noise source with a person standing next to it. If we shipped "shake to generate your seed," the honest description would be: hold the device however you like while its accelerometer's thermal noise makes your key. The user's involvement would be ceremony — and a ceremony that feels like participation while contributing nothing is worse than no ceremony, because it manufactures confidence it hasn't earned.
A single 320×240 frame looks like an enormous pile of entropy. Count the bytes naively and you get 392,538 bits per frame. That number is wrong, and it is wrong in the direction that flatters you.
Most of what varies inside one frame is scene and fixed-pattern noise — the same room, the same dust on the lens, every single time. The only creditable part is what changes between frames of an identical scene. Strip the fixed pattern out and the honest figure is about 70,000 bits.
Then we covered the lens.
| Settled frames | Lit scene | Lens covered |
|---|---|---|
| Bits per pixel | 0.918 | 0.084 |
| Pixels that never change | ~0% | 51.0% |
| Consecutive frames identical | 42% | 95.0% |
| Honest bits per frame | ~69,800 | ~6,700 |
Half the sensor freezes solid. A ten-fold collapse — and the deciding variable is how much light is in the room, which the user controls and the device never checks. Generate a seed at night, or with the wallet face-down in a drawer, and you are on the right-hand column.
In the dark, the naive estimate comes out at 4,472 bits per frame — lower than the honest 6,450. The naive method only overstates when there is scene content to fool it. Faced with a near-black frame it correctly reports almost nothing.
Naive estimation is closest to correct when the source is at its worst, and furthest from correct when it looks its best.
Which means a developer who tests camera entropy in a well-lit room gets the most flattering number available and the most wrong one, simultaneously, with nothing in the result to hint at the problem. Testing under good conditions feels like diligence. It is the opposite.
A seed is generated once, at one moment, on one boot. So frame-to-frame variation within a session tells you nothing useful. The test that matters is whether two cold boots produce different frames.
We ran two, five minutes unpowered each, pointed at an identical scene. Then compared frame k of boot A against frame k of boot B:
# bit agreement between the two boots (0.5 = independent, 1.0 = identical) transient frames 0-33 0.5007 settled frames 34-63 0.5001 # control: consecutive frames inside a single boot within one boot 0.5211 ← less independent
Frames from two separate power-ups are more independent than two frames one-thirtieth of a second apart. With the scene identical, nothing but the sensor could have differed. That is the result we would defend.
Along the way we found a warm-up transient — the sensor's output level swinging by up to 68 counts over the first few dozen frames. Including it doubles the apparent entropy, because any estimator reads that swing as noise. We assumed it was a deterministic power-up curve, which would have been dangerous. It isn't:
Good news for independence. Bad news for anyone who plans to "discard the first N frames" using a constant N.
Dice are slow, tedious, and the only source on this list that answers all three questions honestly.
A fair d6 gives exactly log₂6 ≈ 2.585 bits per roll. That is a proof, not an estimate — the one number here we can state without a lab. Ninety-nine rolls gives 256 bits. And because the construction is public and takes no device secret, you can check our work:
$ printf '3141592653...' | sha256sum a3f1... ← must match what the wallet showed you
That is the whole point. Every other source on this page asks you to trust us. This one lets you verify on an airgapped laptop that the wallet used the rolls you made and nothing else. It is the direct answer to "you couldn't check the RNG" — and it is why it survived our own attempt to improve it by mixing the secure element in. The moment a device secret enters, reproducibility dies, and so does the only thing dice were for.
| Source | Unpredictable | Easy | Provable |
|---|---|---|---|
| Dice | Medium — provable bound | Slow (~99 rolls) | Yes |
| Secure element | High — on trust | Instant | No |
| Camera | Medium lit / Low dark | Instant | No |
| Accelerometer | Low — and not yours | Instant | No |
| MCU software RNG | Low, condition-dependent | Instant | No |
The top-right corner is empty. Nothing is both high-quality and user-verifiable, because the two exclude each other: verifiability needs a public reproducible construction, and the best sources are sealed boxes. We do not think a single source fills that gap. Whether a protocol could — something commit-reveal shaped, where neither the device nor the user alone decides the seed but the user can still check it — is the open question we would most like someone to attack.
Measure before the hash. SHA-256 makes a stuck sensor, a counter, and a real TRNG all look identical. Every number on this page is from raw pre-hash samples. If you measured after conditioning, you measured SHA-256.
Budget from the worst condition you can find, not the average. The camera's honest number is the dark one. The accelerometer's is the still one. Users will find your worst case without trying.
Passing tests proves very little. We threw everything we had at our data — collision counts, Hamming distances across every 256-bit window, n-gram repeats, Markov predictors to context length 12. All clean. And every one of those tests would be passed, comfortably, by the exact generator that just cost Coldcard users 1,367 BTC. Output statistics detect gross failure. They cannot establish that an input was unpredictable. That is not a gap in our method; it is why the third question exists.