Come funziona il provably fair in Pump

Ecco come giocare a Pump su Cloudbet, e come verificare che la pompa difettosa di ogni round sia provably fair.

Come giocare a Pump

Pump è un gioco push-your-luck: gonfi qualcosa (un palloncino, nella versione di Cloudbet) che fa crescere un moltiplicatore a ogni pompata. Incassa in qualsiasi momento per bloccare le tue vincite — ma se spingi troppo oltre, scoppia e perdi la puntata. Prima di iniziare, di solito scegli un livello di difficoltà che bilancia quanto puoi spingerti in sicurezza rispetto a quanto velocemente cresce il moltiplicatore.

Una volta piazzata la scommessa, premi Pump per gonfiare il palloncino un passo alla volta. Ogni pompata riuscita aumenta la tua potenziale vincita. Puoi premere Incassa dopo ogni pompata per incassare il tuo moltiplicatore attuale — oppure continuare a pompare per ottenerne uno più alto. Se la tua prossima pompata è quella difettosa del round, il palloncino scoppia immediatamente e il round finisce senza vincita.

Cloudbet Originals Pump

Il Pump degli Originals di Cloudbet ha cinque livelli di difficoltà — Facile, Medio, Difficile, Esperto e Master — che controllano quante delle 25 pompate del round sono "difettose" (quelle che fanno scoppiare il palloncino). Ha un RTP del 99%, una vincita massima di 1.000.000 $ e accetta scommesse da 0,01 $. La modalità Autobet ti permette di configurare l'importo della scommessa, le pompate per round, i limiti di profitto/perdita e il numero di round da giocare automaticamente — è sempre disponibile anche la modalità manuale ("Pump aspetta il TUO input").

Per i meccanismi generali del provably fair — server seed, client seed, nonce, hashing e come viene creata la Round Signature — consulta la guida al provably fair di Cloudbet. Di seguito trovi come quella sequenza di numeri casuali diventa nello specifico il risultato di un round di Pump.

Trasformare i numeri casuali in una disposizione di gioco

Le 25 pompate di Pump sono etichettate da A a Y in modo da poterle tracciare:

Tutte le pompate partono come non difettose. Usando il primo numero della sequenza di numeri casuali del round, selezioniamo una pompata non difettosa in base al suo indice e la rendiamo difettosa:

Quella pompata viene poi rimossa dall'elenco delle pompate non difettose, così la selezione successiva pesca solo da quelle rimaste:

Questo si ripete finché non si raggiunge il numero di pompate difettose previsto dal livello di difficoltà del round (qui è mostrato medio = 3):

Una volta selezionate tutte le pompate difettose del round, i numeri usati per selezionarle non servono più — resta solo quali pompate sono difettose:

Poiché le pompate vengono attivate una alla volta in ordine (A, poi B, poi C…), conta solo la prima pompata difettosa che il giocatore raggiunge — tutto ciò che viene dopo non viene mai attivato. Quindi la disposizione si semplifica in quell'unica pompata rilevante:

Dettagli tecnici

Chiamiamo il numero di pompate non difettose P. I byte casuali provenienti da SHAKE256 vengono suddivisi in numeri interi senza segno di 4 byte (da 0 a 2³²−1). Per scegliere in modo equo quale pompata rendere difettosa, usiamo il rejection sampling rispetto a P, quindi prendiamo il resto (+1) come indice della pompata non difettosa da rendere difettosa. Se servono ancora altre pompate difettose, la pompata selezionata viene rimossa dall'elenco, P viene ridotto di 1 e viene campionato un nuovo numero — si ripete finché non si raggiunge il numero di pompate difettose del round.

La disposizione finale delle pompate difettose viene generata e salvata lato server prima dell'inizio del round, e non può essere modificata in seguito senza invalidare il Commitment già condiviso con il giocatore. Ogni pompata premuta dal giocatore viene confrontata con questa disposizione pre-impegnata.

Verificalo tu stesso

Abbiamo scritto un piccolo programma Node.js fedele all'implementazione di Pump su Cloudbet — inserisci il tuo Server Seed, Client Seed, Nonce e livello di difficoltà per riprodurre il risultato del tuo round. (Nota: per rivelare il Server Seed di un round su Cloudbet, devi aver già ruotato verso una nuova coppia di seed.)

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

Installa prima l'unica dipendenza:

npm install js-sha3

Altre spiegazioni sul provably fair per i vari giochi:

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