Cómo funciona provably fair en Dice: Golden Ticket

Así se juega a Dice: Golden Ticket en Cloudbet, y así puedes comprobar que cada tirada y sorteo de Golden Ticket son provably fair.

Cómo jugar a Dice: Golden Ticket

Dice: Golden Ticket (DGT) se juega como el clásico Dice de Cloudbet, con una capa adicional: un objetivo bonus oculto que puede pagar un multiplicador extra además de tu premio normal.

Juego base: elige Roll Over (2,00–99,99) o Roll Under (0,01–98,00) y realiza tu apuesta. Si el dado cae de tu lado del objetivo, ganas según el pago que implica ese objetivo (un objetivo más ajustado equivale a un multiplicador mayor).

El bonus de Golden Ticket: en cada ronda también se fija en el tablero un segundo valor oculto —el Golden Ticket—, con su propio multiplicador bonus de entre 3x y 200x. Si tu tirada cae dentro de ±1,75 de la posición del Golden Ticket, ganas el pago bonus a ese multiplicador, de forma instantánea y automática, sin apuesta adicional, y se suma a tu premio normal.

No apuntas directamente al Golden Ticket —no es algo que puedas elegir como objetivo o influir—; simplemente siempre está en algún lugar del tablero en cada ronda, y caer cerca de él es un bonus adicional sobre tu jugada normal.

Dice: Golden Ticket de Cloudbet Originals

El DGT propio de Cloudbet Originals tiene un RTP del 99 %, un multiplicador máximo de dado de x6730 y un premio máximo de 1 000 000 $.

Dice: Golden Ticket (DGT) utiliza la misma base provably fair que el resto de Cloudbet Originals; consulta Cómo funciona Provably Fair para conocer la mecánica general (Semilla del servidor, Semilla del cliente, Nonce, SHAKE256, muestreo por rechazo). El muestreo del valor del dado también funciona igual que en Dice normal: DGT añade además el valor y el multiplicador del Golden Ticket. A continuación se explica cómo esa secuencia se convierte específicamente en tu resultado de DGT.

Transformar números aleatorios en un resultado de juego

DGT tiene que demostrar que dos valores están determinados por aleatoriedad verificable: el valor del dado y el valor del Golden Ticket.

El proceso es sencillo: primero se muestrea un número entero entre 0 y 10 000 (ambos incluidos) que será el valor final del dado. Después se muestrea otro número entero entre 175 y 9825 (ambos incluidos) que será el valor final del Golden Ticket. Ambos números se dividen entre 100 para que tengan dos decimales, quedando en un rango de 0 a 100 (dado) y de 1,75 a 98,25 (Golden Ticket).

El rango del Golden Ticket se mantiene deliberadamente en 1,75–98,25, y no en 0–100, para que, caiga donde caiga, siempre haya una «zona dorada» completa de 3,5 de ancho a su alrededor donde el valor del dado pueda caer y ganar el bonus. Si el rango llegara hasta los extremos (0 o 100), un Golden Ticket cerca de un extremo tendría una zona dorada más pequeña —y peores probabilidades— que uno situado en el centro.

DGT tiene también una característica única: el multiplicador del Golden Ticket se genera a su vez mediante un proceso provably fair. En términos simples: se extrae un tercer número aleatorio entre 0 y 1, que luego se convierte en un multiplicador de entre 3 y 200 mediante una distribución de Pareto truncada.

Diagrama que muestra cómo el sistema de equidad demostrable de Cloudbet genera tres valores enlazados en Dice: Golden Ticket: el resultado del dado, la posición del Golden Ticket y su multiplicador de bonificación.

Detalles técnicos

Los bytes aleatorios generados por SHAKE256 se dividen en enteros sin signo de 4 bytes, en un rango de 0 a 2³²-1. Como el valor del dado tiene 10 001 resultados posibles (0–10 000 incluidos), el muestreo por rechazo usa P = 10 001; el valor del Golden Ticket tiene 9651 resultados posibles (175–9825 incluidos), así que su P = 9651. (Consulta la página de provably fair para saber por qué importa este paso.)

Selección de los valores del dado y del Golden Ticket: el número aleatorio del dado se toma módulo 10 001 para obtener su valor. Se hace lo mismo con el número aleatorio del Golden Ticket usando 9651, y después el resultado se incrementa en 175 (para que quede entre 175 y 9825). Ambos se dividen después entre 100.

Selección del multiplicador del Golden Ticket (ayuda tener algunas nociones de estadística): se extrae un número aleatorio de 32 bits y se divide entre su valor máximo posible, lo que produce un número distribuido uniformemente entre 0 y 1. Ese valor se transforma mediante una distribución de Pareto truncada (límite inferior 3, límite superior 200, alfa 1,343 —elegido para que el valor esperado del multiplicador sea 9, lo que sostiene el objetivo de RTP del 99 % de DGT), y después se redondea a dos decimales.

Jugabilidad

Tanto el valor del dado como el del Golden Ticket se generan en el servidor y permanecen ocultos hasta que termina la ronda; no se pueden cambiar sin ser detectados una vez iniciada la partida. Después de realizar tu apuesta, tu umbral elegido se compara con el valor del dado, y la distancia del Golden Ticket respecto al valor del dado se comprueba contra la «zona dorada» de 1,75 para ver si ganas el bonus.

Código para verificación independiente

Fiel a la implementación de DGT en Cloudbet: introduce tu Semilla del servidor, Semilla del cliente y Nonce para verificar tus propias partidas. (Para revelar la Semilla del servidor de una partida en Cloudbet, debes haber pasado ya a una nueva Semilla del servidor.)

Dependencias:

npm install js-sha3

El código:

// 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);
})();

Más explicaciones de provably fair por juego:

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