blobrand.

consumers — reading the beacon

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.

the property the wire uses never do this trust model archiving

the one property that matters

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.

The test: would your use still work if a stranger read the same bytes at the same moment? If yes, the beacon fits. If no, you want /dev/urandom, and you want it locally.

the wire

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.

endpointshapeuse when
/entropy/chainJSON, one epochyou need a seed once
/entropy/streamSSE, one event per tickyou need continuous freshness
/entropy/statusJSON, pool healthyou need liveness before trusting a draw
SHELL
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.

polling faster than the tick returns the same bytes

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.

PYTHON — fetch one epoch
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"])
PYTHON — follow the stream, deduplicated
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
GO
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
}
JAVASCRIPT — browser, live
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);
};

domain separation

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.

PYTHON
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")

what it is actually good for

1. coordination-free agreement

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.

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

2. public draws anyone can verify

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.

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

3. commit–reveal without a trusted dealer

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.

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

4. verifiable shuffles

Tournament brackets, speaking order, jury pools, exam question order, matchmaking rounds. A seeded Fisher–Yates gives a permutation any observer reproduces.

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

5. audit sampling

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.

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

6. proof that something is not backdated

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.

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

7. reproducible test and fuzz seeds

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.

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

8. seeding a local CSPRNG

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.

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

9. spreading work without a coordinator

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.

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

never do this

key material

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 and initialisation vectors

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.

retry jitter and backoff

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.

anything that must stay unpredictable after the fact

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.

trust model — read before staking anything on this

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.

archiving, so a draw stays checkable

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.

SHELL — append every epoch to a local log
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:

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

see also

copied to clipboard