Where the random comes from
radbeeper 0.6.0 · counter F48824B8207F7E · 9 emissions · generated 2026-10-08 02:14
The moment a nucleus decays is not determined by anything, which makes a Geiger–Müller counter the textbook hardware entropy source. What is not textbook is how much randomness survives the journey down a serial cable, and that is what this page is for. Back to the monitor.
What the serial link gives you
Everything below follows from one line of the protocol. <HEARTBEAT1>> makes the counter send two bytes once per second, and those two bytes are a count: how many arrivals landed in that second. The device drives the clock, so there is no drift between its second and ours — but there is also nothing finer. One integer per second is the entire raw material.
| Link | 115200 baud — not the limit, and never has been |
| What arrives | 2 bytes per second, masked to 14 bits of count |
| Sampling resolution | 1 second, the finest the protocol offers |
| Samples measured here | 2,988 |
| Mean | 0.726 counts per second |
| Variance | 1.623 — Poisson requires this to equal the mean |
| Variance / mean | 2.24, so the arrivals are over-dispersed |
| Most common value | 0 counts, in 62.0% of seconds |
| Min-entropy, measured | 0.636 bits per second (SP 800-90B, 99% bound) |
| Min-entropy, Poisson model | 1.047 bits per second — 1.6× too generous |
| Seconds for 256 bits | 402 s measured, against 245 s the model would have claimed |
Why the spectrum contributes nothing
The monitor accumulates a power spectrum of the same per-second counts, and it would be easy to think the bits come partly from there. They do not, and cannot. The FFT is a deterministic function of the samples, and for any deterministic f, H∞(f(X)) ≤ H∞(X): a transform moves information about, it does not make any. Worse, these coefficients come from a mean-subtracted, Hann-tapered window, so neighbouring bins are correlated by construction — bits pulled from them would look beautiful and carry less than they appear to. The entropy is in the counts. The spectrum is a view of them, used as a health check: a peak means something periodic is contaminating the arrivals, and the emission is marked suspect.
The counts, against the model that was assumed
Empty seconds are 28% more common than Poisson allows, and the tail runs far past it — 5 counts in a second happens 14× more often than the model says it should. Both push in the same direction: an over-dispersed distribution is piled onto its mode, and the mode is exactly what min-entropy is about. That is why the measured figure is smaller than the modelled one, not larger.
How the bits accumulate
Every second recorded here, run as one pool. The measured line is flat at first because the confidence bound has nothing to say until there are about a dozen samples; it then straightens as the estimate settles, and where it crosses the dashed target a line is emitted. The modelled line reaches that height in roughly half the time, which is precisely the error this replaced — and why the pools listed below, collected while the model was believed, all stop short of it.
What the resolution costs
Bucketing arrivals into whole seconds throws most of the randomness away, and it is worth knowing how much. If the link reported the time of each arrival rather than a count per second, the gap between two arrivals would be exponential, and quantised to a millisecond it would carry about 10.4 bits per arrival — roughly 8 bits a second at this background rate, against the 0.64 actually available. The counter's protocol has no such message: <GETCPS>> and the heartbeat both answer with a count, never a timestamp. So this is not a limit of the physics or of the tube. It is the cost of the interface, and the honest thing is to measure what gets through it rather than to claim the ceiling.
Every line, and the counts behind it
Reproducible is not the same as predictable. Each emission is SHA-256 over a label, its sequence number, the second the pool opened, and the counts — all of which are here, so anyone can recompute a past line and see it was not invented. That says nothing whatever about the next one, which comes from decays that have not happened.
radbeeper random --check random-F48824B8207F7E.tsv
| seq | opened | s | counts/s | bits, measured | Poisson | flat |
|---|---|---|---|---|---|---|
| 2 | 2026-09-04T19:12:11 | 494 | 0.682 | 257 | 486 | no |
| 6892a83b 0a8c4b9b 901f239b 55b39584 7291a76b ebae9f3b 9dde258d fb60566a | ||||||
| 1 | 2026-09-04T19:04:29 | 459 | 0.680 | 256 | 450 | yes |
| db885956 cfba5ec5 52b6f63e 5e37332b e1f30b0f c973a10b bfe38bd8 c05d540e | ||||||
| 0 | 2026-09-04T18:56:48 | 459 | 0.686 | 256 | 454 | yes |
| 7d932e4f c8b9565a 2bf73d4f 5cf557bd f22139d2 538ebdbb bd5664be b3c4c017 | ||||||
| 0 | 2026-09-04T18:28:12 | 448 | 0.681 | 258 | 440 | yes |
| b17c9c60 d5deeb7b 4b102dfc bec14efc b523818b 18d3ada0 582bd912 e61ff856 | ||||||
| 0 | not kept | 247 | 0.725 | 124 | 258 | yes |
| 9f463527 c9c014d0 62ec9c08 a0d83eaa 8c3537eb 29cf82fc b3f8b28b 13078940 | ||||||
| 0 | not kept | 226 | 0.788 | 125 | 257 | yes |
| d0eb87e1 9836c8a0 e98db5f2 8be418c5 cac6a386 90a939a2 a7920ad3 02eca16a | ||||||
| 1 | not kept | 181 | 0.983 | 97 | 257 | yes |
| 22e887bb ad9792a0 7ce1f52f 2149600e ce1014b8 ec5e3b1d b3e6a2df 1fe32d11 | ||||||
| 0 | not kept | 265 | 0.675 | 131 | 258 | yes |
| 53dd0237 0744d1fe 8f14e44f 72df981d 0cdaba9b 13a46ae0 b5939dfa e28f290a | ||||||
| 0 | not kept | 209 | 0.885 | 110 | 267 | yes |
| 40cce661 7db2b52b 83527e1c a3722d22 52fba76f 5d1d3be7 6723d52d d853f8cc | ||||||
Rows marked not kept were written by a build that recorded time.monotonic() — seconds since this machine booted — where the wall clock belonged, so they dated themselves to 1969. The digest used the same number, so those lines still recompute; it was the label that was wrong, and it is fixed. Those rows also stopped collecting on the Poisson figure, which is why their measured column falls short of 256 — they are the evidence, not a warning.
Treat this as a good physical entropy source, not a certified one. It has not been through a statistical test battery, and 256 bits of measured min-entropy is a claim about the samples that were seen, not a proof about the output.
Where these photons were
A tube counts what arrives, and what arrives depends on where it is standing. Granite gives more than chalk, altitude gives more than sea level, and a cellar gives less than either — so a count rate without a place is a number nobody else can use. The place is recorded against the counter's serial number over time, because these things get carried about: a reading from last month resolves to where the counter was last month, not to where it is now.
No fix has been recorded for this counter. radbeeper site --name "the north window" --at 51.317,0.891 writes one.
The precision is the privacy control, and it is applied on the way in. A fix is rounded when it is written down, not when it is displayed, so the file cannot be made to give up a precision it was never told. Three decimal places — about 110 m — is the default and is as fine as a fixed monitoring station needs: enough to put it on a map and compare it with somebody else's, not enough to knock on. One place is a town, two a district, four a building, six a doorstep.
Every second, as it was counted
The table below is one row per emission, joined to the count log that was running at the time. Click a row to open the raw seconds behind it. Drag across the chart to narrow the range, drag its edges to resize, scroll to zoom.
The chain column is the point of it. Every emission carries link = H(previous link || this key), so the emissions form a hash chain: any one of them can still be recomputed from its own counts, and the chain additionally says that they were emitted in this order and that none has been taken out. Per-emission integrity cannot see a deletion. The chain can. It is tamper-evidence and not tamper-proofing — anybody who can rewrite the file can recompute the whole chain — but the realistic accident is a truncated copy or a log stitched back together wrongly, and it catches those.
The link is hashed alongside the key and never into it. Folding it in would change every hex line this program has ever produced and would make each one uncheckable by the reference implementation, which knows nothing about chains.
What is in the .bin, and how to check a key by hand
The raw material behind every line on this page is a .bin: each second as it was counted, one file per counter per month, written beside the count log by service and watch, appended to and never rewritten. Where the file itself is published it is in the table at the foot of this page. There is no header at the top of one. Every frame carries its own magic and its own lengths, so a file truncated by a full disk or cut in half by a crash still reads — a reader that loses its place scans forward for the next magic and carries on. A header would have made the first bad byte the last readable one.
One frame
RBF2 | four bytes of magic. RBF1 is the same layout without the trailing link, written before 0.5. A second magic rather than a version byte, so a reader meeting the wrong one sees a frame that is simply not there, instead of parsing a header it half understands and running off the end of it. |
|---|---|
| suspect | one byte: 1 when the spectrum was not flat while this frame was being collected. |
| seq | varint — this counter's emission number. |
| started | varint — whole seconds since the epoch, of the first sample. |
| n | varint — how many samples follow. |
| samples | n of them, and the ordinary second is one byte: one second after the one before it, and the byte is the count. Anything else — a gap, or a count above 0xFD — is 0xFE and then two varints, the gap in seconds and the count. |
| key | varint length, then that many bytes: the 32 raw bytes of the SHA-256, not the 64 characters of its hex. |
| link | the same again, for the chain. An RBF1 frame has none. |
A varint here is LEB128: seven bits of value a byte, lowest group first, the high bit set on every byte but the last.
The key, and the second it is dated to
The time is inside the key, not beside it. A key is one SHA-256 over a preimage carrying the label, the sequence number and the second the frame started — so a frame cannot be moved to another time, or renumbered, and still produce the key written in it:
"radbeeper/entropy/1" 0x00 <seq> 0x00 <started> 0x00 <counts>
seq and started are written out in decimal, as ASCII, and started is the same whole second the frame records above. counts is one hex character a second, the count in that second clamped at f: a second with sixteen counts in it is not doing the work here. The label is what stops a key ever being taken for anything else this program hashes.
The gaps are recorded but not hashed. Only the counts go into the digest, which is why a frame keeps both — the gaps are there so a reader can see where the record has holes.
To check one for yourself, take the frame out as JSON and do the hash by hand. Nothing in here needs this program to be trusted:
radbeeper frames export --serial <serial> --seq <n> --json > frame.json
python3 - <<'EOF'
import json, hashlib
f = json.load(open("frame.json"))[0]
counts = "".join("%x" % min(c, 15) for _, c in f["samples"])
pre = (b"radbeeper/entropy/1\0" + str(f["seq"]).encode()
+ b"\0" + str(f["started"]).encode() + b"\0" + counts.encode())
print(hashlib.sha256(pre).hexdigest() == f["key"])
EOF
And the link
The chain link is a second hash, under a second label, and it never touches the key:
"radbeeper/chain/1" || previous link || key
Both go in as their raw 32 bytes, not as hex text, and the first frame a counter ever wrote hangs from a genesis link of sixty-four zeros. radbeeper frames verify walks the whole of it from there.
Take the evidence with you
Nothing on this page is a summary you have to take on trust. These are the files it was built from.
| random-F48824B8207F7E.tsv | 3.9 KiB — the audit trail: every emission with the counts it came from — radbeeper random --check recomputes them |
|---|
No frames are embedded in this page: the viewer above is showing the summary rows only. The raw seconds are in the .bin files.