How raffld draws
The whole algorithm fits on one page so anyone can re-run it with a hash tool and a few lines of code.
- 1.
Normalize
Split the pasted text on line breaks, trim each line, drop blank lines, and remove exact duplicates. The order of the remaining lines is the entrant order.
- 2.
Commit
listHash = SHA-256(entrants joined by "\n"). This hex string is shown before the draw. Publishing it first means the list cannot be changed afterwards without the hash changing.
- 3.
Timestamp
drawnAt is the ISO-8601 UTC time with milliseconds at the moment the draw runs, for example 2026-09-16T14:03:22.418Z. The server refuses records more than 10 minutes from its own clock.
- 4.
Seed
seed = SHA-256(listHash + "|" + drawnAt + "|" + winnerCount). Nobody chooses the seed; it falls out of the committed list, the time, and the number of winners.
- 5.
Pick
For i from 0 to winnerCount - 1: r = the first 8 bytes of SHA-256(seed + "|" + i) read as an unsigned big-endian integer; j = i + (r mod (entrantCount - i)); swap positions i and j. This is a partial Fisher-Yates shuffle. Positions 0 to winnerCount - 1 are the winners, reported as their original indices.
- 6.
Record
The browser sends listHash, drawnAt, seed, entrantCount, winnerCount and the winner positions to the server. The server recomputes the seed and the winners from those inputs and rejects the record if anything differs. Entrant names are never sent.
Reference implementation
const hex = async (s) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s)))]
.map((b) => b.toString(16).padStart(2, "0")).join("");
async function draw(entrants, n, drawnAt) {
const listHash = await hex(entrants.join("\n"));
const seed = await hex(`${listHash}|${drawnAt}|${n}`);
const idx = entrants.map((_, i) => i);
for (let i = 0; i < n; i++) {
const r = BigInt("0x" + (await hex(`${seed}|${i}`)).slice(0, 16));
const j = i + Number(r % BigInt(entrants.length - i));
[idx[i], idx[j]] = [idx[j], idx[i]];
}
return { listHash, seed, winners: idx.slice(0, n) };
}What this does and does not guarantee
The draw is deterministic and reproducible: given the same list, timestamp and winner count, everyone gets the same winners. The list commitment stops the organizer from editing entrants after publishing the hash.
The organizer still controls when to press the button, and the timestamp changes the outcome. A determined organizer could run many draws privately and only publish the one they like. If that matters for your draw, announce the exact minute you will draw in advance, or run it live in front of participants.
The modulo step has a bias of at most 1 in 2^64 divided by the entrant count, which is far below anything measurable.
Privacy and permitted use
Entrant names are hashed and drawn in your browser and are never sent to raffld. The record stores only the hash, the timestamp, the seed and the winner positions, so verification depends on the organizer sharing the original list.
raffld is for giveaways, classroom picks, team assignments, and free-entry prize draws. It must not be used for lotteries, raffles with paid entry, or any promotion that requires a gambling license where you operate.