Nachweislich fair

Prüfe jedes Austeilen, jeden Wurf und jede Sitzplatzvergabe selbst.

Unsere Tischspiele sind Geschicklichkeitsspiele: Der Seed entscheidet nur über das Mischen, die Würfel und die Sitzordnung. Jeder Zug danach liegt bei dir. Bevor etwas ausgeteilt wird, legt sich der Server auf einen geheimen Seed fest, indem er dessen SHA-256-Hash veröffentlicht. Dein eigener Client-Seed wird eingemischt, sodass der Server kein Blatt wählen kann, nachdem er ihn gesehen hat. Sobald der Seed offengelegt ist, kann jeder jede Karte, jeden Würfel und jeden Stein nachrechnen.

Deine Spiele und dein Client-Seed

So funktioniert es

  1. Festlegung: Der Server wählt einen geheimen Seed (32 zufällige Bytes) und zeigt dir dessen SHA-256-Hash vor der ersten Karte bzw. dem ersten Wurf.
  2. Dein Seed: Dein Browser fügt einen Client-Seed hinzu (zufällig oder deinen gespeicherten). Er wird erst gesendet, nachdem der Hash angezeigt wurde.
  3. Ziehung: Jede Zufallszahl ist HMAC-SHA256(Server-Seed, eine Nachricht mit allen Client-Seeds und einem Zähler). Ihre ersten 52 Bits ergeben eine Zahl.
  4. Offenlegung: Nach dem Spiel wird der Server-Seed veröffentlicht. Sein SHA-256-Hash muss der Festlegung entsprechen, und die Nachrechnung muss genau die gespielten Blätter ergeben.

Von Zahlen zu Karten, Würfeln und Plätzen

Eine 52-Bit-Zahl wird per Verwerfungsmethode zu einer ganzen Zahl unter n: Fällt eine Zahl in den oberen unvollständigen Block des Bereichs 2^52, wird sie übersprungen und der nächste Zähler verwendet, sodass jedes Ergebnis exakt gleich wahrscheinlich ist. Gemischt wird mit Fisher–Yates von oben: Für i von der letzten Position abwärts bis 1 wird j unter i + 1 gewählt, und beide werden getauscht.

Spiel für Spiel

  • Backgammon: Der Eröffnungswurf und beide Würfel jedes Zuges sind int(6) + 1, in Zugreihenfolge.
  • Dame und Xiangqi: keine Würfel und keine Karten. Der Seed bestimmt, wer beginnt (die Sitzordnung).
  • Tarneeb, Trix und Bridge: ein Fisher–Yates-Mischen des Decks mit 52 Karten pro Austeilen; Platz i erhält die Karten 13i bis 13i + 12.
  • Baloot: ein Mischen des Decks mit 32 Karten pro Hand; der Rest des Decks wird für das zweite Austeilen erneut gemischt.
  • Mahjong: Jeder gezogene Stein ist int(verbleibende Steine) aus der Mauer in ihrer festen Reihenfolge – ein Mischen Stein für Stein.
  • Poker: Jeder Tisch führt eine Seed-Sitzung. Vor jedem Austeilen legt sich der Tisch auf das Deck fest: eine Merkle-Wurzel über seine 52 Positionen, wobei jede Position eine Karte plus ein eigenes Salt ist. Wenn du den Tisch verlässt, erhältst du deine eigenen Handkarten, das Board und deren Merkle-Nachweise, sodass du sie mit dieser Festlegung abgleichen kannst; die Karten anderer Spieler werden nicht offengelegt. Das vollständige Deck und der Seed werden veröffentlicht, wenn die Sitzung wechselt: alle 24 Stunden, nach 500 Händen, wenn der Tisch leer wird oder wenn der Server neu startet.
  • Schach: Deine Verbindung hält einen festgelegten Seed, bevor du eine Partie suchst, jemanden herausforderst oder eine Partie gegen einen Bot startest. Die Farben ergeben sich aus den Seeds und Client-Seeds beider Spieler.

Partien, die vor diesem Verfahren gespielt wurden (match_seed_v1), bleiben überprüfbar: Der Prüfer wählt das Verfahren anhand des Protokolls.

Prüfen

Füge ein Partieprotokoll ein (die Schaltfläche „Prüfen“ in deinen Spielen füllt es aus). Jedes Austeilen und jeder Wurf wird nach den Regeln des Spiels nachgespielt und mit dem Gespielten verglichen.

Selbst prüfen

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

Der Prüfer auf dieser Seite läuft in deinem Browser und ruft keinen Server auf. Lade ihn herunter, um ihn offline auszuführen: verifier.js