How We Built Provably Fair Randomization for a Wheel Spinner
A technical walkthrough of the system behind Picksy Proof: server-side CSPRNG, SHA-256 hash chains, and public receipt verification.
The Problem We Were Solving
When we started building Picksy, the question was not "how do we make a wheel spinner" — dozens of those exist. The question was: "how do we make one where the operator cannot cheat, and can prove they did not, even after the fact?"
This is actually a solved problem in online gambling, where "provably fair" systems have existed since at least 2012. The concept is straightforward: commit to a server seed before the bet, reveal it after, let the player verify the outcome themselves. We adapted this pattern for a general-purpose randomizer.
Step 1: Move Randomization Server-Side
The most common mistake in wheel spinners is generating the outcome in the browser. This is convenient but fundamentally unverifiable — the user can inspect the JavaScript, set breakpoints, or in some cases predict outcomes if the PRNG seed is guessable.
Every Picksy spin triggers a POST request to the server. The server picks the winner using Node.js's built-in CSPRNG:
const { randomInt } = require('crypto');
// entries is the array of wheel segments at spin time
const winnerIndex = randomInt(0, entries.length);
const winner = entries[winnerIndex];
crypto.randomInt() uses the operating system's entropy source (typically /dev/urandom on Linux). It is suitable for cryptographic use per NIST SP 800-90A. The outcome is determined on the server before the response is sent — the animation in the browser is purely visual.
Step 2: Log the Full Draw Record
After selecting a winner, we persist a complete draw record to MongoDB:
{
wheelId: ObjectId,
winner: "Alice",
entries: ["Alice", "Bob", "Carol", ...],
entriesCount: 12,
selectedIndex: 3,
method: "crypto.randomInt",
timestamp: ISODate("2026-03-01T14:22:31.004Z"),
receiptHash: "a3f9...", // SHA-256 hash (see step 3)
previousReceiptHash: "8b1c..."
}
Storing the full entry list at spin time (not just the winner) is intentional. It lets a verifier independently confirm that the selected index corresponds to the correct entry, and that the pool was what was claimed.
Step 3: Build the Hash Chain
The receipt hash is the tamper-evidence mechanism. It is a SHA-256 digest of the current draw data concatenated with the previous draw's receipt hash:
const crypto = require('crypto');
function computeReceiptHash(drawData, previousHash) {
const input = JSON.stringify({
winner: drawData.winner,
selectedIndex: drawData.selectedIndex,
entriesCount: drawData.entriesCount,
timestamp: drawData.timestamp,
previousReceiptHash: previousHash,
});
return crypto.createHash('sha256').update(input).digest('hex');
}
Chaining hashes means you cannot alter any single historical record without breaking every subsequent hash. If draw #47 is modified, draw #48's previousReceiptHash no longer matches, draw #48's own hash changes, and so on through the entire chain. The break is detectable by anyone running the verification algorithm.
Step 4: Public Verification
The verification endpoint at /verify accepts a receipt hash, fetches the stored draw record, recomputes the hash from the stored data, and compares. If they match, the record is untampered. The full chain can also be verified by walking backwards through all previousReceiptHash pointers.
This endpoint requires no authentication. Anyone — the wheel owner, a participant, a skeptical audience member — can verify any receipt hash independently.
What This Does Not Guarantee
Provably fair verification confirms that the stored record matches the hash and that the chain is intact. It does not guarantee that the entry list was accurate before the spin (the operator could theoretically exclude someone from the list), or that the wheel was used in a fair process (e.g., spinning multiple times and only recording the desired outcome is theoretically possible but would create multiple records, all verifiable).
For most use cases — classroom drawings, live-stream giveaways, community raffles — these constraints are not practical attack vectors. The system provides meaningful, independently verifiable proof that the recorded outcome was genuinely random and has not been altered after the fact.
The Public Audit Trail
Wheel owners can share a public audit link that exposes the full draw history for a wheel without requiring the viewer to have a Picksy account. Each record in the audit trail shows the full draw data and the receipt hash. The page includes a "Verify Chain" button that walks all records and confirms every hash is consistent.