How provably fair works in Dice

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

How to play Dice

Dice is a fast-paced game where you bet on whether a randomly generated number will land above or below a target you choose. Pick a target number and choose whether you're betting the result will Roll Over or Roll Under it. The closer your target is to the edge of the range, the less likely you are to win — but the bigger your payout if you do. Once you place your bet, the result is revealed instantly.

Cloudbet Originals Dice

Cloudbet's own Originals Dice uses a target range of 0.00 to 100.00, a 99% RTP, a max multiplier of x9,800, and accepts bets from $0.01 to $10,000. Auto Bet is available to automate a number of rounds.

This page covers how Dice turns a verified random number into your roll. For how the underlying randomness itself is generated and secured — server seed, client seed, nonce, hashing — see our provably fair page. Playing Dice: Golden Ticket? It samples its dice value the same way described here, then adds a Golden Ticket value and multiplier on top.

Turning random numbers into a Dice result

Dice's result has to be provably tied to the same verifiable randomness as every other Cloudbet Original. Once a round's random number sequence is generated (via SHAKE256 — see the provable fairness process for that process), Dice uses it like this:

A whole number between 0 and 10,000 (inclusive) is sampled from the sequence, then divided by 100 — giving a value between 0 and 100 with two decimal places. That's your Dice result.

How the sampling works

Dice has 10,001 possible outcomes (0.00 to 100.00 in steps of 0.01), so for this game, P = 10,001.

To pick fairly among those 10,001 outcomes, Dice uses the same rejection sampling method described on our guide to provably fair — a random 32-bit number is generated, then rejected and re-sampled if it would bias the result, until a fair one is found. That number is then run through a modulo operation against 10,001 to land on a value from 0 to 10,000, which is divided by 100 for the final result.

Gameplay

The result is generated and locked in on the server before you place your bet — it can't be changed or altered undetected once the round starts. After you bet, your chosen threshold is compared against this pre-generated number to determine whether you predicted correctly.

Code for independent verification of Dice's provable fairness

This program is accurate to Cloudbet's Dice implementation. Enter your Server Seed, Client Seed, and Nonce to verify your own results. Note: to reveal a game's Server Seed on Cloudbet, you first need to rotate to a new Server Seed.

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.
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
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 getDiceResult(rng) {
  const diceMinValue = 0;
  const diceMaxValue = 10000;
  let number = sampleFromRange(diceMinValue, diceMaxValue, rng);
  number = number / 100; // So that the dice's value ranges from 0 to 100
  return number;
}
// Sample a number between 'min' to 'max' (inclusive) using Rejection Sampling
function sampleFromRange(min, max, rng) {
  const P = max - min + 1
  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 + min;
}

// Example usage
(function main() {
  let nonce = 1;
  if (usingCustomData) { nonce = myNonce; }

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 1 * 2; // We need 1 random number, however due to rejection sampling we may need more
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const diceValue = getDiceResult(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('🔁 Round Signature (can be verified after the game):', roundSignature);
  console.log('🎯 Dice landed on:', diceValue);
})();

More provably fair game explainers:

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