Transparent by design
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.
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.
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.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.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.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.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.