Casino Royale

Transparent by design

Provably fair games

Every round of every game on Casino Royale - dice, money wheel, plinko, slots and roulette - can be independently verified. We commit to a secret server seed before any bet is placed by publishing its SHA-256 hash, combine it with a client seed and a nonce, and reveal the seed afterwards so you can recompute the result yourself.

One random stream for every game

All games draw their numbers from the same stream. Block 0 of the stream is HMAC-SHA256(key = server seed, message = client seed + ":" + nonce), and block k appends ":" + k to the message; a new block is only computed when the previous one is used up. Each 32-byte block is read as eight 4-byte big-endian unsigned integers (words), always in order. To draw a whole number below n, the game takes the next word: if it is below the largest multiple of n that fits in 232 (floor(2^32 / n) * n), the result is the word modulo n; otherwise the word is skipped and the next one is tried, so every result is exactly equally likely. The first draw of a round is the same value the original roulette derivation produced.

Each game makes its draws in a fixed order:

Payouts use whole numbers only: money is counted in the smallest currency unit, multipliers in hundredths, and every division rounds down. Each game version publishes its paytable as canonical JSON together with its SHA-256 hash, so you can confirm exactly which paytable settled your round.

How results are generated

  1. Before you bet, we generate a 256-bit server seed with a cryptographically secure RNG and show you its SHA-256 hash (the commitment).
  2. Every game draws from the same stream: block k is HMAC-SHA256(key = server seed, message = client seed + ":" + nonce) for the first block and the same message plus ":" + k for later ones. Each 32-byte block is read as eight 4-byte big-endian integers, used in order.
  3. To pick a number below n (a roulette pocket, a dice roll out of 10,000, a wheel segment, a reel stop, a plinko bounce), the next integer below the largest multiple of n is taken modulo n. Larger integers are rejected so every outcome is exactly equally likely, with no modulo bias.
  4. Roulette takes one number: the pocket 0-36; on American wheels index 37 is 00. Other games take as many numbers as they need, in a fixed order for each game.
  5. Solo games: your client seed is yours to change, and the nonce increases by one per round. Rotating reveals the old server seed.
  6. Multiplayer rooms: each round commits to a fresh server seed when betting opens. When betting closes, the round's client seed is derived from the client seeds of every player who bet (r<round>- + first 16 hex of SHA-256 of their seeds joined by | in user order), so no single party controls the outcome. The server seed and the list of contributing seeds (player and seed, in user order) are published as the wheel spins, so anyone can recompute both the client seed and the result.

Reference implementation (JavaScript)

class FairStream {
  constructor(serverSeed, clientSeed, nonce) {
    this.enc = new TextEncoder();
    this.message = `${clientSeed}:${nonce}`;
    this.key = crypto.subtle.importKey('raw', this.enc.encode(serverSeed),
      { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
    this.block = 0;   // next block to compute
    this.view = null; // current block
    this.pos = 32;    // byte offset of the next word
  }

  async next32() {
    if (this.pos >= 32) {
      const msg = this.message + (this.block > 0 ? `:${this.block}` : '');
      const mac = await crypto.subtle.sign('HMAC', await this.key, this.enc.encode(msg));
      this.view = new DataView(mac);
      this.block += 1;
      this.pos = 0;
    }
    const v = this.view.getUint32(this.pos); // big-endian
    this.pos += 4;
    return v;
  }

  // Uniform whole number in [0, n): skip words at or above the largest multiple of n.
  async int(n) {
    const limit = Math.floor(2 ** 32 / n) * n;
    for (;;) {
      const v = await this.next32();
      if (v < limit) return v % n;
    }
  }
}

// One stream per round; make the draws in the game's order.
const rng = new FairStream(serverSeed, clientSeed, nonce);
const roll = await rng.int(10000);                   // dice: 4217 means 42.17
// wheel:    const slot = await rng.int(layout.length);
// plinko:   for each row: path.push(await rng.int(2)); slot = number of 1s
// slots:    for each reel: stops.push(await rng.int(strip.length));
// roulette: const pocket = await rng.int(38);       // 37 = "00"; European uses 37

Signed-in players can verify any round they played, by its round number, on the fairness page. It runs this same calculation in your browser and checks the outcome and payout against the round's record.