Cách công bằng có thể kiểm chứng hoạt động trong Keno

Đây là cách chơi Keno trên Cloudbet, và cách kiểm tra xem mỗi lượt quay có thực sự công bằng và có thể kiểm chứng hay không.

Cách chơi Keno

Keno là trò chơi rút số: bạn chọn một tập hợp số trên bảng, sau đó trò chơi tự rút ra tập số của riêng nó. Số lượng con số bạn chọn trùng với số được rút càng nhiều, tiền thắng của bạn càng lớn — mức độ lớn tùy thuộc vào số lượng con số bạn đã chọn ban đầu. Sau khi chọn số và đặt cược, lượt quay diễn ra và mọi kết quả trùng khớp được hiển thị ngay lập tức.

Cloudbet Originals Keno

Keno Originals của riêng Cloudbet sử dụng bảng 40 số: bạn chọn từ 1 đến 10 số, và trò chơi rút ra 10 số. Trò chơi có RTP 99% và chấp nhận cược từ $0.01. Chọn ít số hơn đồng nghĩa với độ biến động cao hơn và tiền thắng mỗi lần trúng lớn hơn; chọn nhiều số hơn đồng nghĩa với thắng thường xuyên hơn nhưng tiền thắng mỗi lần trúng thấp hơn. Bạn có thể dùng Auto Bet để tự động hóa nhiều vòng chơi liên tiếp.

Lượt quay của Keno được tạo ra theo cách có thể kiểm chứng giống như mọi trò chơi Cloudbet Original khác — xem tổng quan về công bằng có thể kiểm chứng của chúng tôi để biết Server Seed, Client Seed và Nonce kết hợp như thế nào để tạo ra Round Signature, sau đó là một chuỗi số ngẫu nhiên thông qua SHAKE256. Trang này tập trung vào phần đặc thù của Keno: chuỗi số ngẫu nhiên đó trở thành tập số được rút thực tế như thế nào.

Biến số ngẫu nhiên thành lượt quay Keno

Bảng Keno được đánh số từ 1 đến 40. Mười trong số 40 con số này cần được rút ra, từng số một, không lặp lại bất kỳ số nào.

Sơ đồ minh họa bảng Keno provably fair của Cloudbet — 40 ô được đánh số, mỗi ô có chỉ số riêng để chọn số công bằng.

Chúng tôi gọi số lượng các con số chưa được rút còn lại là P. P bắt đầu từ 40 và giảm đi 1 mỗi khi một số được rút ra.

Ví dụ minh họa thuật toán quay số provably fair của Keno: số đầu tiên được chọn ngẫu nhiên trong số 40 vị trí trên bảng.

Một số ngẫu nhiên 4 byte (32 bit) được rút từ đầu ra SHAKE256 cho mỗi lượt trong 10 lượt rút, trong khoảng từ 0 đến 4,294,967,295. Để chọn công bằng trong số P con số còn lại, chúng tôi áp dụng phương pháp lấy mẫu loại bỏ — rejection sampling (xem hướng dẫn về rejection sampling của chúng tôi), sau đó lấy kết quả modulo P để xác định vị trí của số được rút tiếp theo. Nếu vẫn còn số cần rút, số vừa chọn sẽ bị loại khỏi nhóm, P giảm đi 1 và quá trình lặp lại.

3. Ví dụ minh họa thuật toán provably fair của Keno đang quay số tiếp theo trong số 39 vị trí còn lại sau khi số đầu tiên được loại bỏ.

Quá trình này tiếp tục cho đến khi cả 10 số đều đã được rút.

4. Bảng Keno provably fair cuối cùng với cả 10 số được quay ngẫu nhiên và có thể xác minh được tô sáng.

5. Sơ đồ luồng tính toán provably fair của Keno tại Cloudbet: các byte ngẫu nhiên và rejection sampling xác định vị trí của từng số được quay trên bảng.

Cách chơi

Toàn bộ 10 số được rút ra được tạo và ẩn trên máy chủ trước khi bạn chọn số — nó không thể bị thay đổi hoặc chỉnh sửa mà không bị phát hiện sau đó, nhờ vào Commitment đã được chia sẻ với bạn từ trước. Các lựa chọn của bạn chỉ đơn giản được đối chiếu với tập số đã được rút sẵn này khi vòng chơi kết thúc, và tiền thắng của bạn được tính dựa trên số lượng trùng khớp bạn có.

Mã để tự xác minh kết quả Keno của bạn trên Cloudbet

Nhập Server Seed, Client Seed và Nonce của bạn để tự kiểm chứng kết quả của mình. Cài đặt thư viện phụ thuộc duy nhất, rồi tự chạy để tái tạo lại kết quả ván chơi của bạn.

Lưu ý: để xem Server Seed của một ván đã hoàn thành trên Cloudbet, bạn cần đã bắt đầu một vòng chơi mới trước đó (thao tác này chuyển sang một cặp seed mới).

Thư viện phụ thuộc:

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 mine positions to the mine positions 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
  }
}

// Step 4: Pick a number of unique positions
function pickUniquePositions(numOfPicks, totalPositions, rng) {
  const available = Array.from({ length: totalPositions }, (_, i) => i);
  const picks = [];

  for (let i = 0; i < numOfPicks; ++i) {
    const index = sample(available.length, rng);

    picks.push(available[index]);
    available.splice(index, 1); // Remove chosen pump from list of non-faulty pumps
  }
  return picks;
}
// 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() {
  let nonce = 1;
  if (usingCustomData) { nonce = myNonce; }
  const numOfHits = 10;
  const totalPositions = 40;

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = totalPositions * 2; // We need at least one random number per position, but there is an improbable possibility  we'll need two
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  let hits = pickUniquePositions(numOfHits, totalPositions, rng);
  hits = hits.map((value)=> value + 1 );
  hits.sort((a, b)=>a-b);

  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(`🎯 Hits:`, hits);
})();