Доведена чесність
Перевірте самі кожну роздачу, кожен кидок і кожне розсаджування.
Наші настільні ігри — це ігри майстерності: сід визначає лише тасування, кубики та те, хто де сидить. Кожен хід після цього — ваш. Перш ніж щось роздати, сервер фіксує секретний сід, публікуючи його хеш SHA-256. До нього домішується ваш власний клієнтський сід, тож сервер не може обрати роздачу, побачивши його. Щойно сід розкрито, будь-хто може перерахувати кожну карту, кожен кубик і кожну плитку.
Як це працює
- Фіксація: сервер обирає секретний сід (32 випадкові байти) і показує вам його хеш SHA-256 до першої карти чи першого кидка.
- Ваш сід: ваш браузер додає клієнтський сід (випадковий або збережений вами). Його надсилають лише після того, як показано хеш.
- Генерація: кожне випадкове число — це HMAC-SHA256(сід сервера, повідомлення з усіма клієнтськими сідами та лічильником). Його перші 52 біти утворюють одне число.
- Розкриття: після гри сід сервера публікується. Його хеш SHA-256 має збігатися з фіксацією, а перерахунок має дати саме ті роздачі, що були зіграні.
Від чисел до карт, кубиків і місць
52-бітне число перетворюється на ціле число, менше за n, вибіркою з відхиленням: число з верхнього неповного блоку діапазону 2^52 пропускається і береться наступний лічильник, тож усі результати точно рівноймовірні. Тасування виконується за Fisher–Yates згори: для i від останньої позиції вниз до 1 обирається j, менше за i + 1, і два елементи міняються місцями.
Гра за грою
- Нарди: перший кидок і обидва кубики кожного ходу — це int(6) + 1, у порядку ходів.
- Шашки та Xiangqi: жодних кубиків і карт. Сід визначає, хто ходить першим (порядок місць).
- Tarneeb, Trix і Бридж: одне тасування Fisher–Yates колоди з 52 карт на роздачу; місце i отримує карти з 13i по 13i + 12.
- Baloot: одне тасування колоди з 32 карт на партію; решту колоди тасують знову для другої роздачі.
- Маджонг: кожна взята плитка — це int(залишилося плиток) зі стіни в її фіксованому порядку, тобто тасування по одній плитці.
- Покер: кожен стіл працює в сід-сесії. Перед кожною роздачею стіл фіксує колоду: корінь Merkle за її 52 позиціями, де кожна позиція — це карта та її власна сіль. Коли ви залишаєте стіл, ви отримуєте свої кишенькові карти, спільні карти та їхні докази Merkle, щоб звірити їх із цією фіксацією; карти інших гравців не розкриваються. Повна колода та сід публікуються під час зміни сесії: кожні 24 години, після 500 роздач, коли стіл порожніє або коли сервер перезапускається.
- Шахи: ваше з'єднання тримає зафіксований сід ще до того, як ви почнете пошук, кинете виклик або почнете партію з ботом. Кольори визначаються сідами та клієнтськими сідами обох гравців.
Матчі, зіграні до цієї схеми (match_seed_v1), лишаються перевірюваними: верифікатор обирає схему за записом.
Перевірити
Вставте запис матчу (його заповнює кнопка «Перевірити» у ваших іграх). Кожну роздачу й кожен кидок відтворюють за правилами самої гри та порівнюють із тим, що було зіграно.
Введіть сіди матчу, щоб побачити, що вони роздають на початку.
Введіть сіди роздачі, щоб перерахувати всю її колоду, або вставте докази карт, отримані, коли ви залишили стіл, щоб перевірити свої карти та спільні карти.
Введіть сіди обох сторін, щоб перерахувати, хто грає білими.
Сторона A (автор пошуку чи виклику)
Сторона B
Перевірте самі
// 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
Верифікатор на цій сторінці працює у вашому браузері й не звертається до сервера. Завантажте його, щоб запускати офлайн: verifier.js