Pump에서 입증된 공정성이 작동하는 방식
Cloudbet에서 Pump를 플레이하는 방법과, 매 라운드의 불량 펌프가 입증된 공정성을 갖추고 있는지 확인하는 방법을 알아보세요.
Pump 플레이 방법
Pump는 욕심과 위험 사이에서 줄타기하는 게임입니다. 펌프질을 할 때마다(Cloudbet 버전에서는 풍선을 부풀립니다) 배수가 커집니다. 언제든 캐시아웃해 당첨금을 확정할 수 있지만, 너무 멀리 밀어붙이면 풍선이 터져 베팅 금액을 잃게 됩니다. 시작하기 전에 보통 난이도를 선택하며, 이는 안전하게 갈 수 있는 거리와 배수가 커지는 속도 사이의 균형을 결정합니다.
베팅 후 펌프(Pump) 버튼을 눌러 풍선을 한 단계씩 부풀립니다. 성공적으로 펌프질할 때마다 잠재적인 당첨금이 올라갑니다. 아무 펌프 단계 이후에나 캐시아웃(Cash Out)을 눌러 현재 배수를 확정할 수 있으며, 더 큰 배수를 노리고 계속 진행할 수도 있습니다. 다음 펌프가 해당 라운드의 불량 펌프라면 풍선은 즉시 터지고 라운드는 당첨금 없이 종료됩니다.
Cloudbet Originals Pump
Cloudbet 자체 제작 Originals Pump는 다섯 가지 난이도(Easy, Medium, Hard, Expert, Master)를 제공하며, 이는 라운드의 25개 펌프 중 몇 개가 "불량"(풍선을 터뜨리는 펌프)인지를 결정합니다. RTP는 99%, 최대 당첨금은 $1,000,000이며, $0.01부터 베팅할 수 있습니다. 오토베팅 모드(Autobet Mode)를 사용하면 베팅 금액, 라운드당 펌프 횟수, 손익 한도, 자동 플레이할 라운드 수를 설정할 수 있으며, 언제든 수동 플레이("Pump waits for YOUR input")도 가능합니다.
입증된 공정성의 일반적인 메커니즘(서버 시드, 클라이언트 시드, 논스, 해싱, 라운드 서명 생성 방식)에 대해서는 Cloudbet의 입증된 공정성 가이드를 참고하세요. 아래에서는 그 무작위 숫자 시퀀스가 구체적으로 Pump 라운드의 결과로 어떻게 변환되는지 설명합니다.
무작위 숫자를 게임 배치로 변환하기
Pump의 25개 펌프에는 추적을 위해 A부터 Y까지 라벨이 붙어 있습니다.

모든 펌프는 처음에는 정상(불량이 아님) 상태로 시작합니다. 게임의 무작위 숫자 시퀀스에서 첫 번째 숫자를 사용해, 인덱스로 정상 펌프 하나를 선택해 불량으로 만듭니다.

해당 펌프는 정상 펌프 목록에서 제거되므로, 다음 선택은 남은 펌프 중에서만 이루어집니다.

이 과정은 해당 라운드의 난이도에 설정된 불량 펌프 개수에 도달할 때까지 반복됩니다(여기서는 medium = 3개인 경우를 예시로 보여줍니다).

라운드의 모든 불량 펌프가 선택되고 나면, 그것들을 선택하는 데 사용된 숫자들은 더 이상 필요하지 않으며, 남는 것은 어떤 펌프가 불량인지에 대한 정보뿐입니다.

펌프는 순서대로(A, 그 다음 B, 그 다음 C…) 한 번에 하나씩 작동하므로, 플레이어가 도달하는 첫 번째 불량 펌프만 실제로 중요하며, 그 이후의 펌프는 전혀 작동하지 않습니다. 따라서 배치는 그 관련 있는 펌프 하나로 단순화됩니다.

기술적 세부사항
정상 펌프의 개수를 P라고 부릅니다. SHAKE256에서 생성된 무작위 바이트는 4바이트 부호 없는 정수(0에서 2³²−1)로 분할됩니다. 어떤 펌프를 불량으로 만들지 공정하게 선택하기 위해, P에 대해 거부 샘플링을 적용한 다음 나머지(+1)를 불량으로 만들 정상 펌프의 인덱스로 사용합니다. 아직 더 필요한 불량 펌프가 있다면 선택된 펌프는 목록에서 제거되고 P는 1만큼 줄어들며, 새로운 숫자가 추출됩니다. 이 과정은 라운드의 불량 펌프 개수에 도달할 때까지 반복됩니다.
최종 불량 펌프 배치는 라운드가 시작되기 전에 서버 측에서 생성되어 저장되며, 이미 플레이어에게 공유된 커밋먼트(Commitment)를 깨뜨리지 않고서는 이후에 변경할 수 없습니다. 플레이어가 누르는 각 펌프는 이 사전에 확정된 배치와 비교됩니다.
직접 검증하기
Cloudbet의 Pump 구현과 정확히 일치하는 작은 Node.js 프로그램을 작성했습니다. 서버 시드, 클라이언트 시드, 논스, 난이도를 입력해 라운드 결과를 재현해 보세요. (참고: Cloudbet에서 라운드의 서버 시드를 공개하려면 이미 새로운 시드 쌍으로 교체한 상태여야 합니다.)
// 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);
})();
의존성 하나를 먼저 설치하세요:
npm install js-sha3
