Équité prouvable

Vérifiez vous-même chaque donne, chaque lancer et chaque attribution des places.

Nos jeux de table sont des jeux d'adresse : le seed ne décide que du mélange, des dés et de la place de chacun. Chaque coup ensuite vous appartient. Avant toute distribution, le serveur s'engage sur un seed secret en publiant son hash SHA-256. Votre propre seed client y est mêlé, de sorte que le serveur ne peut pas choisir une donne après l'avoir vu. Une fois le seed révélé, chacun peut recalculer chaque carte, chaque dé et chaque tuile.

Vos parties et votre seed client

Comment ça marche

  1. Engagement : le serveur choisit un seed secret (32 octets aléatoires) et vous montre son hash SHA-256 avant la première carte ou le premier dé.
  2. Votre seed : votre navigateur ajoute un seed client (aléatoire, ou celui que vous avez enregistré). Il n'est envoyé qu'après l'affichage du hash.
  3. Tirage : chaque nombre aléatoire est HMAC-SHA256(seed serveur, un message contenant tous les seeds clients et un compteur). Ses 52 premiers bits forment un nombre.
  4. Révélation : après la partie, le seed serveur est publié. Son hash SHA-256 doit être égal à l'engagement, et le recalcul doit donner exactement les donnes jouées.

Des nombres aux cartes, aux dés et aux places

Un nombre de 52 bits devient un entier inférieur à n par échantillonnage par rejet : un nombre situé dans le dernier bloc incomplet de l'intervalle 2^52 est ignoré et le compteur suivant est utilisé, si bien que chaque résultat est exactement équiprobable. Un mélange est un Fisher–Yates depuis le haut : pour i de la dernière position jusqu'à 1, on choisit j inférieur à i + 1 et on échange les deux.

Jeu par jeu

  • Backgammon : le lancer d'ouverture et les deux dés de chaque tour sont int(6) + 1, dans l'ordre des tours.
  • Dames et Xiangqi : ni dés ni cartes. Le seed désigne qui joue en premier (l'ordre des places).
  • Tarneeb, Trix et Bridge : un mélange Fisher–Yates du jeu de 52 cartes par donne ; la place i reçoit les cartes 13i à 13i + 12.
  • Baloot : un mélange du jeu de 32 cartes par main ; le reste du jeu est mélangé à nouveau pour la seconde distribution.
  • Mahjong : chaque tuile piochée est int(tuiles restantes) prise dans le mur selon son ordre fixe, un mélange réalisé tuile par tuile.
  • Poker : chaque table utilise une session de seed. Avant chaque donne, la table s'engage sur le paquet : une racine de Merkle calculée sur ses 52 positions, chaque position étant une carte et son propre sel. Quand vous quittez la table, vous recevez vos cartes privées, les cartes communes et leurs preuves de Merkle, afin de les vérifier par rapport à cet engagement ; les cartes des autres joueurs ne sont pas révélées. Le paquet complet et le seed sont publiés lors de la rotation de la session : toutes les 24 heures, après 500 mains, quand la table se vide ou quand le serveur redémarre.
  • Échecs : votre connexion détient un seed engagé avant que vous cherchiez une partie, lanciez un défi ou commenciez une partie contre un bot. Les couleurs découlent des seeds et des seeds clients des deux joueurs.

Les parties jouées avant ce schéma (match_seed_v1) restent vérifiables : le vérificateur choisit le schéma d'après l'enregistrement.

Vérifier

Collez un relevé de partie (le bouton Vérifier de vos parties le remplit). Chaque donne et chaque lancer sont rejoués selon les règles du jeu et comparés à ce qui a été joué.

Vérifiez vous-même

// Node.js 18+ — check a table-game match yourself (scheme match_seed_v2)
const { createHash, createHmac } = require('crypto');
const sha256 = (s) => createHash('sha256').update(s).digest('hex');
const word = (key, msg) => BigInt('0x' + createHmac('sha256', key).update(msg).digest('hex').slice(0, 13));

console.log(sha256(serverSeed) === commitment);            // 1. the reveal matches the commitment

let k = 0;                                                  // 2. the draw stream: one counter per match
const seeds = clientSeeds.map((s, i) => i + '=' + s).join(',');
const next = () => word(serverSeed, 'pf2:' + game + ':' + matchId + ':' + seeds + ':' + (k++));
const SPAN = 1n << 52n;
function int(n) {                                           // 3. whole number below n, no modulo bias
  const N = BigInt(n), limit = SPAN - (SPAN % N);
  for (;;) { const w = next(); if (w < limit) return Number(w % N); }
}
const die = () => int(6) + 1;
function shuffle(a) {                                       // 4. Fisher–Yates from the top
  a = a.slice();
  for (let i = a.length - 1; i > 0; i--) { const j = int(i + 1); [a[i], a[j]] = [a[j], a[i]]; }
  return a;
}
// seat order (pairings): shuffle([0, 1]) first, then the game's opening deal / roll
// Poker (scheme poker_hmac_v1): hand n of seed session S at table T
const handValue = createHmac('sha256', serverSeed).update('pk1:hand:' + T + ':' + S + ':' + n).digest('hex');
console.log(sha256(handValue) === handCommitment);         // stored before the deal
// deck = shuffle([0..51]); key = handValue; message = pk1:T:S:n:{seat}={seed},…:{k}; seats ↑
// rank = id % 13 → 23456789TJQKA; suit = Math.floor(id / 13) → c d h s
// deck commitment (before the deal): salt_i = HMAC(handValue, 'pk1:salt:' + i); leaf_i = sha256('pk1:leaf:' + i + ':' + card_i + ':' + salt_i)
// 64 leaves (52..63 = sha256('pk1:pad')), parent = sha256('pk1:node:' + left + right) → root; on leave you get your leaves + paths
// Chess colour (scheme chess_colour_v1): A = owner of the seek / challenge, B = the other side (a bot adds no seed)
const m = (k) => 'cc1:' + commitA + ':' + (commitB || '-') + ':0=' + clientA + ',1=' + clientB + ':' + k;
// w = word(seedA, m(k)) XOR word(seedB, m(k)); int(2) over w as above → 0 means A plays White

Le vérificateur de cette page s'exécute dans votre navigateur et n'appelle aucun serveur. Téléchargez-le pour l'utiliser hors ligne : verifier.js