Entropy beacon · specification

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

Promises

What a pulse promises.

  1. 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.
  2. 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’s local_random without breaking that commitment.
  3. Chained. value is the SHA-256 of a canonical message that contains the previous pulse’s value. Rewriting any pulse changes every value after it.
  4. Signed. The operator signs the canonical message with an Ed25519 key whose public half is published (/entropy/v2/key). A verifier needs nothing else.
  5. Backed by checkable physics. frame.digest is the SHA-256 of the 393,216 float32 bytes the MHD solver wrote for that frame. frame.epoch_seed and frame.epoch_frame say 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).
  6. Honest about gaps. status is 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.

Cadence

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.

Canonical message

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).
Status bits

Gaps, said in-band.

bitvaluemeaning
01GAP: more than 10 s since the previous pulse was written
12FRAME_REPEAT: same frame.digest as the previous pulse
24NO_PROVENANCE: the solver did not report epoch_seed/epoch_frame
38NO_FRAME: no readable frame; frame.digest is all zeros
416UNCOMMITTED: local_random was not committed by the previous pulse (first pulse of format 2, or the commitment was lost)
532FORMAT_CHANGE: the previous pulse is format 1
Record

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

Verifying

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.

Verify it yourself

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.