Bevisbart rättvist

Kontrollera själv varje giv, varje kast och varje platsfördelning.

Våra bordsspel är skicklighetsspel: seeden avgör bara blandningen, tärningarna och vem som sitter var. Varje drag efter det är ditt. Innan något delas ut binder sig servern vid en hemlig seed genom att publicera dess SHA-256-hash. Din egen klientseed blandas in, så servern kan inte välja en giv efter att ha sett den. När seeden har avslöjats kan vem som helst räkna om varje kort, varje tärning och varje bricka.

Dina spel och din klientseed

Så fungerar det

  1. Åtagande: servern väljer en hemlig seed (32 slumpmässiga byte) och visar dig dess SHA-256-hash före första kortet eller tärningen.
  2. Din seed: din webbläsare lägger till en klientseed (slumpmässig eller den du har sparat). Den skickas först när hashen har visats.
  3. Dragning: varje slumptal är HMAC-SHA256(serverseed, ett meddelande med alla klientseeds och en räknare). Dess första 52 bitar blir ett tal.
  4. Avslöjande: efter spelet publiceras serverseeden. Dess SHA-256-hash måste vara lika med åtagandet, och omräkningen måste ge exakt de givar som spelades.

Från tal till kort, tärningar och platser

Ett 52-bitarstal blir ett heltal under n genom förkastningssampling: ett tal i det översta ofullständiga blocket av intervallet 2^52 hoppas över och nästa räknare används, så att varje resultat är exakt lika sannolikt. En blandning är Fisher–Yates uppifrån: för i från sista positionen ned till 1 väljs j under i + 1 och de två byter plats.

Spel för spel

  • Backgammon: öppningskastet och båda tärningarna i varje drag är int(6) + 1, i dragordning.
  • Dam och Xiangqi: inga tärningar och inga kort. Seeden avgör vem som börjar (platsordningen).
  • Tarneeb, Trix och Bridge: en Fisher–Yates-blandning av kortleken med 52 kort per giv; plats i får korten 13i till 13i + 12.
  • Baloot: en blandning av kortleken med 32 kort per hand; resten av leken blandas om för den andra given.
  • Mahjong: varje dragen bricka är int(brickor kvar) från muren i dess fasta ordning, en blandning som görs en bricka i taget.
  • Poker: varje bord kör en seedsession. Före varje giv binder sig bordet till kortleken: en Merkle-rot över dess 52 positioner, där varje position är ett kort plus ett eget salt. När du lämnar bordet får du dina egna hålkort, de gemensamma korten och deras Merkle-bevis, så att du kan kontrollera dem mot det åtagandet; inga andra spelares kort avslöjas. Hela kortleken och seeden publiceras när sessionen byts: var 24:e timme, efter 500 händer, när bordet blir tomt eller när servern startas om.
  • Schack: din anslutning håller en åtagen seed innan du söker parti, utmanar någon eller startar ett parti mot en bot. Färgerna kommer från båda spelarnas seeds och klientseeds.

Partier som spelades före detta schema (match_seed_v1) går fortfarande att verifiera: verifieraren väljer schema utifrån protokollet.

Verifiera

Klistra in ett partiprotokoll (knappen Verifiera i dina spel fyller i det). Varje giv och varje kast spelas om enligt spelets egna regler och jämförs med det som spelades.

Kontrollera själv

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

Verifieraren på den här sidan körs i din webbläsare och anropar ingen server. Ladda ned den för att köra den offline: verifier.js