How provably fair works in Roulette

Here's how to play Roulette on Cloudbet, and how to check that every spin is provably fair.

How to play Roulette

Roulette is a classic casino game where you bet on where a spinning ball will land on a numbered wheel. You place one or more bets on the number table, then the wheel spins and the ball is dropped. Once it settles into a numbered pocket, that's the winning number — any bets that cover it pay out, and everything else loses.

Placing bets

You can bet on the outcome in a few different ways, and you're free to combine several bets in the same round. Inside bets (on specific numbers) pay more but are harder to hit; outside bets (on broader groups) pay less but hit more often:

  1. Straight-up / Single Number (35:1): bet on one exact number (0–36, or 00 in American Roulette).
  2. Split (17:1): two adjacent numbers.
  3. Street (11:1): three numbers in a row.
  4. Corner (8:1): four numbers meeting at a corner.
  5. First Four / Top Line (8:1 European / 6:1 American): covers 0 (and 00 in American), 1, 2, 3.
  6. Six Line (5:1): two adjacent rows, six numbers.
  7. Column (2:1): one of three vertical columns.
  8. Dozens (2:1): 1–12, 13–24, or 25–36.
  9. Red/Black, Odd/Even, High/Low (1:1 each).

European vs. American Roulette

Cloudbet offers both versions. European Roulette has a single zero (37 possible outcomes, 97.30% RTP); American Roulette adds a second "00" pocket (38 possible outcomes, 97% RTP), which slightly increases the house edge. Which version you're playing should be shown clearly before you place a bet. Both include an Autoplay mode for continuous spins with preset chip values and spin counts.

Once your bets are placed and the round resolves, any winning bets are paid out automatically according to their odds, and the table resets for the next round.

This page covers how Roulette turns a provably fair random number sequence into a winning number. For how that random sequence is generated in the first place — server seed, client seed, nonce, hashing, SHAKE256 — see Cloudbet's provable fairness explainer.

Turning random numbers into a Roulette result

For Roulette, the provably fair system has to prove the winning number is chosen by verifiable randomness, not picked after the fact.

The process is simple: in European Roulette, one whole number between 0 and 36 is sampled from the random number sequence — that's the winning number. In American Roulette, the sampled number can range from 0 to 37, with "37" mapped to the double zero, "00"; results 0 through 36 are taken as-is.

How the winning number is selected

The random bytes generated by SHAKE256 (see our provable fairness page) are split into 4-byte unsigned whole numbers, ranging from 0 to 2³²−1. Roulette's winning number is chosen from 37 possible outcomes (European) or 38 (American) — so in the steps below, P is 37 or 38.

To pick a winning number fairly from those P outcomes, we apply rejection sampling (see How provably fair works), then take the resulting number modulo P — the remainder becomes the winning number (for American Roulette, "37" is displayed as "00").

Once generated, the winning number is saved and hidden server-side — it can't be changed or altered undetected after the round starts. Once you've placed your bet, it's compared against this pre-determined number to settle the round.

Verify your own Roulette results

We've written a small Node.js program accurate to Cloudbet's Roulette implementation (both European and American). Enter your Server Seed, Client Seed, and Nonce to reproduce your result. (Reminder: to reveal a round's Server Seed on Cloudbet, you need to have already rotated to a new seed pair.)

Dependencies:

npm install js-sha3

The code:

// If you are running this yourself, then you can input your own data here
// You can then compare:
// 1) Cloudbet's provided Commitment to the Commitment outputted here. This proves that the Server Seed was not altered during gameplay.
// 2) Cloudbet's provided game outcome to the game outcome outputted here. This proves that your game's outcome was determined solely by the pre-game seeds and was not altered afterwards.
const Version = Object.freeze({ AMERICAN: "american", EUROPEAN: "european" }); // Here the possible versions are listed
let myServerSeed      = 'your_server_seed'; // ✍️YOUR_INPUT✍️: Enter your (unhashed) Server Seed here
let myClientSeed      = 'your_client_seed'; // ✍️YOUR_INPUT✍️: Enter your Client Seed here
let myNonce           = 1;                  // ✍️YOUR_INPUT✍️: Enter your Nonce here
let myRouletteVersion = Version.AMERICAN;   // ✍️YOUR_INPUT✍️: Enter your roulette version (either Version.AMERICAN or Version.EUROPEAN)
const usingCustomData = false;              // Change this to true if you are using your own data

// Load libraries
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');

// Step 1: Generate a Server Seed and Commitment
// And generate a Client Seed - either by player input or by the player's browser
function generateServerSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myServerSeed; }
  const commitment = sha3_256(seed);
  return { 'serverSeed': seed, 'commitment': commitment };
}
function getClientSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myClientSeed; }
  return seed;
}

// Step 2: Create a Round Signature with SHA3 using a Server Seed, Client Seed and Nonce
function createRoundSignature(serverSeed, clientSeed, nonce) {
  return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}

// Step 3: Create a SHAKE256 stream generator from a Round Signature.
// To simplify this example program, this function doesn't create a stream, but instead a sequence of custom length.
function* createRandomNumberSequence(numbersToGenerate, roundSignature) {
  const numOfBytes = 4 * numbersToGenerate;           // 4 bytes (32 bits) per random number we generate
  const byteStream = shake256.create(8 * numOfBytes); // shake takes bits as input
  byteStream.update(roundSignature);                  // Use the Round Signature to seed the generator
  const buf = Buffer.from(byteStream.digest());       // The Buffer class lets us package every 4 bytes into one 32-bit integer
  for (let i = 0; i < numbersToGenerate; i++) {
    yield buf.readUInt32BE(i * 4);                    // Read buf in 4-byte steps
  }
  throw new Error("No more numbers in the random number sequence! Fix: make 'numbersToGenerate' larger.");
}

// Step 4: Draw from the random number sequence to calculate the game result
function getGameResult(rouletteVersion, rng) {
  let numOfOutcomes = 0;
  if (rouletteVersion === Version.EUROPEAN) {
    numOfOutcomes = 37;
  } else if (rouletteVersion === Version.AMERICAN) {
    numOfOutcomes = 38;
  }

  let result = String(sample(numOfOutcomes, rng))

  // Convert the result '37' to '00'
  if (result === '37') {
    result = '00';
  }
  return result;
}
// Sample a number from P number of outcomes using Rejection Sampling
function sample(P, rng) {
  const maxAcceptable = Math.floor(0x100000000 / P) * P;

  // Rejection sampling
  let rand;
  do {
    rand = rng.next().value;
  } while (rand >= maxAcceptable);

  // Modulo operation
  const sampled_number = rand % P;

  return sampled_number;
}

// Example usage
(function main() {
  let nonce = 1;
  let rouletteVersion = Version.AMERICAN;
  if (usingCustomData) { nonce = myNonce; rouletteVersion = myRouletteVersion; }

  const { serverSeed, commitment } = generateServerSeed();
  const clientSeed = getClientSeed();

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 1 * 2; // We need 1 random number, but there is an improbable case we'll need two
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const value = getGameResult(rouletteVersion, rng);

  console.log('📝 Commitment (shown before the game):', commitment);
  console.log('🔒 Server Seed (revealed after the game):', serverSeed);
  console.log('🎲 Client Seed:', clientSeed);
  console.log('🔢 Nonce:', nonce);
  console.log(`🎯 Winning Number (in the ${rouletteVersion} version):`, value);
})();

More provably fair game explainers:

  1. Provably fair Mines
  2. Provably fair Pump
  3. Provably fair Plinko
  4. Provably fair Dice
  5. Provably fair Dice: Golden Ticket
  6. Provably fair Limbo