← Notes

Favilla field notes · entropy · August 2026

Shaking it does nothing

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.

Question 1
Is it actually unpredictable?
Question 2
Is it easy to produce?
Question 3
Can you prove it was?

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.

The accelerometer: the user is not contributing

"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.

Shaken — full ±2048 range
At rest — same scale
240 consecutive samples, X axis, plotted on the sensor's full ±2048-count range. Shaken spans 2,686 counts. At rest spans 14.

Obviously different. Now the same two recordings, but showing only the bottom bit of each sample — the part that carries the noise.

Shaken — least significant bit
At rest — least significant bit
120 samples each. Orange = 1, grey = 0. These are the bits an entropy budget would actually credit.

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.

Unpredictable
Moderate — but from the chip, not you
Easy to produce
Trivial — 256 bits in half a second
Provable
Not at all

The camera: five times less than it looks, ten times less in the dark

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 framesLit sceneLens covered
Bits per pixel0.9180.084
Pixels that never change~0%51.0%
Consecutive frames identical42%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.

The part that surprised us

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.

What actually convinced us the camera works

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:

Frame brightness over 64 frames — three captures, same sensor
Orange rises from 62 and settles near 118. Black falls from 148 to 103. Olive falls from 131 to 110. The two boot curves correlate −0.71 — not one warm-up shape, three, one of them inverted. It vanishes entirely in the dark, so it is exposure settling on available signal, not a fixed power-up sequence.

Good news for independence. Bad news for anyone who plans to "discard the first N frames" using a constant N.

Dice, and why we keep them

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.

The scoreboard

SourceUnpredictableEasyProvable
DiceMedium — provable boundSlow (~99 rolls)Yes
Secure elementHigh — on trustInstantNo
CameraMedium lit / Low darkInstantNo
AccelerometerLow — and not yoursInstantNo
MCU software RNGLow, condition-dependentInstantNo

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.

Three things we would tell anyone building this

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.

What this isn't: one unit, one temperature, two boots, and estimators we wrote ourselves rather than NIST's reference tool. These numbers are strong enough to have overturned two things we believed — they are nowhere near strong enough to budget bits against. Full method, data and caveats are in the long-form paper. If you can break any of this, we would rather hear it now than be trusted and wrong later.