Comment fonctionne l'équité vérifiable au Keno

Voici comment jouer au Keno sur Cloudbet, et comment vérifier que chaque tirage est équitable et vérifiable.

Comment jouer au Keno

Le Keno est un jeu de tirage de numéros : vous choisissez une série de numéros sur une grille, puis le jeu tire sa propre série de numéros. Plus vous trouvez de numéros parmi ceux tirés, plus votre gain est important — le montant du gain dépend du nombre de numéros choisis au départ. Une fois vos numéros choisis et votre mise placée, le tirage a lieu et vos numéros trouvés sont révélés instantanément.

Cloudbet Originals Keno

Le Keno Originals de Cloudbet utilise une grille de 40 numéros : vous choisissez de 1 à 10 numéros, et le jeu en tire 10. Il offre un RTP de 99% et accepte les mises à partir de $0.01. Moins vous choisissez de numéros, plus la volatilité est élevée et plus vous gagnez par numéro trouvé ; plus vous en choisissez, plus les gains sont fréquents mais chacun rapporte moins. La fonction Auto Bet permet d'automatiser plusieurs parties.

Le tirage du Keno est généré de la même manière vérifiable que tous les autres Cloudbet Original — consultez notre aperçu de l'équité vérifiable pour voir comment le Server Seed, le Client Seed et le Nonce se combinent pour former une Round Signature, puis une suite de nombres aléatoires via SHAKE256. Cette page traite de ce qui est spécifique au Keno : comment cette suite de nombres aléatoires devient la série de numéros réellement tirés.

Comment les nombres aléatoires deviennent un tirage du Keno

La grille du Keno est numérotée de 1 à 40. Dix de ces 40 numéros doivent être tirés, un par un, sans répétition.

Schéma montrant la grille Keno provably fair de Cloudbet — 40 cases numérotées, chacune avec son propre index pour une sélection équitable des numéros.

Nous appelons P le nombre de numéros non encore tirés restants. P commence à 40 et diminue de 1 à chaque numéro tiré.

Exemple concret de l'algorithme de tirage provably fair de Keno : le premier numéro est sélectionné aléatoirement parmi les 40 positions de la grille.

Un nombre aléatoire de 4 octets (32 bits) est tiré de la sortie SHAKE256 pour chacun des 10 tirages, allant de 0 à 4 294 967 295. Pour choisir équitablement parmi les P numéros restants, nous appliquons l'échantillonnage par rejet (voir notre guide sur l'échantillonnage par rejet), puis nous prenons le résultat modulo P pour obtenir la position du prochain numéro tiré. S'il reste des numéros à tirer, le numéro choisi est retiré du groupe, P diminue de 1, et le processus se répète.

3. Exemple concret montrant l'algorithme provably fair de Keno tirant le numéro suivant parmi les 39 positions restantes après le retrait du premier tirage.

Ce processus continue jusqu'à ce que les 10 numéros soient tirés.

4. La grille Keno provably fair finale avec les 10 numéros tirés aléatoirement et de manière vérifiable, mis en évidence.

5. Schéma du calcul provably fair de Keno chez Cloudbet : des octets aléatoires et le rejection sampling déterminent la position de chaque numéro tiré sur la grille.

Déroulement du jeu

La série complète des 10 numéros tirés est générée et dissimulée sur le serveur avant que vous ne fassiez vos choix — elle ne peut être modifiée ou altérée sans être détectée par la suite, grâce au Commitment déjà partagé avec vous. Vos choix sont simplement comparés à cette série pré-tirée une fois la partie terminée, et votre gain est calculé en fonction du nombre de numéros trouvés.

Code pour vérifier vous-même vos résultats Keno sur Cloudbet

Saisissez votre Server Seed, votre Client Seed et votre Nonce pour vérifier vos propres résultats. Installez l'unique dépendance requise et exécutez-la vous-même pour reconstituer le résultat de votre partie.

Remarque : pour révéler le Server Seed d'une partie terminée sur Cloudbet, vous devez déjà avoir démarré une nouvelle partie (ce qui fait passer à une nouvelle paire de seeds).

Dépendances :

npm install js-sha3

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