Pulse format 2
What one pulse of the Ledatic entropy beacon contains, what it promises and what it does not, and how anyone can check it with public data only.
Status: draft, 2026-10-18. Supersedes the undocumented format 1
("version":1).
What a pulse promises.
- Unpredictable until published. Each pulse mixes 32
bytes of fresh operating-system randomness (
local_random) that nobody outside the operator has seen before the pulse appears. The physics frame alone is never the source of unpredictability. - Committed before it is known. The operator publishes
SHA-256(local_random)one pulse early (precommit). Once pulse N is out, the operator cannot change pulse N+1’slocal_randomwithout breaking that commitment. - Chained.
valueis the SHA-256 of a canonical message that contains the previous pulse’svalue. Rewriting any pulse changes every value after it. - Signed. The operator signs the canonical message with
an Ed25519 key whose public half is published
(
/entropy/v2/key). A verifier needs nothing else. - Backed by checkable physics.
frame.digestis the SHA-256 of the 393,216 float32 bytes the MHD solver wrote for that frame.frame.epoch_seedandframe.epoch_framesay exactly where in a deterministic epoch the frame sits, so anyone can replay the epoch from its seed and compare digests (tools/plasma/mhd_verify_epoch.rail). - Honest about gaps.
statusis a bit set; a pulse after an outage, a pulse that re-hashed the previous frame, or a pulse whose commitment was lost says so in-band.
What a pulse does not promise: that the operator cannot
see its own next value (local_random is generated by the
operator, as in NIST Beacon 2.0), or that the operator cannot withhold
a pulse. Mixing an external public beacon into external
(planned as format 2.1, drand) narrows the first; outside witnesses
narrow the second.
One pulse, on the even second.
One pulse every 2 s, aligned to even UTC seconds
(unix_ms % 2000 == 0 at the scheduled instant;
unix_ms records the actual write time). The solver
produces about 0.9 frames a second, so almost every pulse sees a new
frame.
Twelve lines, in this order.
UTF-8, \n-terminated lines, in this order, no trailing spaces:
ledatic-beacon/2
<pulse_id> decimal
<unix_ms> decimal, milliseconds since the Unix epoch
<prev_value_hex> 64 lowercase hex
<frame.digest> 64 lowercase hex, or 64 zeros when no frame was readable
<frame.run_frame> decimal (solver frame counter within its run)
<frame.epoch_seed> decimal, or -1 when the solver did not report it
<frame.epoch_frame> decimal frames since that seed's perturbation, or -1
<local_random_hex> 64 lowercase hex
<precommit_hex> 64 lowercase hex = SHA-256(next pulse's local_random)
<external> empty in format 2.0
<status> decimal bit set (below)
value_hex = SHA-256(canonical message), lowercase hex.signature = Ed25519(operator key, canonical message), base64 (standard, padded).key_id= first 16 hex of SHA-256 of the public key’s DER SubjectPublicKeyInfo (the same fingerprint rule the fleet0 witness uses).
Gaps, said in-band.
| bit | value | meaning |
|---|---|---|
| 0 | 1 | GAP: more than 10 s since the previous pulse was written |
| 1 | 2 | FRAME_REPEAT: same frame.digest as the previous pulse |
| 2 | 4 | NO_PROVENANCE: the solver did not report epoch_seed/epoch_frame |
| 3 | 8 | NO_FRAME: no readable frame; frame.digest is all zeros |
| 4 | 16 | UNCOMMITTED: local_random was not committed by the previous pulse (first pulse of format 2, or the commitment was lost) |
| 5 | 32 | FORMAT_CHANGE: the previous pulse is format 1 |
JSON, one per pulse.
{"version":2,"pulse_id":9100000,"timestamp_utc":"2026-10-19T03:00:00Z","unix_timestamp":1792378800,
"unix_ms":1792378800004,"period_ms":2000,
"value_hex":"…","prev_value_hex":"…",
"frame":{"digest":"…","run_frame":4127,"epoch_seed":471529,"epoch_frame":88},
"local_random_hex":"…","precommit_hex":"…","external":"","status":0,
"signature":"…","key_id":"…",
"source":{"type":"2D_MHD_OrszagTang","grid":"128x128","node":"mini"},
"spec":"https://ledatic.org/entropy/spec"}
pulse_id, timestamp_utc, unix_timestamp,
value_hex and prev_value_hex keep their format 1
names and meanings, so the chain continues across the format change: the
first format 2 pulse’s prev_value_hex is the last
format 1 pulse’s value_hex.
Where the records are: /entropy/pulse (the latest),
/entropy/pulse/log (the newest 50, oldest first),
/entropy/pulse/<id> (one pulse by id) and
/entropy/v2/key (the operator key, PEM).
Four checks per pulse.
For each pulse: rebuild the canonical message from the record, check
SHA-256 equals value_hex, check the Ed25519
signature, check prev_value_hex equals the previous
record’s value_hex, and check
SHA-256(local_random) equals the previous record’s
precommit_hex unless status bit 4 is set. Reference
verifier: verify_v2.py.
Replaying physics: run the epoch from epoch_seed for
epoch_frame + 1 frames and compare the float32 planes’
SHA-256 to frame.digest.
Public data, one script.
verify_v2.py runs the
four checks above. It needs only stock Python 3.8 or newer and its
standard library: it uses the cryptography package for
signatures when one is installed and falls back to the RFC 8032
reference arithmetic when not. It prints the key fingerprint it used
(compare it with 5780ca472c3db50d), one line of counts,
and VERDICT=PASS, exiting 0 only when every check held.
curl -sO https://ledatic.org/entropy/verify_v2.py
python3 verify_v2.py --url https://ledatic.org # the newest 50
python3 verify_v2.py --url https://ledatic.org --from <pulse_id> --count 500 # a range, by id
Or keep your own copy of the data and check it offline:
curl -s https://ledatic.org/entropy/v2/key > beacon_v2.pub.pem
curl -s https://ledatic.org/entropy/pulse/log > pulses.json
python3 verify_v2.py --file pulses.json --key beacon_v2.pub.pem
To replay a frame, take a pulse whose status does not carry NO_PROVENANCE (bit 2, value 4), and run the Rail replay tool from a clone of the public Rail repository:
./rail_native run tools/plasma/mhd_verify_epoch.rail <epoch_seed> <epoch_frame + 1> - planes.f32
shasum -a 256 planes.f32 # or sha256sum; equals the pulse's frame.digest
The same checks run in your browser on /entropy, against the newest 50 pulses.