Cómo funciona provably fair en Pump

Así se juega a Pump en Cloudbet, y así puedes comprobar que la bomba defectuosa de cada ronda es provably fair.

Cómo jugar a Pump

Pump es un juego de «hasta dónde te atreves»: inflas algo (un globo, en la versión de Cloudbet) que hace crecer un multiplicador con cada bombeo. Retírate cuando quieras para asegurar tus ganancias; si te pasas, el globo revienta y pierdes tu apuesta. Antes de empezar, normalmente eliges un nivel de dificultad que equilibra hasta dónde puedes llegar con seguridad frente a la velocidad a la que crece el multiplicador.

Una vez realizada tu apuesta, pulsa Pump para inflar el globo paso a paso. Cada bombeo exitoso aumenta tu premio potencial. Puedes pulsar Retirar después de cualquier bombeo para cobrar tu multiplicador actual, o seguir bombeando para conseguir uno mayor. Si tu siguiente bombeo resulta ser el defectuoso de la ronda, el globo revienta al instante y la ronda termina sin premio.

Pump de Cloudbet Originals

El Pump propio de Cloudbet Originals tiene cinco niveles de dificultad —Fácil, Medio, Difícil, Experto y Maestro—, que controlan cuántos de los 25 bombeos de la ronda son «defectuosos» (los que revientan el globo). Tiene un RTP del 99 %, un premio máximo de 1 000 000 $ y acepta apuestas desde 0,01 $. El modo Autobet te permite configurar el importe de la apuesta, los bombeos por ronda, límites de ganancia/pérdida y el número de rondas para jugar automáticamente; el modo manual («Pump espera TU decisión») también está siempre disponible.

Para conocer la mecánica general de provably fair —semilla del servidor, semilla del cliente, nonce, hashing y cómo se crea la Firma de ronda—, consulta la guía provably fair de Cloudbet. A continuación se explica cómo esa secuencia de números aleatorios se convierte específicamente en el resultado de una ronda de Pump.

Transformar números aleatorios en la disposición del juego

Los 25 bombeos de Pump se etiquetan de la A a la Y para poder seguirlos:

Diagrama que muestra las 25 bombas de Pump etiquetadas de la A a la Y, utilizadas para seguir la selección de equidad demostrable de la bomba defectuosa de Cloudbet.

Todos los bombeos empiezan como no defectuosos. Usando el primer número de la secuencia aleatoria de la partida, elegimos un bombeo no defectuoso por índice y lo convertimos en defectuoso:

Ejemplo práctico del algoritmo de equidad demostrable de Pump: la primera bomba defectuosa se selecciona al azar entre las 25 bombas no defectuosas.

Ese bombeo se elimina después de la lista de bombeos no defectuosos, de modo que la siguiente selección solo se hace entre los que quedan:

Diagrama que muestra cómo el sistema de equidad demostrable de Pump de Cloudbet elimina de la lista una bomba defectuosa seleccionada antes de sortear la siguiente.

Esto se repite hasta alcanzar el número de bombeos defectuosos del nivel de dificultad de la ronda (aquí se muestra medio = 3):

Ejemplo práctico que muestra al algoritmo de equidad demostrable de Pump seleccionando una segunda bomba defectuosa entre las bombas no defectuosas restantes.

Una vez elegido cada bombeo defectuoso de la ronda, los números usados para seleccionarlos ya no hacen falta; lo que queda es simplemente qué bombeos son defectuosos:

Diagrama que muestra la disposición completa de bombas defectuosas de una ronda antes de que el cálculo de equidad demostrable de Cloudbet la simplifique a la única bomba que importa.

Como los bombeos se activan de uno en uno y en orden (A, luego B, luego C…), solo importa realmente el primer bombeo defectuoso que alcanza el jugador; todo lo que va después nunca llega a activarse. Así que la disposición se simplifica a ese único bombeo relevante:

Diagrama que muestra la disposición de equidad demostrable de bombas defectuosas de Pump simplificada a la primera bomba defectuosa que el jugador alcanzaría realmente.

Detalles técnicos

Llamamos P al número de bombeos no defectuosos. Los bytes aleatorios de SHAKE256 se dividen en enteros sin signo de 4 bytes (de 0 a 2³²−1). Para elegir de forma justa qué bombeo es defectuoso, usamos muestreo por rechazo contra P, y tomamos el resto (+1) como el índice del bombeo no defectuoso que se vuelve defectuoso. Si aún hacen falta más bombeos defectuosos, el bombeo elegido se elimina de la lista, P se reduce en 1 y se muestrea un nuevo número, repitiendo el proceso hasta alcanzar el número de bombeos defectuosos de la ronda.

La disposición final de bombeos defectuosos se genera y se guarda en el servidor antes de que empiece la ronda, y no se puede cambiar después sin romper el Compromiso ya compartido con el jugador. Cada bombeo que pulsa el jugador se comprueba contra esta disposición pre-comprometida.

Verifícalo tú mismo

Hemos escrito un pequeño programa en Node.js fiel a la implementación de Pump en Cloudbet: introduce tu Semilla del servidor, Semilla del cliente, Nonce y nivel de dificultad para reproducir el resultado de tu ronda. (Nota: para revelar la Semilla del servidor de una ronda en Cloudbet, debes haber rotado ya a un nuevo par de semillas.)

// 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
let myDifficultyLevel = 'easy';             // ✍️YOUR_INPUT✍️: Enter your difficulty level here (easy, medium, hard, expert, or master)
const usingCustomData = false;              // Change this to true if you are using your own data

// These are the possible difficulty levels (and how many faulty pumps there are per difficulty level)
const faultyPumpsPerDiff = {
  'easy':       1,
  'medium':     3,
  'hard':       5,
  'expert':     9,
  'master':     12
};

// 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); // shaker256 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: Generate indices of the faulty pumps
function pickUniquePositions(numOfFaultyPumps, totalPumps, rng) {
  const available = Array.from({ length: totalPumps }, (_, i) => i);
  const faultyPumps = [];

  for (let i = 0; i < numOfFaultyPumps; ++i) {
    const index = sample(available.length, rng);

    faultyPumps.push(available[index]);
    available.splice(index, 1); // Remove chosen pump from list of non-faulty pumps
  }
  return faultyPumps;
}
// Rejection sampling for fair results
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() {
  const totalPumps = 25;
  let difficultyLevel = 'easy';
  let nonce = 1;
  if (usingCustomData) { difficultyLevel = myDifficultyLevel; nonce = myNonce; }

  const numOfFaultyPumps = faultyPumpsPerDiff[difficultyLevel];

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 12 * 2;                 // We have at most 12 faulty pumps, however due to rejection sampling we may need more numbers
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const faultyPumps = pickUniquePositions(numOfFaultyPumps, totalPumps, rng);
  let firstFaultyPump = Math.min(...faultyPumps);  // Select the smallest/first faulty pump
  firstFaultyPump += 1;                            // Increment the index so that it starts with 1, 2, 3, ... instead of 0, 1, 2

  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('🎯 Difficulty level:', difficultyLevel);
  console.log('💣 First faulty pump (zero-indexed, i.e., 1, 2, 3, ...):', firstFaultyPump);
})();

Instala primero la única dependencia:

npm install js-sha3

Más explicaciones de provably fair por juego:

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