feat(rng): one press unlocks a bulk RNG health test on an uninitialized device - #338
Open
BitHighlander wants to merge 2 commits into
Open
feat(rng): one press unlocks a bulk RNG health test on an uninitialized device#338BitHighlander wants to merge 2 commits into
BitHighlander wants to merge 2 commits into
Conversation
ENTROPY_FREE_BUDGET (64 KB/boot) is too small for the audit it exists to
enable. A 64-bit birthday scan over N blocks detects an effective keyspace of
~2*log2(N)-1 bits; 64 KB gives N=8192, so ~25 bits. The failure this mechanism
was built in response to -- a build-config slip yielding ~40-bit seeds that
passed every standard statistical test -- needs N=2^20, i.e. 8 MB, for ~39
bits. Measured on rc26 hardware, 64 KB pulls clean in 2.3s and passes every
test we have (birthday, chi-square, Shannon, adjacent-word), which is exactly
what a ~40-bit RNG would also do. Reaching 8 MB under the byte cap costs 128
manual replugs.
The cap was also asymmetric in the wrong direction. It never stopped a patient
remote attacker -- host malware just waits for the replugs that happen anyway
and accumulates 64 KB at a time -- while it fully priced out the honest
auditor, who needs one contiguous run.
So gate the bulk path on a single explicit press rather than a byte count, and
only before initialization:
- one confirm per boot unlocks unmetered draws. A remote host cannot forge a
press, which is the property the byte cap was only approximating.
- uninitialized only. No seed exists, so there is no key material to
correlate against. The 32 bytes that do become a seed are drawn later, in
reset.c, from noise that has not happened yet, and are SHA-256'd with
host-supplied entropy before use.
Initialized devices are unchanged: 64 KB, then a confirm every draw. The unlock
re-locks the moment ResetDevice completes, because storage_isInitialized() is
re-read on every call -- no replug needed. A wiped device can be audited again,
which is intended: it holds no seed, and re-auditing before re-seeding is the
supported flow.
Cost is one static bool and two branches. Device build green, all variants.
Review caught two overclaims in the previous comment, both worth fixing in the source rather than only in the PR thread. 1. "detects <= N bits" is wrong. A 64-bit scan over N=2^20 expects ONE collision at a 39-bit support, but P(0 collisions) is then e^-1 = 37%. Zero collisions excludes only <=37.4 bits at 95% confidence (<=36.8 at 99%). Reworded to "1 expected collision at k bits", which is what the arithmetic actually says. 2. More fundamentally, no output analysis can bound the entropy of an RNG's internal state. A strong PRNG seeded with 40 bits emits a stream that passes every one of these tests by construction -- which is precisely the shape of the failure that motivated this work, so citing it as the thing we now catch was backwards. Empirical Shannon entropy of a sample is likewise not cryptographic min-entropy; NIST SP 800-90B assesses the raw noise source and wants continuous RCT/APT health tests, which is separate future work. The justification for 8 MB is unchanged but restated correctly: the size is set by the POSITIVE control, not a detection threshold. A zero-collision result is meaningless alone -- a no-op detector also returns zero -- so the scan must run at a width where collisions are expected and counted against theory. 32-bit collisions expect N^2/2^33: 0.03 at 64 KB (control cannot run), 8 at 1 MB, 512 at 8 MB. 8 MB is the first size at which the result carries information. No functional change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
ENTROPY_FREE_BUDGET(64 KB/boot, added in2f9269f64) is too small for the health test it exists to enable — because the positive control cannot run at that size.A zero-collision result proves nothing on its own: a detector that never fires also returns zero. So the scan must also run at a width where collisions are expected and their count checked against theory. 32-bit collisions over N blocks expect
N²/2³³:8 MB is the first size at which the result carries information. Under the old cap, reaching it cost 128 manual replugs.
The cap was also asymmetric in the wrong direction: it never stopped a patient remote attacker — host malware simply waits for the replugs that happen anyway and accumulates 64 KB at a time — while it fully priced out the honest auditor. The original comment concedes the failure mode ("so nobody ever checked").
What
Gate the bulk path on a single explicit press rather than a byte count, and only before initialization:
Initialized devices are unchanged: 64 KB, then a confirm on every draw.
Hardware validation
Flashed to an uninitialized device. 8.00 MB in 345 s (23.7 KB/s, 8192 calls, mean 42 ms/chunk), with exactly one stall — chunk 65, 17.4 s, the press — at exactly the 64 KB boundary.
sha256(sample)
32178de71a1083bb79d9c1fa891b081066632d7403bd99bfdfb8f6dae9cb484cWhat this does and does not prove
Does: rules out stuck or badly biased output, repeated buffers, transport caching, gross correlation, and — via the 32-bit control — a collision detector that silently does nothing.
Does not: bound the entropy of the RNG's internal state. Specifically:
e⁻¹= 37%. Zero collisions excludes only ≤37.4 bits at 95% confidence (≤36.8 at 99%). The table reads "1 expected collision at k bits" for this reason.7.999976 bits/byteis empirical Shannon entropy of the sample, not cryptographic min-entropy. NIST SP 800-90B assesses the raw noise source and wants continuous RCT/APT health tests — separate future work, tracked below.Follow-ups (not in this PR)
SEIS/CECSseed-and-clock error bits give partial coverage today (reset_rng()on fault).ResetDeviceso wallet generation never continues the audited stream.Cost
One
static booland two branches. Device build green across all variants.