Demostrablemente justo
Comprueba tú mismo cada reparto, cada tirada y cada asignación de asientos.
Nuestros juegos de mesa son juegos de habilidad: la semilla solo decide el barajado, los dados y quién se sienta dónde. Cada jugada posterior es tuya. Antes de repartir nada, el servidor se compromete con una semilla secreta publicando su hash SHA-256. Tu propia semilla de cliente se mezcla con ella, así que el servidor no puede elegir un reparto después de verla. Una vez revelada la semilla, cualquiera puede recalcular cada carta, cada dado y cada ficha.
Tus partidas y tu semilla de cliente
Cómo funciona
- Compromiso: el servidor elige una semilla secreta (32 bytes aleatorios) y te muestra su hash SHA-256 antes de la primera carta o el primer dado.
- Tu semilla: tu navegador añade una semilla de cliente (aleatoria o la que guardaste). Solo se envía después de mostrarse el hash.
- Extracción: cada número aleatorio es HMAC-SHA256(semilla del servidor, un mensaje con todas las semillas de cliente y un contador). Sus primeros 52 bits forman un número.
- Revelación: tras la partida se publica la semilla del servidor. Su hash SHA-256 debe ser igual al compromiso, y el recálculo debe dar exactamente los repartos que se jugaron.
De números a cartas, dados y asientos
Un número de 52 bits se convierte en un entero menor que n mediante muestreo por rechazo: un número que cae en el bloque parcial superior del rango 2^52 se descarta y se usa el siguiente contador, de modo que cada resultado es exactamente equiprobable. Un barajado es Fisher–Yates desde arriba: para i desde la última posición hasta 1, se elige j menor que i + 1 y se intercambian ambos.
Juego a juego
- Backgammon: la tirada inicial y los dos dados de cada turno son int(6) + 1, en orden de turno.
- Damas y Xiangqi: sin dados ni cartas. La semilla determina quién juega primero (el orden de asientos).
- Tarneeb, Trix y Bridge: un barajado Fisher–Yates de la baraja de 52 cartas por reparto; el asiento i recibe las cartas 13i a 13i + 12.
- Baloot: un barajado de la baraja de 32 cartas por mano; el resto de la baraja se vuelve a barajar para el segundo reparto.
- Mahjong: cada ficha robada es int(fichas restantes) del muro en su orden fijo, un barajado hecho ficha a ficha.
- Póquer: cada mesa funciona con una sesión de semilla. Antes de cada reparto, la mesa se compromete con la baraja: una raíz de Merkle sobre sus 52 posiciones, donde cada posición es una carta más su propia sal. Al dejar la mesa recibes tus propias cartas, las cartas comunitarias y sus pruebas de Merkle, para que puedas comprobarlas con ese compromiso; no se revelan las cartas de ningún otro jugador. La baraja completa y la semilla se publican cuando la sesión rota: cada 24 horas, tras 500 manos, cuando la mesa se vacía o cuando el servidor se reinicia.
- Ajedrez: tu conexión tiene una semilla comprometida antes de que busques partida, lances un desafío o empieces una partida contra un bot. Los colores salen de las semillas y las semillas de cliente de ambos jugadores.
Las partidas jugadas antes de este esquema (match_seed_v1) siguen siendo verificables: el verificador elige el esquema según el registro.
Verificar
Pega un registro de partida (el botón Verificar de tus partidas lo rellena). Cada reparto y cada tirada se reproducen con las reglas del propio juego y se comparan con lo que se jugó.
Introduce las semillas de una partida para ver qué reparten al inicio.
Introduce las semillas de una mano para recalcular toda su baraja, o pega las pruebas de cartas que recibiste al dejar la mesa para comprobar tus cartas y las cartas comunitarias.
Introduce las semillas de ambos lados para recalcular quién juega con blancas.
Lado A (creador de la búsqueda o del desafío)
Lado B
Compruébalo tú mismo
// 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
El verificador de esta página se ejecuta en tu navegador y no hace ninguna llamada al servidor. Descárgalo para usarlo sin conexión: verifier.js