Release Notes
wj-diode-quantum 1.0.0 Beta 4 - 5 August 2026
The serving side can now keep its pool across a restart, held encrypted on disk. The collecting side sends each ANU source as soon as it has it rather than batching them, and the API gains raw output formats, a machine-readable specification and plain text help. The wire format is unchanged, so the two sides can be upgraded independently.
Added
An encrypted pool that survives a restart. Set server.pool_file and the pool is
written to disk under a passphrase taken from WJ_POOL_KEY, using AES-256-GCM with the
key stretched by PBKDF2-HMAC-SHA256. This partly reverses the Beta 3 decision below: the
numbers still never touch disk in the clear, but a container flap no longer costs the
entire stock, which on a link that carries nothing back was expensive. Leaving pool_file
unset keeps the previous behaviour exactly.
A value is never served twice, now including across a restart. The removal is recorded on disk before the value is written to the response, so a crash between the two can only lose the value, never hand it out again. Two files are used to make that affordable: a small one holding the served history and the newest values, rewritten on every request, and a large one holding the rest, rewritten only when values arrive and periodically. A pool file that will not decrypt stops the server rather than being discarded.
Three more ways to take a value. /api/random/hex returns 256 bare hex characters,
/api/random/base64 the same value base64 encoded, and /api/random/binary exactly 128
raw bytes with no encoding and no trailing newline. All four value endpoints carry the
collection timestamp, source, size and remaining pool depth in X-Quantum-* headers — the
only place the raw form can put them — and set Cache-Control: no-store. The text and
binary endpoints report a drained pool as plain text, so a client reading the body as
bytes is not handed a JSON error it would accept as a value.
GET /api/spec serves an OpenAPI 3.1 description of the whole API, and
GET /api/help serves the same guidance as the root page as plain text, for a
terminal.
server.hot_entries tunes how many of the newest values the small pool file carries.
Changed
Each ANU source is sent as soon as it has answered, rather than a cycle collecting from every due source and sending one combined transfer. The two endpoints differ by two orders of magnitude in how long they take — the legacy one takes a couple of minutes for a full request, the keyed one seconds — so batching left the fast source's values waiting on the slow one, and a failure anywhere in the cycle discarded both. A source that is down now costs only its own values.
/api/version includes a product label, along with the value size and an API version,
so a response read out of context identifies itself.
/api/status reports whether the pool is persistent, which is what decides whether an
empty pool after a restart is expected or a fault. stats prints it too.
setup recv asks whether to keep the pool, and generates a passphrase if so. It
prints the passphrase rather than writing it anywhere: a key stored beside the file it
opens protects very little, and where it belongs is the operator's decision.
Fixed
The legacy ANU endpoint timed out on every cycle at the default settings. anu.legacy.size
defaulted to 128, and that endpoint returns roughly two seconds per kilobyte — about four minutes —
against a default anu.timeout_seconds of 60. Measured against the live service, the default size
is now 64 and the timeout 180, which leaves a comfortable margin. A config that sets either
explicitly was never affected.
Notes
The pool file is protected where it travels without the key — backups, volume snapshots, a
disk taken out of a machine — not against something that can already read both the file
and the process environment. There is no key escrow and no recovery: losing WJ_POOL_KEY
loses the pool, and a diode carries nothing back to ask for the numbers again.
served_history now costs time as well as memory, since it is rewritten every time a
value is served. Measured at the defaults that is a 3.5 MB write and about 18 ms per
request, so a persistent pool tops out near 55 requests a second — well above what either
ANU endpoint can supply, but worth knowing if there is no rate limit in front. Lowering
served_history raises the ceiling and shortens the replay-protection window.
wj-diode-quantum 1.0.0 Beta 3 - 4 August 2026
Values are now 1024 bits, nothing is written to disk on either side, and the transport is a stream rather than a series of files. Both sides must be upgraded together — the wire format has changed completely and a diode cannot renegotiate.
Changed
- Values are 1024 bits, up from 128. Each is 256 lowercase hex characters. How bytes are fetched from ANU is unchanged; only the slicing is. A given request now yields an eighth as many values, each eight times larger.
- Nothing touches disk. The pool file, its journal, the outbox, the spool and the
ingestsubcommand are all gone, along with thedatabase,spool_dir,outbox_dirandspool_poll_secondssettings. Values exist only in pipes and in memory. A restart on the serving side starts with an empty pool, which refills from the far side's next cycle. - The transport is a stream.
diode.send_commandis fed values on its standard input andserver.receive_commandsupplies them on its standard output —diodetool stream-sendandstream-recvrather thansendandauto-recv.send_commandno longer takes a{}placeholder, andreceive_commandis now required. - The wire format is JSON Lines, one value per line. Batch framing and batch identifiers are gone. A damaged line costs exactly one value and parsing resumes at the next newline, rather than a damaged batch costing everything in it.
- Each ANU endpoint has its own schedule.
collect.interval_secondsis replaced byinterval_secondson each source, defaulting to eight hours for the keyed API — about 90 requests a month against a quota of roughly 100 — and one hour for the legacy one, which allows a request a minute. statsasks the running server over HTTP instead of reading a file, since there is no longer a file to read. It no longer works with the server stopped.- Config files contain no paths at all, so nothing is resolved relative to the config's directory any more.
Added
- A documentation page at
/, with curl examples, an endpoint reference and live pool figures. Self-contained: no JavaScript, no external assets, and readable in light or dark. - The number of bytes discarded per fetch is logged, so a
length/sizepair that wastes a noticeable fraction of a response is visible rather than silent. - A warning at startup when send retries can outlast the shortest collection interval.
wj-diode-quantum 1.0.0 Beta 2 - 4 August 2026
A fix for the keyed ANU endpoint, which never worked in the first beta.
Fixed
- The keyed API at
api.quantumnumbers.anu.edu.aucould not be used at all. It returnslengthas a JSON string and omitssizeentirely, while the legacy endpoint returns both as numbers.anuResponsedeclared them as ints, so every keyed response failed to decode before the data was reached and the source produced nothing butparse response: json: cannot unmarshal string into Go struct field. Neither field is used for anything — they only echo the request back — so neither is decoded now. Collections from the legacy endpoint were never affected.
Documentation
- Corrected the yield figures. The two endpoints read
sizedifferently despite being sent the sametype=hex16request: the keyed one counts 16-bit values, so a block is twicesizein bytes, while the legacy one counts bytes. Atlength: 1024that is 20,480 bytes (1,280 values) from the keyed API atsize: 10— twice what was previously documented — and 16,384 bytes (1,024 values) from the legacy API atsize: 16. Both measured against the live endpoints.
wj-diode-quantum 1.0.0 Beta - 2 August 2026
First release.
Collects quantum random numbers from the Australian National University, pushes them across a data diode, and serves them one at a time on the far side.
Collecting
- Draws from both ANU endpoints: the current keyed API at
api.quantumnumbers.anu.edu.auand the older unkeyed one atqrng.anu.edu.au. Either or both can be enabled; when both are on, both are used on every cycle and their values are merged into one batch, each tagged with its source. - Blocks returned by either API are concatenated and re-sliced into 128-bit values locally, so the differing per-block size limits do not matter. Leftover bytes are discarded rather than padded.
- Batches wait in an outbox until the send command succeeds, so an interrupted or failed transfer is retried rather than lost. After a configurable number of attempts a batch is moved aside so one bad file cannot block the queue.
Serving
- One 128-bit value per request over HTTP, with the time it was collected.
- The pool is a bounded LIFO: newest served first, oldest discarded once it reaches capacity.
- A value is never served twice. Each take is journalled and flushed to disk before the value reaches the client, so the guarantee survives a crash, not just a clean shutdown.
- Batches arriving over the diode are validated whole and rejected whole. A batch that arrives twice is recognised and adds nothing.
servecan run and supervise the receiving tool, so the protected machine needs one process.
Getting set up
setup sendandsetup recvask only what cannot be defaulted and write a working config, along with the companion files the diode tool needs. Passwords go to a0600file rather than a command line.sample-configprints a config showing every option.statsreports pool depth and age without writing to the database, so it is safe to run against a live server.
Notes
- The API has no authentication and binds to
127.0.0.1by default. - The legacy ANU endpoint is limited to one request per minute and is being retired by ANU.