"Provably fair" gets used as a marketing badge. It's actually a specific technique, and it proves one specific thing. This article explains how it works, using the real code from our stake-game add-ons, shows how a player can check a round step by step, and is clear about what it doesn't prove.
Games where players stake money for a chance of a payout are gambling in most countries, whatever the fairness method. Running them needs the right licence where you and your players are, and that's your responsibility. Nothing here is legal advice: check with a lawyer. Our game add-ons are marked adults-only, which raises your site's minimum sign-up age to 18.
The idea: commit, then reveal
The problem provably fair solves is simple. When a game server picks a random outcome, how does the player know it wasn't changed after they placed their bet? The answer is a commitment:
- The server picks a secret random value (the server seed) and shows the player its SHA-256 hash, a fingerprint, before the round is played.
- The outcome is computed from the server seed and a value from the player's side (the client seed).
- When the round ends, the server reveals the server seed.
- The player hashes the revealed seed and checks it matches the fingerprint they saw earlier. Then they recompute the outcome themselves.
Because a different seed would produce a different fingerprint, the server can't swap the seed after showing the fingerprint without being caught.
How our games derive each result
Our stake-game add-ons for PrizeX are Tower, Crossing, Flip and Train. They're add-ons, sold separately; none is included in an edition. All four use the same building block. Here it is from Tower's engine:
// Uniform [0, 1) from the HMAC of client_seed:index: 52 bits over 2^52.
function tower_draw(string $serverSeed, string $clientSeed, string $index): float
{
$h = hash_hmac('sha256', $clientSeed . ':' . $index, $serverSeed);
return hexdec(substr($h, 0, 13)) / 4503599627370496.0;
}
Before any draw, the player's seed is joined with the round number (the nonce, which counts that player's rounds of that game), so $clientSeed above is really yourSeed:roundNumber. In words: take HMAC-SHA256 of the text yourSeed:roundNumber:index, keyed with the server seed. Take the first 13 hex characters, which is 52 bits, and divide by 252 to get a number between 0 and 1. Each game uses a different index for each random event. Tower uses floor:slot, Crossing the lane number, Flip the toss number, and Train the car number.
In Tower, every floor's traps are placed when the round starts, before the player's first pick, using a Fisher–Yates shuffle driven by those draws:
for ($f = 1; $f <= $floors; $f++) {
$order = range(0, $tiles - 1);
for ($i = $tiles - 1; $i > 0; $i--) {
$j = (int) floor(tower_draw($serverSeed, $clientSeed, $f . ':' . $i) * ($i + 1));
[$order[$i], $order[$j]] = [$order[$j], $order[$i]];
}
$traps[$f] = array_slice($order, 0, $trapCount); // first slots are the traps
}
For each player and game, the server makes the seed for the next round in advance (32 random bytes) and shows its fingerprint before that round starts. When a round starts, it takes that committed seed and immediately commits a fresh one for the round after. Your seed is made in your browser when you open the game. Once you set your own, it's saved there, and you can change it at any time in the fairness panel. Changing it never changes the server seed, because the server's seed was already fixed and shown before your seed arrived.
How a player verifies a round
Each game page has a "How every round is decided" panel. It shows the next round's fingerprint, a box for your seed, and, for the current round, its fingerprint, round number and, once it ends, the secret seed. To check a Tower round:
- Before you start, note the next round's fingerprint. Change your seed first if you like.
- Start the round. Its fingerprint must be the one you noted. The start request carries the fingerprint you were shown, and the server refuses to start a round with any other seed. If the committed seed had changed, you're shown the new fingerprint and asked to start again.
- After the round, copy the secret seed, your seed and the round number.
- Check the fingerprint: the SHA-256 of the secret seed, hashed as text, must equal the fingerprint you noted. If it doesn't, the seed was changed.
- Rebuild the tower: run the shuffle above with the secret seed,
yourSeed:roundNumberin place of the client seed, the difficulty's tile and trap counts, and its number of floors. The traps you get must match where the game put them.
Any language with HMAC-SHA256 can do this. The PHP above works once you fill in the seeds, the round number and the difficulty's numbers. We don't host a verifier, on purpose: checking with your own tool is the point.
What provably fair does not prove
This is the part most "provably fair" pages leave out.
- It only protects you if you check. Committing the server seed before your seed is known stops the server from picking a seed to suit your seed, but only a player who notes the fingerprint and recomputes the round actually confirms it.
- It needs the site's committed-seed store. Committed seeds live in a database table the games create on first use. If a site's database user isn't allowed to create it, the games fall back to the older scheme, where the server makes each round's seed after receiving yours, and log a warning. A round played that way shows "—" for its round number.
- It doesn't prove the game is a good bet. Every game has a house edge, set by its return-to-player (RTP) setting. Tower's default RTP is 96%, so on average it pays back 96% of stakes. Provably fair shows the edge is applied as published, not that it's small.
- It doesn't prove you'll be paid. Payouts and withdrawals are a separate trust question.
- It doesn't make the game legal. See the warning at the top.
- It doesn't apply to everything on the site. These stake games use seeds and fingerprints per bet. Lottery and raffle draws have their own scheme, Verified draws, described below.
If you're running these games
- Show the RTP and the payout table. Each game page shows the same multiplier table the server pays from. Leave it visible.
- Keep the fairness panel on. Point players to it in your help pages, with the verification steps.
- Check that committed seeds are in use. If rounds show "—" for their round number, your database user can't create the seeds table, and the games are using the older, weaker scheme.
- Get licensed first. Real-money stake games are gambling in most places. If you're not licensed, don't run them for money.
- Protect players. Keep the 18+ sign-up floor, use the game's minimum and maximum bets, and point players to help if they need it.
Common questions
What does provably fair mean?
The game server commits to a secret seed by showing its hash before you play, then reveals the seed afterwards. You can check the hash matches and recompute the result yourself, which proves the result wasn't changed after the round started.
How do I verify a provably fair game?
Note the next round's fingerprint (hash) before playing. After the round, hash the revealed server seed and check it matches. Then recompute the outcome from the server seed, your seed and the round number, using the game's published formula, such as HMAC-SHA256 of "yourSeed:roundNumber:index".
Does provably fair mean I can't lose?
No. It proves the outcome wasn't changed after the round began. It says nothing about the odds, and every game has a house edge set by its RTP.
Are lottery draws provably fair too?
They can be checked, with a different scheme. With Verified draws on (Plugins › Lottery; on for a new store), each draw is committed to a secret seed when it's made, and only the seed's fingerprint is shown. Its result comes from that seed, the list of tickets that could win and public drand randomness published after the draw closes, and anyone can recalculate it on the draw's Verify page. With Verified draws off, draws use a secure random generator at the draw time and can't be checked this way.