Dowodliwie uczciwe
Sprawdź sam każde rozdanie, każdy rzut i każde przydzielenie miejsc.
Nasze gry stołowe to gry umiejętności: seed decyduje tylko o tasowaniu, kościach i o tym, kto gdzie siedzi. Każdy ruch potem należy do ciebie. Zanim cokolwiek zostanie rozdane, serwer zobowiązuje się do tajnego seeda, publikując jego hash SHA-256. Twój własny seed klienta jest do niego domieszany, więc serwer nie może wybrać rozdania po jego zobaczeniu. Gdy seed zostanie ujawniony, każdy może przeliczyć każdą kartę, każdą kość i każdy kamień.
Jak to działa
- Zobowiązanie: serwer wybiera tajny seed (32 losowe bajty) i pokazuje ci jego hash SHA-256 przed pierwszą kartą lub pierwszym rzutem.
- Twój seed: twoja przeglądarka dodaje seed klienta (losowy lub zapisany przez ciebie). Jest wysyłany dopiero po pokazaniu hasha.
- Liczby: każda liczba losowa to HMAC-SHA256(seed serwera, wiadomość zawierająca wszystkie seedy klientów i licznik). Jej pierwsze 52 bity tworzą jedną liczbę.
- Ujawnienie: po grze seed serwera zostaje opublikowany. Jego hash SHA-256 musi być równy zobowiązaniu, a ponowne przeliczenie musi dać dokładnie te rozdania, które zostały rozegrane.
Od liczb do kart, kości i miejsc
Liczba 52-bitowa staje się liczbą całkowitą mniejszą od n dzięki próbkowaniu z odrzuceniem: liczba z górnego niepełnego bloku zakresu 2^52 jest pomijana i używany jest kolejny licznik, więc każdy wynik jest dokładnie jednakowo prawdopodobny. Tasowanie to Fisher–Yates od góry: dla i od ostatniej pozycji w dół do 1 wybiera się j mniejsze od i + 1 i zamienia oba miejscami.
Gra po grze
- Backgammon: rzut otwierający i obie kości każdej tury to int(6) + 1, w kolejności tur.
- Warcaby i Xiangqi: bez kości i bez kart. Seed wyznacza, kto zaczyna (kolejność miejsc).
- Tarneeb, Trix i Brydż: jedno tasowanie Fisher–Yates talii 52 kart na rozdanie; miejsce i otrzymuje karty od 13i do 13i + 12.
- Baloot: jedno tasowanie talii 32 kart na partię; reszta talii jest tasowana ponownie do drugiego rozdania.
- Mahjong: każdy dobrany kamień to int(pozostałe kamienie) ze ściany w jej stałej kolejności — tasowanie kamień po kamieniu.
- Poker: każdy stół działa w sesji seeda. Przed każdym rozdaniem stół zobowiązuje się do talii: korzeń Merkle obejmujący jej 52 pozycje, gdzie każda pozycja to karta wraz z własną solą. Gdy opuszczasz stół, otrzymujesz swoje własne karty, karty wspólne i ich dowody Merkle, aby sprawdzić je względem tego zobowiązania; karty innych graczy nie są ujawniane. Pełna talia i seed są publikowane przy zmianie sesji: co 24 godziny, po 500 rozdaniach, gdy stół się opróżni lub gdy serwer zostanie uruchomiony ponownie.
- Szachy: twoje połączenie ma zobowiązany seed, zanim zaczniesz szukać partii, rzucisz wyzwanie lub zaczniesz partię z botem. Kolory wynikają z seedów i seedów klientów obu graczy.
Partie rozegrane przed tym schematem (match_seed_v1) nadal można zweryfikować: weryfikator wybiera schemat na podstawie zapisu.
Weryfikuj
Wklej zapis partii (wypełnia go przycisk Weryfikuj w twoich grach). Każde rozdanie i każdy rzut są odtwarzane według zasad danej gry i porównywane z tym, co rozegrano.
Wpisz seedy partii, aby zobaczyć, co rozdają na początku.
Wpisz seedy rozdania, aby przeliczyć całą jego talię, albo wklej dowody kart otrzymane przy opuszczaniu stołu, aby sprawdzić swoje karty i karty wspólne.
Wpisz seedy obu stron, aby przeliczyć, kto gra białymi.
Strona A (autor szukania lub wyzwania)
Strona B
Sprawdź sam
// 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
Weryfikator na tej stronie działa w twojej przeglądarce i nie łączy się z serwerem. Pobierz go, aby uruchomić offline: verifier.js