Juegos personalizados - Probablemente justo
Cómo se usa en los juegos personalizados
Cada apuesta en un YEET Original parte de una semilla de ronda (round seed). Esta semilla de ronda es un HMAC-SHA256 de tu semilla de cliente y el nonce de la apuesta, cifrado con una semilla de servidor elegida por YEET. El juego obtiene entonces cada valor aleatorio que necesita para esa apuesta a partir de dicha semilla de ronda. La semilla de servidor se te muestra en formato hash antes de jugar y se revela cuando rotas tu par de semillas, de modo que puedes volver a calcular la semilla de ronda de cualquier apuesta pasada y comprobar que su resultado no se alteró después de apostar.
Es posible que otros verificadores no coincidan
Los sitios de verificación creados para otros casinos pueden generar números aleatorios de forma distinta, por lo que comprobar una apuesta de YEET allí puede dar un resultado diferente. Verifica tus apuestas de YEET en la pestaña de Verificación de la ventana de Transparencia (Fairness) del juego, o con el código de esta página.
Generación de números aleatorios
Cada apuesta se genera en dos pasos. Primero, tu par de semillas y el nonce de la apuesta generan una semilla de ronda. Segundo, la semilla de ronda produce un flujo de bytes aleatorios, y el juego lee números enteros imparciales de ese flujo. Las mismas entradas siempre dan los mismos números, que es lo que hace que un resultado sea verificable.
1. Las entradas
- Una cadena hexadecimal aleatoria de 64 caracteres que genera nuestro servidor
- Una cadena que puedes modificar en cualquier momento
- Un contador que aumenta con cada partida que juegas
- Cursor — la última parte del mensaje de la semilla de ronda. Siempre es 0.
2. La semilla de ronda
La semilla de ronda es un HMAC-SHA256 cifrado con la semilla de servidor (como 32 bytes) sobre el texto clientSeed:nonce:0. El resultado son 32 bytes, escritos como 64 caracteres hexadecimales. Una apuesta tiene exactamente una semilla de ronda.
async function deriveGameSeed(serverSeed, clientSeed, nonce) {
const seedBytes = hexToBytes(serverSeed);
if (seedBytes.length !== 32) {
throw new Error(`Server seed must be 32 bytes (64 hex characters), got ${seedBytes.length}.`);
}
const key = await crypto.subtle.importKey(
'raw', toArrayBuffer(seedBytes), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
const message = new TextEncoder().encode(`${clientSeed}:${nonce}:0`);
return bytesToHex(new Uint8Array(await crypto.subtle.sign('HMAC', key, message)));
}3. Entropía de la semilla de ronda
La semilla de ronda es la clave de un segundo HMAC-SHA256. Cada llamada firma un contador de 8 bytes (little-endian, empezando en 0) y genera un bloque de 32 bytes; el contador aumenta en uno por cada bloque. Las lecturas toman los bytes del bloque actual en orden. Cuando una lectura necesita más bytes de los que quedan en el bloque, se ignora el resto del bloque y se genera el siguiente.
class GameEntropy {
constructor(seedBytes) {
if (seedBytes.length !== 32) throw new Error('Seed must be exactly 32 bytes (64 hex characters).');
this.buffer = new Uint8Array(32);
this.offset = 32;
this.cursor = 0;
this.key = crypto.subtle.importKey(
'raw', toArrayBuffer(seedBytes), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
);
}
async next(count) {
if (count < 1 || count > 32) throw new RangeError('count must be between 1 and 32');
if (this.offset + count > 32) {
const message = new ArrayBuffer(8);
const view = new DataView(message);
view.setUint32(0, this.cursor >>> 0, true);
view.setUint32(4, Math.floor(this.cursor / 0x100000000) >>> 0, true);
const hash = await crypto.subtle.sign('HMAC', await this.key, message);
this.buffer.set(new Uint8Array(hash));
this.cursor++;
this.offset = 0;
}
const slice = this.buffer.slice(this.offset, this.offset + count);
this.offset += count;
return slice;
}
}4. Números imparciales
Los resultados se obtienen como números enteros. readUint32 lee 4 bytes como un entero de 32 bits sin signo (little-endian). unbiasedUint(entropy, n) devuelve un número del 0 al n − 1, cada uno con la misma probabilidad. Cuando n es una potencia de dos, toma el resto directamente. De lo contrario, solo acepta valores por debajo del mayor múltiplo de n que cabe en 32 bits y vuelve a leer cuando un valor es igual o superior, por lo que ningún resultado se ve favorecido.
async function readUint32(entropy) {
const bytes = await entropy.next(4);
return new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0, true);
}
async function unbiasedUint(entropy, range) {
if (range <= 0) throw new Error('Range must be positive');
if (range === 1) return 0;
if ((range & (range - 1)) === 0) {
return (await readUint32(entropy)) % range;
}
const maxValue = 0xffffffff;
const threshold = maxValue - (maxValue % range) - 1;
for (;;) {
const raw = await readUint32(entropy);
if (raw <= threshold) return raw % range;
}
}5. Ayudantes
function hexToBytes(hex) {
if (typeof hex !== 'string') throw new TypeError('Hex value must be a string.');
const clean = hex.trim().replace(/^0x/i, '');
if (clean.length % 2 !== 0) throw new Error('Hex value must have an even length.');
if (!/^[0-9a-fA-F]*$/.test(clean)) throw new Error('Hex value contains non-hex characters.');
const bytes = new Uint8Array(clean.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
function bytesToHex(bytes) {
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
}
function toArrayBuffer(bytes) {
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}6. Verificación de una apuesta
- Abre la apuesta desde tu historial y copia la semilla del cliente, el nonce y la semilla del servidor cifrada.
- Rota tu par de semillas para revelar la semilla del servidor y verifica que su hash SHA-256 coincida con el hash que viste antes de la apuesta.
- Ejecuta
deriveGameSeedcon la semilla del servidor, la semilla del cliente y el nonce para obtener la semilla de la ronda. - Crea una
GameEntropya partir de la semilla de la ronda, aplica la conversión del juego (consulta Conversiones) y compara el resultado con tu apuesta.
