Cách provably fair hoạt động trong Pump
Đây là cách chơi Pump trên Cloudbet, và cách kiểm tra xem bơm lỗi của mỗi vòng có phải là provably fair hay không.
Cách chơi Pump
Pump là một trò chơi "được ăn cả, ngã về không": bạn bơm một thứ gì đó (một quả bóng bay trong phiên bản của Cloudbet) để tăng hệ số nhân sau mỗi lần bơm. Rút tiền bất cứ lúc nào để chốt khoản thắng của bạn — nhưng nếu bơm quá đà, nó sẽ nổ, và bạn mất tiền cược. Trước khi bắt đầu, bạn thường chọn một mức độ khó để cân bằng giữa việc bạn có thể an toàn đi được bao xa và tốc độ tăng của hệ số nhân.
Sau khi đặt cược, nhấn Pump để bơm quả bóng bay từng bước một. Mỗi lần bơm thành công làm tăng khoản thưởng tiềm năng của bạn. Bạn có thể nhấn Rút Tiền sau bất kỳ lần bơm nào để nhận hệ số nhân hiện tại — hoặc tiếp tục bơm để có hệ số lớn hơn. Nếu lần bơm tiếp theo của bạn trùng với bơm lỗi của vòng đó, quả bóng bay sẽ nổ ngay lập tức và vòng chơi kết thúc mà không có khoản thưởng nào.
Cloudbet Originals Pump
Pump Originals của Cloudbet có năm mức độ khó — Dễ, Trung bình, Khó, Chuyên gia và Bậc thầy — kiểm soát số lượng bơm bị lỗi trong tổng số 25 lần bơm của vòng chơi (những lần bơm làm nổ quả bóng bay). Trò chơi có tỷ lệ RTP 99%, mức thắng tối đa 1.000.000 $, và chấp nhận cược từ 0,01 $. Chế độ Autobet cho phép bạn cấu hình số tiền cược, số lần bơm mỗi vòng, giới hạn lãi/lỗ và số vòng chơi tự động — chế độ chơi thủ công ("Pump chờ THAO TÁC của BẠN") luôn có sẵn.
Để biết cơ chế chung của provably fair — server seed, client seed, nonce, hashing, và cách Round Signature được tạo ra — xem hướng dẫn provably fair của Cloudbet. Dưới đây là cách chuỗi số ngẫu nhiên đó trở thành kết quả cụ thể của một vòng Pump.
Biến số ngẫu nhiên thành sơ đồ trò chơi
25 lần bơm của Pump được đánh nhãn từ A đến Y để chúng ta có thể theo dõi chúng:

Tất cả các lần bơm ban đầu đều không lỗi. Sử dụng số đầu tiên trong chuỗi số ngẫu nhiên của trò chơi, chúng tôi chọn một lần bơm không lỗi theo chỉ số và biến nó thành lỗi:

Lần bơm đó sau đó bị loại khỏi danh sách các lần bơm không lỗi, để lượt chọn tiếp theo chỉ lấy từ những gì còn lại:

Việc này lặp lại cho đến khi đạt đủ số lượng bơm lỗi theo mức độ khó của vòng chơi (trung bình = 3 được minh họa ở đây):

Sau khi mọi lần bơm lỗi của vòng chơi đã được chọn xong, các số đã dùng để chọn chúng không còn cần thiết nữa — những gì còn lại chỉ là những lần bơm nào bị lỗi:

Vì các lần bơm được kích hoạt lần lượt theo thứ tự (A, rồi B, rồi C…), chỉ có lần bơm lỗi đầu tiên mà người chơi chạm đến mới thực sự quan trọng — bất kỳ lần nào sau đó sẽ không bao giờ được kích hoạt. Vì vậy sơ đồ được đơn giản hóa chỉ còn một lần bơm liên quan đó:

Chi tiết kỹ thuật
Chúng tôi gọi số lượng bơm không lỗi là P. Các byte ngẫu nhiên từ SHAKE256 được chia thành các số nguyên không dấu 4 byte (0 đến 2³²−1). Để chọn công bằng lần bơm nào bị lỗi, chúng tôi sử dụng rejection sampling đối với P, sau đó lấy phần dư (+1) làm chỉ số của lần bơm không lỗi để biến thành lỗi. Nếu vẫn cần thêm bơm lỗi, lần bơm đã chọn sẽ bị loại khỏi danh sách, P giảm đi 1, và một số mới được lấy mẫu — lặp lại cho đến khi đạt đủ số lượng bơm lỗi của vòng chơi.
Sơ đồ bơm lỗi cuối cùng được tạo ra và lưu ở phía máy chủ trước khi vòng chơi bắt đầu, và không thể bị thay đổi sau đó mà không phá vỡ Commitment đã được chia sẻ với người chơi. Mỗi lần người chơi nhấn bơm chỉ đơn giản là được đối chiếu với sơ đồ đã cam kết trước này.
Tự kiểm chứng
Chúng tôi đã viết một chương trình Node.js nhỏ chính xác với cách triển khai Pump của Cloudbet — nhập Server Seed, Client Seed, Nonce và mức độ khó của bạn để tái tạo lại kết quả vòng chơi của bạn. (Lưu ý: để công bố Server Seed của một vòng trên Cloudbet, bạn cần đã làm mới sang một cặp seed mới.)
// 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);
})();
Cài đặt thư viện duy nhất cần thiết trước:
npm install js-sha3
