Equità dimostrabile

Verifica tu stesso ogni smazzata, ogni lancio e ogni assegnazione dei posti.

I nostri giochi da tavolo sono giochi di abilità: il seed decide solo il mescolamento, i dadi e chi siede dove. Ogni mossa successiva è tua. Prima di distribuire qualsiasi cosa, il server si impegna su un seed segreto pubblicandone l'hash SHA-256. Il tuo seed client viene mescolato al suo, così il server non può scegliere una smazzata dopo averlo visto. Una volta rivelato il seed, chiunque può ricalcolare ogni carta, ogni dado e ogni tessera.

Le tue partite e il tuo seed client

Come funziona

  1. Impegno: il server sceglie un seed segreto (32 byte casuali) e ti mostra il suo hash SHA-256 prima della prima carta o del primo dado.
  2. Il tuo seed: il tuo browser aggiunge un seed client (casuale o quello che hai salvato). Viene inviato solo dopo che l'hash è stato mostrato.
  3. Estrazione: ogni numero casuale è HMAC-SHA256(seed del server, un messaggio con tutti i seed client e un contatore). I suoi primi 52 bit formano un numero.
  4. Rivelazione: dopo la partita il seed del server viene pubblicato. Il suo hash SHA-256 deve corrispondere all'impegno e il ricalcolo deve restituire esattamente le smazzate giocate.

Dai numeri a carte, dadi e posti

Un numero a 52 bit diventa un intero minore di n tramite campionamento per rifiuto: un numero che cade nel blocco parziale superiore dell'intervallo 2^52 viene scartato e si usa il contatore successivo, così ogni risultato è esattamente equiprobabile. Un mescolamento è Fisher–Yates dall'alto: per i dall'ultima posizione fino a 1, si sceglie j minore di i + 1 e si scambiano i due.

Gioco per gioco

  • Backgammon: il lancio d'apertura ed entrambi i dadi di ogni turno sono int(6) + 1, in ordine di turno.
  • Dama e Xiangqi: niente dadi e niente carte. Il seed stabilisce chi muove per primo (l'ordine dei posti).
  • Tarneeb, Trix e Bridge: un mescolamento Fisher–Yates del mazzo da 52 carte per smazzata; il posto i riceve le carte da 13i a 13i + 12.
  • Baloot: un mescolamento del mazzo da 32 carte per mano; il resto del mazzo viene rimescolato per la seconda distribuzione.
  • Mahjong: ogni tessera pescata è int(tessere rimaste) dal muro nel suo ordine fisso, un mescolamento fatto una tessera alla volta.
  • Poker: ogni tavolo usa una sessione di seed. Prima di ogni smazzata il tavolo si impegna sul mazzo: una radice di Merkle sulle sue 52 posizioni, dove ogni posizione è una carta più il suo sale. Quando lasci il tavolo ricevi le tue carte private, le carte comuni e le relative prove di Merkle, così puoi verificarle rispetto a quell'impegno; le carte degli altri giocatori non vengono rivelate. Il mazzo completo e il seed vengono pubblicati quando la sessione ruota: ogni 24 ore, dopo 500 mani, quando il tavolo si svuota o quando il server si riavvia.
  • Scacchi: la tua connessione detiene un seed impegnato prima che tu cerchi una partita, lanci una sfida o inizi una partita contro un bot. I colori derivano dai seed e dai seed client di entrambi i giocatori.

Le partite giocate prima di questo schema (match_seed_v1) restano verificabili: il verificatore sceglie lo schema in base al registro.

Verifica

Incolla un registro di partita (il pulsante Verifica nelle tue partite lo compila). Ogni smazzata e ogni lancio vengono rigiocati con le regole del gioco e confrontati con quanto giocato.

Verifica tu stesso

// 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

Il verificatore di questa pagina gira nel tuo browser e non effettua chiamate al server. Scaricalo per eseguirlo offline: verifier.js