When I built ZimBet — a real-time platform with six games (Aviator, Plinko, Mines, Dice, Coinflip and Wheel) — the most interesting part wasn't the animation. It was answering one question a player is right to ask: "how do I know this isn't rigged?" The answer is a technique called provably fair, and once you see it, it's surprisingly simple.
The trust problem
In any game of chance run by software, the operator generates the random outcome. That's a conflict of interest: nothing visibly stops them from generating a losing result for you whenever it suits them. "Trust us" isn't an answer. Provably fair replaces trust with maths — it lets the player verify, after every round, that the result was decided by a process fixed before they placed the bet.
The three ingredients
A provably-fair round is built from three values:
- Server seed — a long secret random string the server picks. It's kept hidden during play and revealed afterwards.
- Client seed — a value the player contributes (or can change). This is the crucial part: because the player influences the input, the server can't have pre-computed a result purely to its own advantage.
- Nonce — a counter that increments each round, so the same pair of seeds produces a different outcome every bet.
The commit-and-reveal trick
Here's the clever bit. Before the round, the server publishes a hash of its secret server seed — say a SHA-256 of it. A cryptographic hash is one-way: you can't work backwards from the hash to the seed. So the player sees a fingerprint of the seed without seeing the seed itself.
The hash is a promise. The server has committed to one specific seed and physically can't swap it later without the hash no longer matching.
When the round ends, the server reveals the actual server seed. The player (or anyone) can then do two checks:
- Hash the revealed seed and confirm it equals the hash shown before the round — proving the seed wasn't changed.
- Feed
server seed + client seed + nonceinto the same hashing function and confirm it produces the exact outcome they saw — proving the result was determined by those committed inputs, not picked to make them lose.
Turning a hash into a game result
The output of the hash is just a hex string. Each game maps that deterministically to its own outcome:
- Dice / Wheel: take bytes from the hash, convert to a number in a fixed range, and that's the roll.
- Aviator: the same number maps onto the curve that decides where the multiplier "crashes" — so the crash point was fixed the instant the round started, even though it feels live.
- Plinko: consecutive bits decide left/right at each peg row, producing the slot the ball lands in.
- Mines: the hash deterministically shuffles which tiles hide mines.
The key property is determinism: identical inputs always produce the identical result, which is exactly what makes the round verifiable.
Building it in ZimBet
ZimBet is a React + TypeScript app. Each game renders its animation on the Canvas API — the rising Aviator curve, the bouncing Plinko ball — while shared state stays consistent across players through Supabase Realtime channels, so a multiplayer round looks the same on everyone's screen. The provably-fair layer sits underneath all of it: the visuals are just a dramatic way of revealing a number that was already decided.
The honest caveat
Provably fair proves one thing only: the result wasn't tampered with. It does not change the odds. A house edge can be perfectly fair and still favour the house over time — fairness here means verifiable, not winnable. I think that distinction is worth stating plainly, because "provably fair" gets thrown around as if it means "you'll win", and it doesn't.
Why it's a great thing to build
Implementing provably fair is a compact lesson in applied cryptography: hashing, commitment schemes, deterministic randomness and verification, all in service of a feature a user can actually understand. You finish it knowing exactly why a hash being one-way matters in practice — which is a much better way to learn it than a textbook.