Provably fair

Check every deal, roll and seat draw yourself.

Our table games are games of skill: the seed only decides the shuffle, the dice and who sits where. Every move after that is yours. Before anything is dealt, the server commits to a secret seed by publishing its SHA-256 hash. Your own client seed is mixed in, so the server cannot pick a deal after seeing it. Once the seed is revealed, anyone can recompute every card, die and tile.

Your games and client seed

How it works

  1. Commit: the server picks a secret seed (32 random bytes) and shows you its SHA-256 hash before the first card or die.
  2. Your seed: your browser adds a client seed (random, or the one you saved). It is sent only after the hash is shown.
  3. Draw: every random number is HMAC-SHA256(server seed, a message holding all client seeds and a counter). Its first 52 bits are one number.
  4. Reveal: after the game the server seed is published. Its SHA-256 hash must equal the commitment, and recomputing must give exactly the deals that were played.

From numbers to cards, dice and seats

A 52-bit number becomes a whole number below n by rejection sampling: a number in the top partial block of the 2^52 range is skipped and the next counter is used, so every result is exactly equally likely. A shuffle is Fisher–Yates from the top: for i from the last position down to 1, pick j below i + 1 and swap the two.

Game by game

  • Backgammon: the opening roll and both dice of every turn are int(6) + 1, in turn order.
  • Draughts and Xiangqi: no dice and no cards. The seed draws who plays first (the seat order).
  • Tarneeb, Trix and Bridge: one Fisher–Yates shuffle of the 52-card deck per deal; seat i receives cards 13i to 13i + 12.
  • Baloot: one shuffle of the 32-card deck per hand; the rest of the deck is shuffled again for the second deal.
  • Mahjong: each tile drawn is int(tiles left) from the wall in its fixed order, a shuffle done one tile at a time.
  • Poker: each table runs a seed session. Before every deal the table commits to the deck: a Merkle root over its 52 positions, each position being a card plus its own salt. When you leave the table you receive your own hole cards, the board and their Merkle proofs, so you can check them against that commitment; no other player's cards are revealed. The full deck and the seed are published when the session rotates: every 24 hours, after 500 hands, when the table empties or when the server restarts.
  • Chess: your connection holds a committed seed before you seek, challenge or start a game against a bot. The colours come from both players' seeds and client seeds.

Matches played before this scheme (match_seed_v1) stay verifiable: the verifier picks the scheme from the record.

Verify

Paste a match record (the Verify button in your games fills it in). Every deal and roll is replayed with the game's own rules and compared with what was played.

Check it yourself

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

The verifier on this page runs in your browser and makes no server call. Download it to run it offline: verifier.js