How provably fair works in Dice: Golden Ticket

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

How to play Dice: Golden Ticket

Dice: Golden Ticket (DGT) plays like Cloudbet's classic Dice game, with one extra layer: a hidden bonus target that can pay out a bonus multiplier on top of your normal win.

Base game: choose Roll Over (2.00–99.99) or Roll Under (0.01–98.00), then place your bet. If the dice lands on your side of the target, you win at the payout your target implies (a tighter target = bigger multiplier).

The Golden Ticket bonus: each round, a second, hidden value — the Golden Ticket — is also set on the board, with its own bonus multiplier between 3x and 200x. If your dice roll lands within ±1.75 of the Golden Ticket's position, you win the bonus payout at that multiplier, instantly and automatically — no extra bet required, and it stacks with your regular win.

You don't aim for the Golden Ticket directly — it's not something you can target or influence, it's just always somewhere on the board each round, and landing near it is a bonus on top of your regular play.

Cloudbet Originals Dice: Golden Ticket

Cloudbet's own Originals DGT has a 99% RTP, a max dice multiplier of x6,730, and a max win of $1,000,000.

Dice: Golden Ticket (DGT) uses the same provably fair foundation as every Cloudbet Original — see How Provably Fair Works for the general mechanics (Server Seed, Client Seed, Nonce, SHAKE256, rejection sampling). Its dice-value sampling also works the same way as plain Dice — DGT adds the Golden Ticket value and multiplier on top. Below is how that sequence becomes your DGT result specifically.

Transforming random numbers into a game outcome

DGT needs to prove two values are determined by verifiable randomness: the dice's value and the Golden Ticket's value.

The process is simple: first, one whole number between 0 and 10000 (inclusive) is sampled to be the dice's eventual value. Then another whole number between 175 and 9825 (inclusive) is sampled to be the Golden Ticket's eventual value. Both numbers are divided by 100 so they each have two decimal places — ranging from 0 to 100 (dice) and 1.75 to 98.25 (Golden Ticket).

The Golden Ticket's range is deliberately kept to 1.75–98.25, not 0–100, so that wherever it lands, there's always a full 3.5-wide "golden zone" around it where the dice's value can land and win the bonus. If the range went all the way to the edges (0 or 100), a Golden Ticket landing near an edge would have a smaller golden zone — and worse odds — than one landing in the middle.

DGT also has a unique feature: the Golden Ticket's multiplier is itself generated by a provably fair process. In simple terms: a third random number between 0 and 1 is drawn, then converted into a multiplier between 3 and 200 using a truncated Pareto distribution.

Technical details

The random bytes generated by SHAKE256 are split into 4-byte unsigned integers, ranging from 0 to 2³²-1. Since the dice value has 10001 possible outcomes (0–10000 inclusive), rejection sampling uses P = 10001; the Golden Ticket value has 9651 possible outcomes (175–9825 inclusive), so its P = 9651. (See the provably fair page for why this step matters.)

Selecting the dice's and Golden Ticket's values: the dice's random number is taken modulo 10001 to get its value. The same is done for the Golden Ticket's random number using 9651, then the result is incremented by 175 (so it ranges 175–9825). Both are then divided by 100.

Selecting the Golden Ticket's multiplier (some familiarity with statistics helps here): a random 32-bit number is drawn and divided by its maximum possible value, producing a uniformly distributed number between 0 and 1. That value is transformed into a truncated Pareto distribution (lower bound 3, upper bound 200, alpha 1.343 — chosen so the multiplier's expected value is 9, which upholds DGT's 99% RTP target), then rounded to two decimal places.

Gameplay

Both the dice's and the Golden Ticket's values are generated server-side and hidden until the round ends — they can't be changed undetected once play begins. After you place your bet, your chosen threshold is compared against the dice's value, and the Golden Ticket's distance from the dice's value is checked against the 1.75 "golden zone" to see if you win the bonus.

Code for independent verification

Accurate to the DGT implementation on Cloudbet — enter your Server Seed, Client Seed, and Nonce to verify your own games. (To reveal a game's Server Seed on Cloudbet, you need to have already moved on 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 getGameResult(rng) {
  // Dice
  // Ranges from 0 to 100, with two decimal places
  const diceMinValue  = 0;
  const diceMaxValue  = 10000;
  let diceValue = sampleFromRange(diceMinValue, diceMaxValue, rng);
  diceValue = diceValue / 100;          // Now the value ranges from 0 to 100

  // Golden Ticket
  // Ranges from 1.75 to 98.25 due to fairness
  // (If it landed on 99, for example, then the chance of the dice landing near
  // it would be smaller than the guarantee of 3.5%)
  const gtMinValue  = 175;
  const gtMaxValue  = 9825;
  let goldenTicketValue = sampleFromRange(gtMinValue, gtMaxValue, rng);
  goldenTicketValue = goldenTicketValue / 100;

  // Golden Ticket Multiplier
  const multiplier = getRandomNumberTruncatedPareto(rng);  // Complicated maths, can be ignored ;)

  return { diceValue, goldenTicketValue, multiplier };
}
// Sample a number using the truncated Pareto distribution
function getRandomNumberTruncatedPareto(rng) {
  // Get a uniformly distributed number between 0 and 1
  let randomInt32 = rng.next().value;
  const fraction = randomInt32 / 0xFFFFFFFF;

  // Truncated Pareto distribution. The expected value is 9.00
  const m0 = 3;
  const m1 = 200;
  const alpha = 1.343;

  const r = Math.pow(m0 / m1, alpha);
  const inner = 1 - fraction * (1 - r);
  let randomNumber = m0 / Math.pow(inner, 1 / alpha);
  randomNumber = Math.round(randomNumber * 100) / 100;  // Round to 2 decimal places
  return randomNumber;
}
// 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 = 3 * 2; // We need 3 random numbers, however due to rejection sampling we may need more
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const { diceValue, goldenTicketValue, multiplier } = getGameResult(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);
  console.log('🎫 Golden ticket landed on:', goldenTicketValue);
  console.log('⭐ Multiplier:', multiplier);
})();

More provably fair game explainers:

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