Jak działa provably fair w Keno

Oto jak grać w Keno na Cloudbet oraz jak sprawdzić, że każde losowanie jest provably fair.

Jak grać w Keno

Keno to gra polegająca na losowaniu liczb: wybierasz zestaw liczb na planszy, a następnie gra losuje własny zestaw liczb. Im więcej Twoich typów trafi w wylosowane liczby, tym wyższa wygrana — a to, o ile wyższa, zależy od liczby typów wybranych na początku. Po wybraniu liczb i postawieniu zakładu losowanie odbywa się natychmiast, a wszelkie trafienia są od razu widoczne.

Cloudbet Originals Keno

Własna gra Cloudbet z serii Originals — Keno — wykorzystuje planszę z 40 liczbami: wybierasz od 1 do 10 liczb, a gra losuje 10. RTP wynosi 99%, a zakłady można stawiać już od $0.01. Im mniej liczb wybierzesz, tym wyższa zmienność i większe wygrane za trafienie; im więcej liczb wybierzesz, tym częstsze wygrane, ale niższe zwroty za trafienie. Dostępna jest funkcja Auto Bet, która automatyzuje serię rund.

Losowanie w Keno jest generowane w ten sam, możliwy do zweryfikowania sposób co w każdej innej grze Cloudbet Originals — zobacz nasz przegląd systemu provably fair, aby dowiedzieć się, jak Server Seed, Client Seed i Nonce łączą się w Round Signature, a następnie w ciąg liczb losowych generowany przez SHAKE256. Ta strona opisuje to, co jest specyficzne dla Keno: w jaki sposób ten ciąg liczb losowych zamienia się w rzeczywisty zestaw wylosowanych liczb.

Jak liczby losowe zamieniają się w losowanie Keno

Plansza Keno jest ponumerowana od 1 do 40. Spośród tych 40 liczb trzeba wylosować dziesięć, pojedynczo, bez powtórzeń.

Diagram przedstawiający planszę Keno provably fair Cloudbet — 40 ponumerowanych pól, każde z własnym indeksem zapewniającym uczciwy wybór liczb.

Liczbę jeszcze niewylosowanych liczb nazywamy P. P zaczyna się od 40 i zmniejsza się o 1 za każdym razem, gdy zostanie wylosowana kolejna liczba.

Przykład działania algorytmu losowania provably fair w Keno: pierwsza liczba jest losowana spośród wszystkich 40 pozycji na planszy.

Dla każdego z 10 losowań z wyniku SHAKE256 pobierana jest losowa liczba o długości 4 bajtów (32 bitów), z zakresu od 0 do 4,294,967,295. Aby wybrać liczbę sprawiedliwie spośród pozostałych P liczb, stosujemy próbkowanie odrzucające (zobacz nasz przewodnik po próbkowaniu odrzucającym), a następnie wyznaczamy resztę z dzielenia wyniku przez P, aby uzyskać pozycję kolejnej wylosowanej liczby. Jeśli trzeba jeszcze wylosować kolejne liczby, wybrana liczba jest usuwana z puli, P zmniejsza się o 1, a cały proces się powtarza.

3. Przykład pokazujący, jak algorytm provably fair Keno losuje kolejną liczbę spośród 39 pozostałych pozycji po usunięciu pierwszego losowania.

Proces trwa, dopóki nie zostanie wylosowanych wszystkich 10 liczb.

4. Ostateczna plansza Keno provably fair z wszystkimi 10 losowo i weryfikowalnie wylosowanymi liczbami podświetlonymi.

5. Schemat blokowy obliczeń provably fair Keno Cloudbet: losowe bajty i rejection sampling określają pozycję każdej wylosowanej liczby na planszy.

Rozgrywka

Pełny zestaw 10 wylosowanych liczb jest generowany i ukrywany na serwerze, zanim dokonasz swoich wyborów — dzięki Commitment udostępnionemu Ci wcześniej nie da się go później zmienić bez wykrycia. Twoje typy są po prostu porównywane z tym wcześniej wylosowanym zestawem po zakończeniu rundy, a wygrana jest obliczana na podstawie liczby trafień.

Kod do samodzielnej weryfikacji Twoich wyników Keno na Cloudbet

Wprowadź swój Server Seed, Client Seed i Nonce, aby zweryfikować własne wyniki. Zainstaluj jedyną wymaganą zależność i uruchom kod samodzielnie, aby odtworzyć wynik swojej gry.

Uwaga: aby ujawnić Server Seed z zakończonej gry na Cloudbet, musisz już rozpocząć nową rundę (co powoduje zmianę na nową parę seedów).

Zależności:

npm install js-sha3

Kod:

// 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);
})();