Cách provably fair hoạt động trong Roulette

Đây là cách chơi Roulette trên Cloudbet, và cách kiểm tra xem mỗi lượt quay có phải là provably fair hay không.

Cách chơi Roulette

Roulette là một trò chơi casino kinh điển, nơi bạn cược vào vị trí một quả bóng đang quay sẽ dừng lại trên một bánh xe có đánh số. Bạn đặt một hoặc nhiều cược trên bàn số, sau đó bánh xe quay và quả bóng được thả vào. Khi nó dừng lại ở một ô có số, đó là số thắng cuộc — mọi cược bao phủ số đó sẽ được trả thưởng, và mọi cược khác đều thua.

Đặt cược

Bạn có thể cược vào kết quả theo một vài cách khác nhau, và bạn có thể tự do kết hợp nhiều cược trong cùng một vòng. Cược trong (vào các số cụ thể) trả thưởng cao hơn nhưng khó trúng hơn; cược ngoài (vào các nhóm rộng hơn) trả thưởng thấp hơn nhưng trúng thường xuyên hơn:

  1. Straight-up / Single Number (35:1): cược vào một số chính xác (0–36, hoặc 00 trong Roulette kiểu Mỹ).
  2. Split (17:1): hai số liền kề.
  3. Street (11:1): ba số liên tiếp trên một hàng.
  4. Corner (8:1): bốn số gặp nhau tại một góc.
  5. First Four / Top Line (8:1 kiểu Châu Âu / 6:1 kiểu Mỹ): bao gồm 0 (và 00 trong kiểu Mỹ), 1, 2, 3.
  6. Six Line (5:1): hai hàng liền kề, sáu số.
  7. Column (2:1): một trong ba cột dọc.
  8. Dozens (2:1): 1–12, 13–24, hoặc 25–36.
  9. Đỏ/Đen, Lẻ/Chẵn, Cao/Thấp (1:1 mỗi loại).

Roulette kiểu Châu Âu và kiểu Mỹ

Cloudbet cung cấp cả hai phiên bản. Roulette kiểu Châu Âu có một ô số không duy nhất (37 kết quả khả dĩ, RTP 97,30%); Roulette kiểu Mỹ có thêm ô "00" thứ hai (38 kết quả khả dĩ, RTP 97%), làm tăng nhẹ lợi thế nhà cái. Phiên bản bạn đang chơi sẽ được hiển thị rõ ràng trước khi bạn đặt cược. Cả hai đều có chế độ Autoplay để quay liên tục với giá trị chip và số lượt quay được đặt sẵn.

Sau khi cược của bạn được đặt và vòng chơi kết thúc, mọi cược thắng sẽ được trả tự động theo tỷ lệ tương ứng, và bàn chơi được đặt lại cho vòng tiếp theo.

Trang này trình bày cách Roulette biến một chuỗi số ngẫu nhiên provably fair thành số thắng cuộc. Để biết cách chuỗi số ngẫu nhiên đó được tạo ra ngay từ đầu — server seed, client seed, nonce, hashing, SHAKE256 — xem hướng dẫn provably fair của Cloudbet.

Biến số ngẫu nhiên thành kết quả Roulette

Đối với Roulette, hệ thống provably fair phải chứng minh số thắng cuộc được chọn bởi tính ngẫu nhiên có thể kiểm chứng, chứ không phải được chọn sau khi sự việc đã xảy ra.

Quy trình rất đơn giản: trong Roulette kiểu Châu Âu, một số nguyên từ 0 đến 36 được lấy mẫu từ chuỗi số ngẫu nhiên — đó là số thắng cuộc. Trong Roulette kiểu Mỹ, số được lấy mẫu có thể nằm từ 0 đến 37, với "37" được ánh xạ thành số 00 kép; các kết quả từ 0 đến 36 được giữ nguyên.

Cách số thắng cuộc được chọn

Các byte ngẫu nhiên được tạo bởi SHAKE256 (xem trang provably fair của chúng tôi) được chia thành các số nguyên không dấu 4 byte, từ 0 đến 2³²−1. Số thắng cuộc của Roulette được chọn trong số 37 kết quả khả dĩ (Châu Âu) hoặc 38 (Mỹ) — vì vậy trong các bước dưới đây, P là 37 hoặc 38.

Để chọn công bằng một số thắng cuộc trong số P kết quả đó, chúng tôi áp dụng rejection sampling (xem Cách provably fair hoạt động), sau đó lấy số kết quả modulo P — phần dư trở thành số thắng cuộc (đối với Roulette kiểu Mỹ, "37" được hiển thị là "00").

Sơ đồ luồng cho thấy hệ thống công bằng có thể chứng minh của Roulette từ Cloudbet sử dụng phương pháp lấy mẫu loại trừ và phép toán modulo để xác định con số thắng.

Sau khi được tạo ra, số thắng cuộc được lưu và giấu kín ở phía máy chủ — nó không thể bị thay đổi hay can thiệp mà không bị phát hiện sau khi vòng chơi bắt đầu. Sau khi bạn đặt cược, nó được đối chiếu với số đã được xác định trước này để phân định kết quả vòng chơi.

Tự kiểm chứng kết quả Roulette của bạn

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 Roulette của Cloudbet (cả kiểu Châu Âu và kiểu Mỹ). Nhập Server Seed, Client Seed và Nonce của bạn để tái tạo lại kết quả của bạn. (Nhắc lại: để 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.)

Các thư viện cần cài đặt:

npm install js-sha3

Mã nguồn:

// 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.
const Version = Object.freeze({ AMERICAN: "american", EUROPEAN: "european" }); // Here the possible versions are listed
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 myRouletteVersion = Version.AMERICAN;   // ✍️YOUR_INPUT✍️: Enter your roulette version (either Version.AMERICAN or Version.EUROPEAN)
const usingCustomData = false;              // Change this to true if you are using your own data

// 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); // shake 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: Draw from the random number sequence to calculate the game result
function getGameResult(rouletteVersion, rng) {
  let numOfOutcomes = 0;
  if (rouletteVersion === Version.EUROPEAN) {
    numOfOutcomes = 37;
  } else if (rouletteVersion === Version.AMERICAN) {
    numOfOutcomes = 38;
  }

  let result = String(sample(numOfOutcomes, rng))

  // Convert the result '37' to '00'
  if (result === '37') {
    result = '00';
  }
  return result;
}
// Sample a number from P number of outcomes using Rejection Sampling
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() {
  let nonce = 1;
  let rouletteVersion = Version.AMERICAN;
  if (usingCustomData) { nonce = myNonce; rouletteVersion = myRouletteVersion; }

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 1 * 2; // We need 1 random number, but there is an improbable case we'll need two
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const value = getGameResult(rouletteVersion, rng);

  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(`🎯 Winning Number (in the ${rouletteVersion} version):`, value);
})();

Các bài giải thích khác về provably fair:

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