Så fungerar provably fair i Keno

Så här spelar du Keno på Cloudbet, och så här kontrollerar du att varje dragning är provably fair.

Så spelar du Keno

Keno är ett sifferspel: du väljer en uppsättning nummer på en spelplan, sedan drar spelet sin egen uppsättning nummer. Ju fler av dina val som matchar de dragna numren, desto större blir vinsten – hur mycket större beror på hur många nummer du valde från början. När du har gjort dina val och satsat sker dragningen, och alla träffar visas direkt.

Cloudbet Originals Keno

Cloudbets egen Originals Keno använder en spelplan med 40 nummer: du väljer 1 till 10 nummer, och spelet drar 10. Det har en RTP på 99 % och accepterar insatser från $0.01. Färre val ger högre volatilitet och större utbetalning per träff; fler val ger tätare vinster men lägre utbetalning per träff. Auto Bet finns tillgängligt för att automatisera ett antal rundor.

Kenos dragning genereras på samma verifierbara sätt som alla andra Cloudbet Original – se vår översikt över provably fair för hur Server Seed, Client Seed och Nonce kombineras till en Round Signature och sedan en ström av slumptal via SHAKE256. Den här sidan tar upp det som är specifikt för Keno: hur den slumptalssekvensen blir den faktiska uppsättningen dragna nummer.

Så blir slumptal till en Keno-dragning

Kenos spelplan är numrerad 1 till 40. Tio av dessa 40 nummer ska dras, ett i taget, utan att något nummer upprepas.

Diagram som visar Cloudbets provably fair Keno-bräde — 40 numrerade rutor, var och en med sitt eget index för rättvist nummerval.

Vi kallar antalet ännu inte dragna nummer för P. P börjar på 40 och minskar med 1 varje gång ett nummer dras.

Genomräknat exempel på Kenos provably fair dragningsalgoritm: det första numret väljs slumpmässigt bland alla 40 positioner på brädet.

Ett slumpmässigt 4-byte-tal (32-bitars) hämtas från SHAKE256-utdatan för var och en av de 10 dragningarna, med intervallet 0 till 4 294 967 295. För att välja rättvist bland de P återstående numren använder vi rejection sampling (se vår guide till rejection sampling), och tar sedan resultatet modulo P för att få fram nästa dragna nummers position. Om fler nummer fortfarande ska dras tas det valda numret bort från poolen, P minskar med 1, och processen upprepas.

3. Genomräknat exempel som visar hur Kenos provably fair algoritm drar nästa nummer bland de 39 återstående positionerna efter att den första dragningen tagits bort.

Detta fortsätter tills alla 10 nummer är dragna.

4. Det slutgiltiga provably fair Keno-brädet med alla 10 slumpmässigt och verifierbart dragna numren markerade.

5. Flödesschema över Cloudbets provably fair Keno-beräkning: slumpmässiga bytes och rejection sampling avgör varje dragna numrets position på brädet.

Spelupplägg

Hela uppsättningen av de 10 dragna numren genereras och döljs på servern innan du gör dina val – den kan inte ändras eller manipuleras oupptäckt i efterhand, tack vare Commitment som redan delats med dig. Dina val stäms helt enkelt av mot denna redan dragna uppsättning när rundan avgörs, och din vinst beräknas utifrån hur många träffar du har.

Kod för att självständigt verifiera dina Cloudbet Keno-resultat

Ange din Server Seed, Client Seed och Nonce för att verifiera dina egna resultat. Installera det enda beroendet, och kör koden själv för att återskapa resultatet av ditt spel.

Obs: för att visa Server Seed för ett avslutat spel på Cloudbet måste du redan ha startat en ny runda (vilket roterar till ett nytt seed-par).

Dependencies:

npm install js-sha3

The code:

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