How provably fair works in Pump

Here's how to play Pump on Cloudbet, and how to check that every round's faulty pump is provably fair.

How to play Pump

Pump is a push-your-luck game: you pump something (a balloon in Cloudbet's version) that grows a multiplier with each pump. Cash out any time to lock in your winnings — push too far, though, and it bursts, and you lose your stake. Before you start, you typically pick a difficulty level that balances how far you can safely go against how fast the multiplier grows.

Once you place your bet, press Pump to inflate the balloon one step at a time. Each successful pump raises your potential payout. You can press Cash Out after any pump to collect your current multiplier — or keep pumping for a bigger one. If your next pump happens to be the round's faulty one, the balloon pops immediately and the round ends with no payout.

Cloudbet Originals Pump

Cloudbet's own Originals Pump has five difficulty levels — Easy, Medium, Hard, Expert, and Master — controlling how many of the round's 25 pumps are "faulty" (the ones that pop the balloon). It has a 99% RTP, a max win of $1,000,000, and accepts bets from $0.01. Autobet Mode lets you configure your bet amount, pumps per round, profit/loss limits, and number of rounds to play automatically — manual play ("Pump waits for YOUR input") is always available too.

For the general mechanics of provably fair — server seed, client seed, nonce, hashing, and how the Round Signature is created — see Cloudbet's provably fair guide. Below is how that random sequence of numbers becomes a Pump round's outcome specifically.

Transforming random numbers into a game layout

Pump's 25 pumps are labelled A to Y so we can track them:

All pumps start non-faulty. Using the first number in the game's random number sequence, we pick a non-faulty pump by index and make it faulty:

That pump is then removed from the list of non-faulty pumps, so the next selection only draws from what's left:

This repeats until the round's difficulty level's faulty-pump count is reached (medium = 3 shown here):

Once every faulty pump for the round is picked, the numbers used to select them are no longer needed — what's left is just which pumps are faulty:

Since pumps are triggered one at a time in order (A, then B, then C…), only the first faulty pump the player reaches actually matters — anything after it is never triggered. So the layout simplifies to just that one relevant pump:

Technical details

We call the number of non-faulty pumps P. The random bytes from SHAKE256 are split into 4-byte unsigned integers (0 to 2³²−1). To fairly pick which pump is faulty, we use rejection sampling against P, then take the remainder (+1) as the index of the non-faulty pump to make faulty. If more faulty pumps are still needed, the selected pump is removed from the list, P is reduced by 1, and a new number is sampled — repeated until the round's faulty-pump count is reached.

The final faulty-pump layout is generated and saved server-side before the round starts, and can't be changed afterward without breaking the Commitment already shared with the player. Each pump the player presses is checked against this pre-committed layout.

Verify it yourself

We've written a small Node.js program accurate to Cloudbet's Pump implementation — enter your Server Seed, Client Seed, Nonce, and difficulty level to reproduce your round's result. (Note: to reveal a round's Server Seed on Cloudbet, you need to have already rotated to a new seed pair.)

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

Install the one dependency first:

npm install js-sha3

More provably fair game explainers:

  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