Доказуемо честно
Проверете сами всяко раздаване, всяко хвърляне и всяко разпределение на местата.
Нашите игри на маса са игри на умение: сийдът решава само разбъркването, заровете и кой къде седи. Всеки ход след това е ваш. Преди да се раздаде каквото и да е, сървърът се обвързва с таен сийд, като публикува неговия хеш 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
