Corectitudine demonstrabilă

Verifică singur fiecare împărțire, fiecare aruncare și fiecare atribuire a locurilor.

Jocurile noastre de masă sunt jocuri de îndemânare: seed-ul decide doar amestecarea, zarurile și cine unde stă. Fiecare mutare de după aceea îți aparține. Înainte să se împartă ceva, serverul se angajează la un seed secret publicându-i hash-ul SHA-256. Propriul tău seed client este amestecat în el, așa că serverul nu poate alege o împărțire după ce l-a văzut. Odată ce seed-ul este dezvăluit, oricine poate recalcula fiecare carte, fiecare zar și fiecare piesă.

Jocurile tale și seed-ul client

Cum funcționează

  1. Angajament: serverul alege un seed secret (32 de octeți aleatori) și îți arată hash-ul său SHA-256 înainte de prima carte sau de primul zar.
  2. Seed-ul tău: browserul tău adaugă un seed client (aleator sau cel salvat de tine). Este trimis doar după ce hash-ul a fost afișat.
  3. Extragere: fiecare număr aleator este HMAC-SHA256(seed-ul serverului, un mesaj cu toate seed-urile client și un contor). Primii săi 52 de biți formează un număr.
  4. Dezvăluire: după joc, seed-ul serverului este publicat. Hash-ul său SHA-256 trebuie să fie egal cu angajamentul, iar recalcularea trebuie să dea exact împărțirile jucate.

De la numere la cărți, zaruri și locuri

Un număr de 52 de biți devine un întreg mai mic decât n prin eșantionare cu respingere: un număr din blocul parțial superior al intervalului 2^52 este sărit și se folosește contorul următor, astfel încât fiecare rezultat este exact la fel de probabil. Amestecarea este Fisher–Yates de sus: pentru i de la ultima poziție până la 1, se alege j mai mic decât i + 1 și cele două se schimbă între ele.

Joc cu joc

  • Table: aruncarea de deschidere și ambele zaruri ale fiecărei mutări sunt int(6) + 1, în ordinea mutărilor.
  • Dame și Xiangqi: fără zaruri și fără cărți. Seed-ul stabilește cine începe (ordinea locurilor).
  • Tarneeb, Trix și Bridge: o amestecare Fisher–Yates a pachetului de 52 de cărți la fiecare împărțire; locul i primește cărțile de la 13i la 13i + 12.
  • Baloot: o amestecare a pachetului de 32 de cărți la fiecare mână; restul pachetului este amestecat din nou pentru a doua împărțire.
  • Mahjong: fiecare piesă trasă este int(piese rămase) din zid în ordinea sa fixă, o amestecare făcută piesă cu piesă.
  • Poker: fiecare masă rulează o sesiune de seed. Înainte de fiecare împărțire, masa se angajează asupra pachetului: o rădăcină Merkle peste cele 52 de poziții ale sale, fiecare poziție fiind o carte plus propria sare. Când părăsești masa, primești propriile cărți, cărțile comune și dovezile lor Merkle, ca să le poți verifica față de acest angajament; cărțile celorlalți jucători nu sunt dezvăluite. Pachetul complet și seed-ul sunt publicate când sesiunea se schimbă: la fiecare 24 de ore, după 500 de mâini, când masa se golește sau când serverul repornește.
  • Șah: conexiunea ta deține un seed angajat înainte să cauți un joc, să lansezi o provocare sau să începi un joc împotriva unui bot. Culorile provin din seed-urile și seed-urile client ale ambilor jucători.

Meciurile jucate înainte de această schemă (match_seed_v1) rămân verificabile: verificatorul alege schema după înregistrare.

Verifică

Lipește o înregistrare de meci (butonul Verifică din jocurile tale o completează). Fiecare împărțire și fiecare aruncare sunt rejucate după regulile jocului și comparate cu ce s-a jucat.

Verifică singur

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

Verificatorul de pe această pagină rulează în browserul tău și nu face niciun apel la server. Descarcă-l pentru a-l rula offline: verifier.js