Prokazatelně férové
Sami si ověřte každé rozdání, každý hod i každé rozesazení.
Naše stolní hry jsou hry dovednosti: seed rozhoduje jen o zamíchání, kostkách a o tom, kdo kde sedí. Každý další tah je váš. Než se cokoli rozdá, server se zaváže k tajnému seedu zveřejněním jeho hashe SHA-256. Váš vlastní klientský seed se do něj přimíchá, takže server nemůže zvolit rozdání poté, co ho uvidí. Jakmile je seed odhalen, kdokoli může přepočítat každou kartu, každou kostku i každý kámen.
Jak to funguje
- Závazek: server zvolí tajný seed (32 náhodných bajtů) a ukáže vám jeho hash SHA-256 před první kartou nebo prvním hodem.
- Váš seed: váš prohlížeč přidá klientský seed (náhodný nebo ten, který jste uložili). Odešle se až poté, co se zobrazí hash.
- Generování: každé náhodné číslo je HMAC-SHA256(seed serveru, zpráva se všemi klientskými seedy a čítačem). Jeho prvních 52 bitů tvoří jedno číslo.
- Odhalení: po hře se seed serveru zveřejní. Jeho hash SHA-256 se musí rovnat závazku a přepočet musí dát přesně ta rozdání, která se hrála.
Od čísel ke kartám, kostkám a místům
52bitové číslo se převede na celé číslo menší než n zamítacím vzorkováním: číslo v horním neúplném bloku rozsahu 2^52 se přeskočí a použije se další čítač, takže každý výsledek je přesně stejně pravděpodobný. Míchání je Fisher–Yates shora: pro i od poslední pozice dolů po 1 se zvolí j menší než i + 1 a obě se prohodí.
Hra po hře
- Vrhcáby: úvodní hod a obě kostky každého tahu jsou int(6) + 1, v pořadí tahů.
- Dáma a Xiangqi: žádné kostky ani karty. Seed určí, kdo začíná (pořadí míst).
- Tarneeb, Trix a Bridž: jedno zamíchání Fisher–Yates balíčku 52 karet na rozdání; místo i dostane karty 13i až 13i + 12.
- Baloot: jedno zamíchání balíčku 32 karet na kolo; zbytek balíčku se pro druhé rozdání zamíchá znovu.
- Mahjong: každý tažený kámen je int(zbývající kameny) ze zdi v jejím pevném pořadí — míchání po jednom kameni.
- Poker: každý stůl běží v seed relaci. Před každým rozdáním se stůl zaváže k balíčku: kořen Merkle přes jeho 52 pozic, kde každá pozice je karta a její vlastní sůl. Když od stolu odejdete, dostanete své vlastní karty, společné karty a jejich důkazy Merkle, takže je můžete ověřit proti tomuto závazku; karty ostatních hráčů se neodhalují. Celý balíček a seed se zveřejní, když se relace vystřídá: každých 24 hodin, po 500 kolech, když se stůl vyprázdní nebo když se server restartuje.
- Šachy: vaše připojení drží zavázaný seed dřív, než začnete hledat soupeře, někoho vyzvete nebo začnete hru proti botovi. Barvy vycházejí ze seedů a klientských seedů obou hráčů.
Zápasy odehrané před tímto schématem (match_seed_v1) zůstávají ověřitelné: ověřovač zvolí schéma podle záznamu.
Ověřit
Vložte záznam zápasu (vyplní ho tlačítko Ověřit ve vašich hrách). Každé rozdání a každý hod se přehraje podle pravidel dané hry a porovná s tím, co se hrálo.
Zadejte seedy zápasu a uvidíte, co na začátku rozdají.
Zadejte seedy kola a přepočítejte celý jeho balíček, nebo vložte důkazy karet, které jste dostali při odchodu od stolu, a ověřte své karty a společné karty.
Zadejte seedy obou stran a přepočítejte, kdo hraje bílými.
Strana A (autor hledání nebo výzvy)
Strana B
Ověřte si to sami
// 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
Ověřovač na této stránce běží ve vašem prohlížeči a nevolá žádný server. Stáhněte si ho a spusťte offline: verifier.js