The front page is about putting entropy in. This page is about taking it out, and about the difference between the two, because a beacon everyone can read is a different tool than a random number generator.
Before an epoch ticks, nobody knows its bytes. After it ticks, everybody knows the same bytes.
Unpredictable in advance. Universal afterward. That pairing is the entire product. It is not secrecy — there is none here, every reader on earth gets identical output. Every good use on this page trades on shared unpredictability. Every bad use confuses it with private randomness.
/dev/urandom, and you want it locally.
One epoch is 32 bytes, 256 bits, one tick per second. That is the ceiling. More contributors raise the quality of each epoch, never the rate.
| endpoint | shape | use when |
|---|---|---|
| /entropy/chain | JSON, one epoch | you need a seed once |
| /entropy/stream | SSE, one event per tick | you need continuous freshness |
| /entropy/status | JSON, pool health | you need liveness before trusting a draw |
curl -sS https://blobrand.com/entropy/chain
{"chain":"d03011fd4673a8842f2b685089975948ea94ae532f6126caff640ce69bb191bd","epoch":11557085,"bytes":32}
The epoch field is the sequence number, not a timestamp. It is how you name a specific draw, how you deduplicate, and how a third party reproduces your result later. Treat it as the primary key of every beacon value.
Five requests in one second give five identical chain values. A consumer that ignores epoch and hashes on a timer will silently reuse one 256-bit value many times over, and every downstream draw inherits that collapse. Always advance on epoch change, never on wall clock.
import json, urllib.request
def fetch_epoch(timeout=15):
"""Return (epoch, 32 bytes) for the current beacon value."""
req = urllib.request.Request(
"https://blobrand.com/entropy/chain",
headers={"User-Agent": "blobrand-consumer/1.0"},
)
with urllib.request.urlopen(req, timeout=timeout) as r:
d = json.load(r)
return d["epoch"], bytes.fromhex(d["chain"])
def stream_epochs():
"""Yield (epoch, 32 bytes) once per tick. Never yields an epoch twice."""
req = urllib.request.Request(
"https://blobrand.com/entropy/stream",
headers={"User-Agent": "blobrand-consumer/1.0"},
)
last = None
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode("utf-8", "replace").strip()
if not line.startswith("data:"):
continue
try:
d = json.loads(line[5:])
epoch, raw = d["epoch"], bytes.fromhex(d["chain"])
except (ValueError, KeyError):
continue
if epoch == last or len(raw) != 32:
continue
last = epoch
yield epoch, raw
type Epoch struct {
Chain string `json:"chain"`
Epoch uint64 `json:"epoch"`
}
func FetchEpoch(ctx context.Context) (uint64, []byte, error) {
req, _ := http.NewRequestWithContext(ctx, "GET",
"https://blobrand.com/entropy/chain", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
var e Epoch
if err := json.NewDecoder(resp.Body).Decode(&e); err != nil {
return 0, nil, err
}
raw, err := hex.DecodeString(e.Chain)
if err != nil || len(raw) != 32 {
return 0, nil, fmt.Errorf("bad chain value")
}
return e.Epoch, raw, nil
}
const es = new EventSource("https://blobrand.com/entropy/stream");
let last = null;
es.onmessage = (ev) => {
const d = JSON.parse(ev.data);
if (d.epoch === last) return; // dedupe on epoch, always
last = d.epoch;
const raw = Uint8Array.from(
d.chain.match(/../g).map((b) => parseInt(b, 16))
);
onBeacon(d.epoch, raw);
};
One epoch feeds many decisions. If two of them hash the same 32 bytes the same way, they produce correlated results, and a party who can predict one predicts the other. Bind every use to a distinct label before hashing.
import hashlib
def derive(seed, label, counter=0):
"""Independent output per label. Never hash the raw seed directly."""
return hashlib.sha256(
seed + b"|" + label.encode() + b"|" + counter.to_bytes(8, "big")
).digest()
leader_bytes = derive(seed, "leader-election")
shuffle_bytes = derive(seed, "bracket-shuffle")
audit_bytes = derive(seed, "audit-sample-2026-Q3")
Independent processes that never talk to each other reach the same arbitrary decision, because they read the same beacon. No consensus round, no leader, no shared database, no clock skew argument. This is the use that has no local substitute — /dev/urandom cannot do it, because two machines would draw different values.
def elect(nodes, seed):
"""Every node runs this alone and gets the same answer."""
nodes = sorted(nodes) # canonical order, or you diverge
h = derive(seed, "leader-election")
return nodes[int.from_bytes(h[:8], "big") % len(nodes)]
epoch, seed = fetch_epoch()
print(epoch, elect(["alpha", "beta", "gamma", "delta"], seed))
Sorting first is not decoration. If one node enumerates its peers in a different order, the modulo lands elsewhere and agreement breaks. Canonicalise every input that feeds a shared derivation.
Good fits: tie-breaking between equal-cost plans, choosing which replica runs a singleton job this minute, agreeing on a shard split, picking a canary cohort across independently deployed services.
Announce the epoch before it exists. Run the draw after it publishes. Now every entrant recomputes your result and confirms you did not shop for a favourable seed.
def draw(entrants, seed, label, n):
"""Rank by hash, take the top n. Deterministic and checkable."""
ranked = sorted(entrants, key=lambda e: hashlib.sha256(
seed + b"|" + label.encode() + b"|" + e.encode()
).digest())
return ranked[:n]
# published in advance: "winners drawn from epoch 11600000"
winners = draw(entrants, seed_at_11600000, "raffle-2026-08", 3)
Publish three things and the draw is auditable forever: the entrant list, the epoch number, and the exact derivation code. Anyone can rerun it. The organiser never touches a random number generator they control.
Same shape covers sortition — citizen assemblies, review panels, rotating on-call from a volunteer pool — anywhere the losers need a reason to believe the process.
Classic problem: everyone must choose simultaneously, but someone has to go last. The beacon supplies a value nobody could have known during the commit window.
TARGET = 11600000 # phase 1, before TARGET ticks — publish only the commitment commitment = hashlib.sha256(my_choice + my_nonce).digest() # phase 2, after TARGET publishes — reveal, then combine epoch, beacon = fetch_epoch_at(TARGET) assert hashlib.sha256(my_choice + my_nonce).digest() == commitment outcome = derive(beacon + b"".join(sorted(all_revealed)), "combine")
Folding the beacon in removes the last-mover advantage: a participant who withholds their reveal until they have seen the others still cannot steer the result, because the beacon half was fixed independently of all of them.
Tournament brackets, speaking order, jury pools, exam question order, matchmaking rounds. A seeded Fisher–Yates gives a permutation any observer reproduces.
def beacon_shuffle(items, seed, label):
items = list(items)
for i in range(len(items) - 1, 0, -1):
h = derive(seed, label, i)
j = int.from_bytes(h[:8], "big") % (i + 1)
items[i], items[j] = items[j], items[i]
return items
The modulo introduces bias, bounded by roughly len(items) / 2^64. For brackets and playlists that is far below anything observable. If you are shuffling something where a challenger will do the statistics, use rejection sampling instead of modulo.
Choosing which records to inspect is exactly where an auditee wants influence. Beacon selection removes the argument, and it is the mechanism behind risk-limiting election audits.
def sample(record_ids, seed, label, k):
"""Which k of n get inspected. Selected after the population is frozen."""
return sorted(record_ids, key=lambda r: hashlib.sha256(
seed + b"|" + label.encode() + b"|" + str(r).encode()
).digest())[:k]
Order of operations carries the whole guarantee: publish the population hash first, then take the beacon epoch, then sample. Reversed, the auditee picks a population that suits the seed.
You cannot predict epoch N before it ticks. So a document containing epoch N's chain value provably did not exist beforehand. That is a lower bound on creation time, requiring no timestamp authority.
curl -sS https://blobrand.com/entropy/chain >> build-manifest.txt sha256sum artifact.tar.gz >> build-manifest.txt
Useful for build provenance, research pre-registration, sealed bids, and creative timestamping. Note the direction: it proves not earlier than, never not later than. For an upper bound you need to publish the document's hash somewhere append-only, or feed it back as a UDP contribution so it lands in the chain itself.
A shared daily seed gives every CI runner, every laptop, and every contributor the same randomised test order — different each day, identical across machines, and quotable in a defect report.
read -r EPOCH CHAIN < <(curl -sS https://blobrand.com/entropy/chain \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["epoch"], d["chain"])')
SEED=$(( 16#${CHAIN:0:8} % 2147483647 )) # keep it inside signed 32 bit
echo "test seed $SEED at epoch $EPOCH"
pytest --randomly-seed=$SEED
The win is that "fails on seed 2489123047" becomes a reproducible defect rather than a shrug. Pin the epoch in CI so a whole pipeline shares one seed instead of drawing a new one per job.
You can safely fold the beacon into local randomness, provided local randomness remains in the mix. This is the same discipline the capacitor applies to its own contributors: /dev/urandom is the floor, contributions only ever add.
class Mixer:
"""Never weaker than os.urandom, even if the beacon is stale or hostile."""
def __init__(self):
self._key = hashlib.sha256(os.urandom(32)).digest()
self._ctr = 0
def absorb(self, beacon):
self._key = hashlib.sha256(self._key + beacon + os.urandom(32)).digest()
def read(self, n):
out = bytearray()
while len(out) < n:
out += hashlib.sha256(self._key + self._ctr.to_bytes(8, "big")).digest()
self._ctr += 1
return bytes(out[:n])
Read that absorb carefully. A fresh os.urandom(32) enters on every call, so an attacker who controls the beacon entirely still faces 256 bits of local entropy they never saw. Drop that term and you have built a public random number generator, which is not a random number generator at all.
Be clear-eyed about the benefit: this is defence against a locally broken generator, not a meaningful upgrade to a working one. If os.urandom is healthy, the beacon adds no security. If it is silently broken — a cloned virtual machine image, an embedded board with no entropy source at boot — the beacon is what keeps two machines from generating identical keys.
Experiment assignment, cache shard rotation, and cohort selection all need every node to agree on the same split without asking a service which side a user falls on.
def bucket(user_id, seed, label, buckets=100):
h = hashlib.sha256(seed + b"|" + label.encode() + b"|" + user_id.encode())
return int.from_bytes(h.digest()[:8], "big") % buckets
in_treatment = bucket(uid, seed, "checkout-experiment") < 10 # 10% cohort
Holding the epoch fixed for an experiment's lifetime keeps assignment stable. Rotating the epoch reshuffles every user at once, which is occasionally what you want and usually a bug — decide deliberately which one you are doing.
Every reader on earth holds these bytes. A key derived from the beacon alone is a key you published. This includes anything reachable from a key: seeds for keypair generation, HMAC secrets, session tokens, password salts you treat as secret, recovery codes.
Nonces must be unique per key, and beacon values are shared by definition. Two services encrypting under the same key while reading the same epoch produce a nonce collision, and under AES-GCM or ChaCha20-Poly1305 nonce reuse loses confidentiality and leaks the authentication key. This failure is silent and total. Use the randomness your cryptography library already provides.
Jitter exists to decorrelate clients. A shared beacon does the exact opposite: every client reading the same epoch computes the same delay and retries in the same instant. You have replaced jitter with a synchronised stampede, and it gets worse as more clients adopt it. Jitter must come from a local generator, always.
CSRF tokens, password reset links, invite codes, cache-busting values with security meaning, guess-resistant identifiers. Beacon values are public the moment they exist and archived forever after.
blobrand publishes raw hashes. There is no signature, and no verifiable delay function. Two consequences follow, and neither should be discovered late.
There is a third property worth stating plainly, because it cuts against the usual pitch for a distributed beacon. Every epoch mixes the capacitor host's /dev/urandom as its floor. That is what makes the pool safe against bad contributors, and it also means output is not independent of that one host. blobrand is a shared public value with many contributors raising its quality. It is not a decentralised beacon in the threat-model sense, and no amount of contributors changes that.
Fits: coordination, reproducibility, transparency, liveness, low and medium stakes fairness where participants have no strong incentive to attack the operator, and any case where being publicly checkable beats being maximally hardened.
Does not fit: money on the outcome, adversarial participants with real resources, regulatory or legal weight. Those want a threshold-signed beacon such as drand, or the NIST randomness beacon, both of which sign every pulse so you can verify a value was really produced at a given round.
A verifiable draw is only verifiable while the value survives. The beacon publishes the current epoch and nothing else — there is no historical lookup endpoint, so you keep the record.
curl -sS -N https://blobrand.com/entropy/stream \
| sed -u -n 's/.*"chain":"\([0-9a-f]*\)".*"epoch":\([0-9]*\).*/\2 \1/p' \
| while read -r epoch chain; do
printf '%s %s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$epoch" "$chain"
done >> beacon.log
For a scheduled draw you do not need every epoch, only the one you named. Announce the target, wait for it, capture it, publish it alongside the result:
def wait_for(target_epoch):
"""Block until the named epoch publishes, then return its bytes."""
for epoch, raw in stream_epochs():
if epoch >= target_epoch:
if epoch > target_epoch:
raise RuntimeError("missed epoch %d, saw %d" % (target_epoch, epoch))
return raw
Treat overshoot as an error rather than rounding to the next value. Silently accepting a later epoch is precisely the freedom a draw organiser should not have, and refusing it costs one retry with a new announced target.
Publish the epoch number, the chain hex, the input set, and the derivation source. Those four items let a stranger reproduce your result years later, which is the only thing that makes any of this worth doing.