Agents keep needing a random number that another party can check: picking a reviewer, breaking a tie, sampling a test case, running a small raffle. Math.random() works until someone asks "prove you didn't reroll."
drand already fixes this. It's a public randomness beacon run by a group of independent organizations (the League of Entropy). Every 3 seconds its quicknet chain publishes a round: a BLS signature over the round number, plus randomness = SHA-256(signature). Anyone holding the chain's public key can check a round offline.
The catch is that most agents don't call drand directly. They call some HTTP API that relays it. Then the question is whether you trust the relay.
You shouldn't have to. Here's how to take a beacon from any relay and verify it yourself before you use it.
The relay
I'll use a small free relay I run, Proof Random API. It returns the latest quicknet round as flat JSON:
curl -sS 'https://proof-random-api.pn-26f.workers.dev/v1/random?nonce=my-request-1'
{ "round": 12345678, "randomness": "…64 hex…", "signature": "…96 hex…",
"nonce": "my-request-1", "verified": false, "x402": false }
Note "verified": false. The relay does not check the signature, and it says so. Verification happens on your side, against keys you pin yourself.
Pin the chain, not the response
Never read the public key from the thing you're verifying. Hard-code quicknet's chain hash and public key (both published by drand):
const CHAIN = '52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971';
const PUBKEY = '83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a';
Verify the same round independently
Install the official client: npm i drand-client. Fetch the relay's round, then fetch and BLS-verify that same round from drand's own HTTP endpoint, and compare:
import { HttpCachingChain, HttpChainClient, fetchBeacon } from 'drand-client';
export async function getVerifiedBeacon(nonce) {
const r = await fetch('https://proof-random-api.pn-26f.workers.dev/v1/random?nonce=' + encodeURIComponent(nonce));
const data = await r.json();
const opts = { disableBeaconVerification: false, noCache: false,
chainVerificationParams: { chainHash: CHAIN, publicKey: PUBKEY } };
const client = new HttpChainClient(new HttpCachingChain('https://drand.cloudflare.com/' + CHAIN, opts), opts);
const independent = await fetchBeacon(client, data.round); // checks BLS sig + SHA-256(sig)
if (independent.signature !== data.signature || independent.randomness !== data.randomness)
throw new Error('relay beacon does not match verified drand beacon');
return { ...data, clientVerified: true };
}
If the relay lies, tampers, or replays the wrong round, the comparison fails. The relay is now just a convenience, not a trust anchor.
Turn it into an unbiased integer
randomness % 6 is biased. Hash with your nonce and use rejection sampling:
import { createHash } from 'node:crypto';
export function sampleInteger(b, max) {
const bound = Math.floor(2 ** 32 / max) * max;
for (let i = 0; i < 1000; i++) {
const raw = createHash('sha256')
.update(`${CHAIN}:${b.round}:${b.randomness}:${b.nonce}:${i}`)
.digest().readUInt32BE(0);
if (raw < bound) return raw % max;
}
throw new Error('sampling exhausted');
}
const b = await getVerifiedBeacon(crypto.randomUUID());
console.log(sampleInteger(b, 6)); // a die roll anyone can recompute
Anyone with the round, nonce and this function gets the same number.
The honest limit
Verification proves the number came from drand. It does not prove you didn't shop for a round. If you call, see a 2, and call again, the latest-round endpoint won't stop you.
For a draw that has to be fair against you, all parties agree on a future round number and the nonce before that round is published, then everyone verifies that round. The free relay only serves the latest round today, so use it for sampling and tie-breaks, not for prizes.
Code
The full client (input checks and timeouts included) is verified-client.mjs in the repo: https://github.com/kepler-ops-maker/proof-random-api
The relay is a free prototype. There's no payment and no account, and it doesn't claim to be a VRF. If you'd rather skip the relay, point fetchBeacon at drand directly. The verification code stays the same.
Top comments (0)