How verifiable draws work
Every Giveaway Picker draw publishes enough data for anyone to recompute the winners. This page is the full specification of the randnext-draw-v1 algorithm, and an honest account of what it does and doesn’t prove.
The short version
- The organizer locks the entry list. RandNext publishes the list’s fingerprint (
entries_hash) and a commitment: the SHA-256 hash of a secret random seed. - The organizer shares the record link before the draw time, so participants can save the fingerprint and commitment.
- At the draw time, RandNext reveals the seed. Anyone can check that it matches the commitment and recompute the winners from the seed and the list.
Because the seed was committed to before anyone could see the result, and the list was locked before the seed was revealed, neither can be swapped afterwards without the record failing to verify. Check one yourself in the verifier, or with the Python reference script.
1. Normalizing the entry list
The list is normalized the same way in the browser, on the server and in every verifier, so the same text always produces the same fingerprint.
- Split on
\n(U+000A) only. A\rleft over from\r\nis removed in the next step; a lone\r, U+2028 and other line separators do not split lines. - Apply Unicode NFC normalization to each line, then trim leading and trailing whitespace. The whitespace set is fixed (it matches JavaScript’s
String.prototype.trim) rather than taken from a language’s own definition: U+0009–U+000D, U+0020, U+00A0, U+1680, U+2000–U+200A, U+2028, U+2029, U+202F, U+205F, U+3000, U+FEFF. - Drop empty lines.
- If the draw uses
duplicates = merge, keep only the first occurrence of each line. Withkeep, repeated lines stay, and each copy is a separate chance to win. - Keep the original order. The list is never sorted.
entries_bytes = UTF-8(lines.join("\n")), with no trailing newline.
Limits: 2 to 10,000 entries of up to 200 characters, and up to 1,000 winners.
2. Seed and commitment
VERSION = "randnext-draw-v1" (ASCII) entries_hash = SHA256(entries_bytes) 32 bytes server_seed = 32 bytes from a CSPRNG sealed until the draw time commitment = SHA256(server_seed) published when the list is locked params = "id=" + draw_id + "\nwinners=" + k + "\nentries=" + n (ASCII) final_seed = SHA256( UTF8(VERSION) || 0x00 || server_seed || entries_hash || UTF8(params) )
|| is byte concatenation; server_seed and entries_hash are the raw 32 bytes, shown as lowercase hex in records. The draw ID, the number of winners k and the number of entries n are mixed in so a seed can’t be reused for another draw, and the winner count can’t be changed after the fact.
3. Random stream and picking winners
block(i) = HMAC-SHA256(key = final_seed, msg = UTF8(VERSION) || uint32_be(i)), i = 0, 1, 2, … stream = block(0) || block(1) || … read 4 bytes at a time as big-endian uint32 uniform(m): # an integer in [0, m), m ≤ 2^32 limit = floor(2^32 / m) * m repeat u = next_uint32() until u < limit return u mod m a = [0, 1, …, n-1] # partial Fisher–Yates for i in 0 … k-1: j = i + uniform(n - i) swap(a[i], a[j]) winners = a[0 … k-1] # a[0] is 1st place
Rejection sampling keeps every position equally likely. The output is a list of positions in the normalized list; the winning entries are the lines at those positions, in rank order.
Verify a draw yourself
Three independent ways, all following the steps above:
- The browser verifier: enter a draw ID, or paste a record and list. Nothing is sent anywhere.
- Download verify-draw.py, a standard-library-only Python 3 reference implementation written from this specification, separately from the site’s code.
- Your own implementation, from the record at
GET /api/v1/draws/{id}and the list at/api/v1/draws/{id}/entries(see the API docs).
python3 verify-draw.py --fetch dr_7kq2m9x4tb1c python3 verify-draw.py --record record.json --entries entries.txt
The script exits with status 0 only when the fingerprint, the commitment, the final seed and the winners all match.
What you still have to trust
A verifiable draw proves that the published winners follow from a list and a seed that were both fixed before the result was known. It does not prove everything. For v1 records there is one trust assumption: RandNext generates the seed, so you are trusting that RandNext did not pick a seed that favors someone when the list was locked. The commitment only guarantees that the result can’t change after locking. v2 records remove that assumption with a public randomness beacon (below).
| Risk | How the draw guards against it | What’s left |
|---|---|---|
| The organizer edits the list after seeing the result | The result depends on entries_hash, which is published when the list is locked. A locked list can’t be changed or deleted. | None. |
| The organizer changes the number of winners afterwards | The winner count is part of params, which goes into final_seed. | None. |
| The organizer deletes a draw they don’t like and draws again | Locked draws can’t be deleted. Cancelling is only possible before the draw time and stays public. Records drawn soon after locking are marked “Not pre-announced”. | An organizer can create several draws and only share the one they like. Only you can close this gap: get the record link before the draw time. |
| The organizer adds entries until the result suits them | The seed is revealed only after the list is locked, so the organizer can’t know the result when locking. | In v1, an organizer working together with RandNext could do this. v2 draws don’t allow it: the beacon round isn’t known to anyone at lock time. |
| RandNext picks a convenient seed at lock time | v1: only RandNext’s published commitment and your trust. v2: the result also depends on a drand beacon round nobody knows at lock time. | Yes for v1 records. None for v2 records. |
| RandNext rewrites a record after the draw | Anyone who saved the commitment or the entries fingerprint would notice. v2 records are also signed by RandNext when locked and when drawn, so a saved copy proves what was published. | It relies on people keeping their own copies. A public daily log of all commitments is planned. |
If you’re a participant: ask for the record link before the draw time and save the commitment and fingerprint. A link you only see after the draw can’t tell you whether the organizer made other draws first.
randnext-draw-v2: a public randomness beacon
Every draw created since October 2026 uses v2. It mixes in a value from the drand quicknet beacon run by the League of Entropy. When a draw is locked it fixes a beacon round R that hasn’t been produced yet — the first round at least 3 seconds after locking and no earlier than the chosen draw time — and the draw time is moved onto that round.
VERSION = "randnext-draw-v2"
params = "id=" + id + "\nwinners=" + k + "\nentries=" + n + "\nbeacon_round=" + R
final_seed = SHA256( UTF8(VERSION) || 0x00 || server_seed || entries_hash
|| beacon_randomness(R) || UTF8(params) )
block(i) = HMAC-SHA256(final_seed, UTF8(VERSION) || uint32_be(i)) (rest as in v1)quicknet: chain hash 52db9ba7…e971, a round every 3 seconds since 1692803367, round r published at 1692803367 + (r−1)·3. The beacon’s randomness is SHA-256 of its 48-byte BLS signature over SHA-256(uint64_be(r)), checkable against the League of Entropy public key. Anyone can fetch round R from api.drand.sh and compare.
Nobody — RandNext included — can know round R when the list is locked, so the result is unknown to everyone until the draw time. The algorithm field on each record says which version it uses; v1 records stay verifiable with the v1 rules on this page.
Signed statements
RandNext also signs two statements with an Ed25519 key: one when the draw is locked (fingerprint, commitment, beacon round, times) and one when it is drawn (seed, beacon value, final seed, winners). Keep a copy of a record and its signatures and you can later prove what RandNext published, even if the record changed. The public key is at /api/v1/draws/signing-keys; the verifier checks both signatures and the beacon’s BLS signature.
FAQ
Can RandNext see or change the result before the draw time?
The result is fixed the moment the list is locked: it follows from the sealed seed and the locked list, and nothing can change either afterwards. The seed is stored encrypted and is revealed only at the draw time. RandNext does generate the seed: for v1 records you trust that it did not choose one on purpose; v2 records also depend on a public beacon value nobody knows at lock time.
What happens if nobody opens the page at the draw time?
The draw is revealed on the first visit after the draw time, and a scheduled job reveals any draw nobody opened. The draw time only decides when the seed is published; the result was fixed at lock time either way.
Why does a record say “Not pre-announced”?
The draw happened less than 10 minutes after the list was locked. That leaves little time for the organizer to share the record first, so you can’t rule out that they ran several draws and kept one. Ask whether the link was shared before the draw.
I’m on a hidden list. How do I check that I was included?
Hidden lists show only their size and fingerprint. Ask the organizer for the list, then paste it into the verifier: if the fingerprint matches, that is exactly the list that was drawn from.
What happens to a draw that is removed for breaking the Terms?
Its title, description, entries and winner names are hidden, but the fingerprint, commitment, seed and winning positions stay public, so the record can still be checked.
Ready to run one? Create a verifiable draw with the Giveaway Picker — free, no account.