Games
How provably fair actually works — and how to verify a round yourself
Server seeds, client seeds, nonces and HMAC-SHA256, explained step by step. What provably fair guarantees, what it doesn't, and how to check a result independently.
- Author
- Gamicy Engineering
- Published
- Reading time
- 3 min
"Provably fair" is printed on almost every crypto casino game, but it is often explained vaguely. It is a precise, simple cryptographic protocol. Once you understand it, you can verify any round yourself in a few lines of code — and you can tell when a game's implementation doesn't actually let you.
The problem it solves
In an online game, the server generates the outcome. A player has no way to know whether the outcome was decided fairly before the bet, or chosen afterwards knowing what the player bet on. Provably fair solves exactly that: it proves the outcome was fixed before the bet and was not changed after.
The three ingredients
- Server seed — a random secret generated by the operator. Before any bet, the operator publishes its SHA-256 hash. This is the commitment.
- Client seed — a value the player controls and can change at any time. Because the server doesn't know it in advance, it can't pre-compute favourable outcomes.
- Nonce — a counter that increases with every bet under the same seed pair, so each round has a different input.
From seeds to an outcome
The common scheme combines the ingredients with HMAC-SHA256, using the server seed as the key and the client seed, nonce and a cursor as the message. The 32-byte result is turned into numbers between 0 and 1, four bytes at a time:
import { createHmac, createHash } from "node:crypto";
const serverSeed = "…revealed after rotation…";
const clientSeed = "my-own-seed";
const nonce = 42;
// 1. The commitment must match what was published before you played
const commitment = createHash("sha256").update(serverSeed).digest("hex");
// 2. Derive bytes for this round
const bytes = createHmac("sha256", serverSeed)
.update(`${clientSeed}:${nonce}:0`)
.digest();
// 3. Four bytes -> a float in [0, 1)
const float =
bytes[0] / 256 + bytes[1] / 256 ** 2 + bytes[2] / 256 ** 3 + bytes[3] / 256 ** 4;
// 4. Map the float to a game result, e.g. a dice roll from 0.00 to 100.00
const roll = Math.floor(float * 10001) / 100;Games that need more randomness than one float — a Plinko ball needs one decision per row, a Mines board needs several positions — take the next four bytes, and when the 32 bytes run out they increase the cursor and compute another HMAC block.
How to verify a round
- Before playing, note the hashed server seed the game shows you, and set your own client seed.
- Play. Each bet uses the next nonce.
- Rotate the seed pair. The game reveals the previous server seed and commits to a new one.
- Hash the revealed server seed with SHA-256 and check it equals the commitment you noted. If it doesn't, the seed was changed.
- Recompute each round with the server seed, your client seed and the nonce, and check the results match what you saw.
You can do this in our provably fair verifier, which runs entirely in your browser — the seeds never leave your device.
What provably fair does not prove
Provably fair proves that outcomes weren't manipulated per bet. It does not, on its own, prove that the game's math model is what the operator says it is. The mapping from float to outcome — and therefore the RTP — has to be published and checked separately. A game that reveals seeds but doesn't document its mapping is only partly verifiable.
It also depends on the details: the player must be able to set the client seed before the server seed is used, and the commitment must be shown before the first bet. If either is missing, the guarantee weakens considerably.
Provably fair mechanics are part of every original game we build. See the formats we work with on the games page.
Related services
- Original game development
Crash, dice, plinko, mines and custom titles — game logic, interface and backend, with provably fair outcomes.