Доведена чесність

Перевірте самі кожну роздачу, кожен кидок і кожне розсаджування.

Наші настільні ігри — це ігри майстерності: сід визначає лише тасування, кубики та те, хто де сидить. Кожен хід після цього — ваш. Перш ніж щось роздати, сервер фіксує секретний сід, публікуючи його хеш SHA-256. До нього домішується ваш власний клієнтський сід, тож сервер не може обрати роздачу, побачивши його. Щойно сід розкрито, будь-хто може перерахувати кожну карту, кожен кубик і кожну плитку.

Ваші ігри та клієнтський сід

Як це працює

  1. Фіксація: сервер обирає секретний сід (32 випадкові байти) і показує вам його хеш SHA-256 до першої карти чи першого кидка.
  2. Ваш сід: ваш браузер додає клієнтський сід (випадковий або збережений вами). Його надсилають лише після того, як показано хеш.
  3. Генерація: кожне випадкове число — це HMAC-SHA256(сід сервера, повідомлення з усіма клієнтськими сідами та лічильником). Його перші 52 біти утворюють одне число.
  4. Розкриття: після гри сід сервера публікується. Його хеш 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), лишаються перевірюваними: верифікатор обирає схему за записом.

Перевірити

Вставте запис матчу (його заповнює кнопка «Перевірити» у ваших іграх). Кожну роздачу й кожен кидок відтворюють за правилами самої гри та порівнюють із тим, що було зіграно.

Перевірте самі

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