Cách provably fair hoạt động trong Dice: Golden Ticket

Đây là cách chơi Dice: Golden Ticket trên Cloudbet, và cách kiểm tra xem mỗi lượt tung và lượt rút Golden Ticket có phải là provably fair hay không.

Cách chơi Dice: Golden Ticket

Dice: Golden Ticket (DGT) chơi giống như trò chơi Dice cổ điển của Cloudbet, với thêm một lớp: một mục tiêu thưởng ẩn có thể trả thêm một hệ số nhân thưởng ngoài khoản thắng thông thường của bạn.

Trò chơi cơ bản: chọn Roll Over (2,00–99,99) hoặc Roll Under (0,01–98,00), sau đó đặt cược. Nếu xúc xắc rơi vào phía bạn chọn so với mục tiêu, bạn thắng với tỷ lệ trả thưởng mà mục tiêu đó ngụ ý (mục tiêu càng hẹp = hệ số nhân càng lớn).

Thưởng Golden Ticket: mỗi vòng, một giá trị ẩn thứ hai — Golden Ticket — cũng được đặt trên bàn chơi, với hệ số nhân thưởng riêng từ 3x đến 200x. Nếu kết quả xúc xắc của bạn rơi vào trong phạm vi ±1,75 so với vị trí của Golden Ticket, bạn thắng khoản thưởng theo hệ số nhân đó, ngay lập tức và tự động — không cần đặt cược thêm, và khoản thưởng này cộng dồn với khoản thắng thông thường của bạn.

Bạn không nhắm trực tiếp vào Golden Ticket — đây không phải là thứ bạn có thể nhắm tới hay tác động được, nó chỉ luôn xuất hiện đâu đó trên bàn chơi mỗi vòng, và việc rơi gần nó là một khoản thưởng cộng thêm vào lượt chơi thông thường của bạn.

Cloudbet Originals Dice: Golden Ticket

DGT Originals của Cloudbet có tỷ lệ RTP 99%, hệ số nhân xúc xắc tối đa x6.730, và mức thắng tối đa 1.000.000 $.

Dice: Golden Ticket (DGT) sử dụng cùng nền tảng provably fair như mọi Original khác của Cloudbet — xem Cách Provably Fair Hoạt Động để biết cơ chế chung (Server Seed, Client Seed, Nonce, SHAKE256, rejection sampling). Việc lấy mẫu giá trị xúc xắc của trò chơi này cũng hoạt động giống hệt như Dice thông thường — DGT chỉ cộng thêm giá trị và hệ số nhân Golden Ticket. Dưới đây là cách chuỗi số đó trở thành kết quả DGT của bạn một cách cụ thể.

Biến số ngẫu nhiên thành kết quả trò chơi

DGT cần chứng minh hai giá trị được xác định bởi tính ngẫu nhiên có thể kiểm chứng: giá trị của xúc xắc và giá trị của Golden Ticket.

Quy trình rất đơn giản: đầu tiên, một số nguyên từ 0 đến 10000 (bao gồm cả hai đầu) được lấy mẫu để trở thành giá trị cuối cùng của xúc xắc. Sau đó một số nguyên khác từ 175 đến 9825 (bao gồm cả hai đầu) được lấy mẫu để trở thành giá trị cuối cùng của Golden Ticket. Cả hai số đều được chia cho 100 để mỗi số có hai chữ số thập phân — trong khoảng từ 0 đến 100 (xúc xắc) và 1,75 đến 98,25 (Golden Ticket).

Phạm vi của Golden Ticket được cố tình giữ ở mức 1,75–98,25, thay vì 0–100, để dù nó rơi ở đâu, luôn có một "vùng vàng" rộng đầy đủ 3,5 xung quanh nó mà giá trị xúc xắc có thể rơi vào để thắng thưởng. Nếu phạm vi kéo dài đến tận rìa (0 hoặc 100), một Golden Ticket rơi gần rìa sẽ có vùng vàng nhỏ hơn — và tỷ lệ thắng kém hơn — so với một Golden Ticket rơi ở giữa.

DGT cũng có một tính năng độc đáo: hệ số nhân của Golden Ticket cũng được tạo ra bởi một quy trình provably fair. Nói đơn giản: một số ngẫu nhiên thứ ba từ 0 đến 1 được lấy ra, sau đó được chuyển đổi thành một hệ số nhân từ 3 đến 200 bằng cách sử dụng phân phối Pareto bị cắt cụt.

Sơ đồ cho thấy hệ thống công bằng có thể chứng minh của Cloudbet tạo ra ba giá trị liên kết trong Dice: Golden Ticket — kết quả xúc xắc, vị trí Golden Ticket, và hệ số nhân thưởng của nó.

Chi tiết kỹ thuật

Các byte ngẫu nhiên được tạo bởi SHAKE256 được chia thành các số nguyên không dấu 4 byte, từ 0 đến 2³²-1. Vì giá trị xúc xắc có 10001 kết quả khả dĩ (0–10000, bao gồm cả hai đầu), rejection sampling sử dụng P = 10001; giá trị Golden Ticket có 9651 kết quả khả dĩ (175–9825, bao gồm cả hai đầu), nên P của nó = 9651. (Xem trang provably fair để biết vì sao bước này quan trọng.)

Chọn giá trị của xúc xắc và Golden Ticket: số ngẫu nhiên của xúc xắc được tính modulo 10001 để có giá trị của nó. Điều tương tự cũng được thực hiện với số ngẫu nhiên của Golden Ticket bằng cách dùng 9651, sau đó kết quả được cộng thêm 175 (để nó nằm trong khoảng 175–9825). Cả hai sau đó đều được chia cho 100.

Chọn hệ số nhân của Golden Ticket (một chút kiến thức về thống kê sẽ hữu ích ở đây): một số ngẫu nhiên 32 bit được lấy ra và chia cho giá trị tối đa có thể của nó, tạo ra một số phân bố đều từ 0 đến 1. Giá trị đó được biến đổi thành một phân phối Pareto bị cắt cụt (giới hạn dưới 3, giới hạn trên 200, alpha 1,343 — được chọn sao cho giá trị kỳ vọng của hệ số nhân là 9, giúp duy trì mục tiêu RTP 99% của DGT), sau đó được làm tròn đến hai chữ số thập phân.

Cách chơi

Cả giá trị của xúc xắc và Golden Ticket đều được tạo ra ở phía máy chủ và giữ bí mật cho đến khi vòng chơi kết thúc — chúng không thể bị thay đổi mà không bị phát hiện sau khi ván chơi bắt đầu. Sau khi bạn đặt cược, ngưỡng bạn đã chọn được đối chiếu với giá trị của xúc xắc, và khoảng cách của Golden Ticket so với giá trị xúc xắc được kiểm tra so với "vùng vàng" 1,75 để xem bạn có thắng thưởng hay không.

Mã nguồn để kiểm chứng độc lập

Chính xác với cách triển khai DGT trên Cloudbet — nhập Server Seed, Client Seed và Nonce của bạn để tự kiểm chứng các ván chơi của mình. (Để công bố Server Seed của một ván trên Cloudbet, bạn cần đã chuyển sang một Server 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.
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
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(rng) {
  // Dice
  // Ranges from 0 to 100, with two decimal places
  const diceMinValue  = 0;
  const diceMaxValue  = 10000;
  let diceValue = sampleFromRange(diceMinValue, diceMaxValue, rng);
  diceValue = diceValue / 100;          // Now the value ranges from 0 to 100

  // Golden Ticket
  // Ranges from 1.75 to 98.25 due to fairness
  // (If it landed on 99, for example, then the chance of the dice landing near
  // it would be smaller than the guarantee of 3.5%)
  const gtMinValue  = 175;
  const gtMaxValue  = 9825;
  let goldenTicketValue = sampleFromRange(gtMinValue, gtMaxValue, rng);
  goldenTicketValue = goldenTicketValue / 100;

  // Golden Ticket Multiplier
  const multiplier = getRandomNumberTruncatedPareto(rng);  // Complicated maths, can be ignored ;)

  return { diceValue, goldenTicketValue, multiplier };
}
// Sample a number using the truncated Pareto distribution
function getRandomNumberTruncatedPareto(rng) {
  // Get a uniformly distributed number between 0 and 1
  let randomInt32 = rng.next().value;
  const fraction = randomInt32 / 0xFFFFFFFF;

  // Truncated Pareto distribution. The expected value is 9.00
  const m0 = 3;
  const m1 = 200;
  const alpha = 1.343;

  const r = Math.pow(m0 / m1, alpha);
  const inner = 1 - fraction * (1 - r);
  let randomNumber = m0 / Math.pow(inner, 1 / alpha);
  randomNumber = Math.round(randomNumber * 100) / 100;  // Round to 2 decimal places
  return randomNumber;
}
// Sample a number between 'min' to 'max' (inclusive) using Rejection Sampling
function sampleFromRange(min, max, rng) {
  const P = max - min + 1;
  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 + min;
}

// Example usage
(function main() {
  let nonce = 1;
  if (usingCustomData) { nonce = myNonce; }

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 3 * 2; // We need 3 random numbers, however due to rejection sampling we may need more
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const { diceValue, goldenTicketValue, multiplier } = getGameResult(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('🔁 Round Signature (can be verified after the game):', roundSignature);
  console.log('🎯 Dice landed on:', diceValue);
  console.log('🎫 Golden ticket landed on:', goldenTicketValue);
  console.log('⭐ Multiplier:', multiplier);
})();

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 Limbo
  6. Provably fair Roulette
  7. Provably fair Keno