Jeux personnalisés - Provably Fair
Comment il est utilisé dans les jeux personnalisés
Chaque pari sur un Yeet Original démarre à partir d'une « round seed » (graine de tour). La round seed est un HMAC-SHA256 combinant ta client seed et le nonce du pari, crypté avec une server seed choisie par YEET. Le jeu tire ensuite chaque valeur aléatoire nécessaire au pari depuis cette round seed. La server seed t'est montrée sous forme hachée avant de jouer et est révélée lorsque tu changes ta paire de seeds, afin que tu puisses recalculer la round seed de n'importe quel pari passé et vérifier que le résultat n'a pas été modifié après ta mise.
Les autres vérificateurs peuvent ne pas correspondre
Les sites de vérification conçus pour d'autres casinos peuvent générer des nombres aléatoires différemment, donc vérifier un pari YEET ailleurs peut donner un résultat différent. Vérifie tes paris YEET dans l'onglet Vérification de la fenêtre d'équité du jeu, ou avec le code sur cette page.
Génération aléatoire de nombres
Chaque pari est généré en deux étapes. D'abord, ta paire de seeds et le nonce du pari produisent une round seed. Ensuite, la round seed génère un flux d'octets aléatoires, et le jeu lit des nombres entiers non biaisés à partir de ce flux. Les mêmes entrées donnent toujours les mêmes nombres, ce qui rend le résultat vérifiable.
1. Les entrées
- Une chaîne hexadécimale aléatoire de 64 caractères générée par notre serveur
- Une chaîne que vous pouvez modifier à tout moment
- Un compteur qui augmente à chaque partie jouée
- Curseur — la dernière partie du message de la round seed. Il est toujours à 0.
2. La round seed
La round seed est un HMAC-SHA256 utilisant la server seed (en 32 octets) sur le texte clientSeed:nonce:0. Le résultat est de 32 octets, écrits en 64 caractères hexadécimaux. Un pari correspond exactement à une round seed.
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. Entropie de la round seed
La round seed sert de clé pour un second HMAC-SHA256. Chaque appel signe un compteur de 8 octets (little-endian, commençant à 0) et fournit un bloc de 32 octets ; le compteur augmente de un à chaque bloc. Les lectures prennent les octets du bloc actuel dans l'ordre. Lorsqu'une lecture nécessite plus d'octets que ce qu'il reste dans le bloc, le reste du bloc est ignoré et le bloc suivant est généré.
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. Nombres impartiaux
Les résultats sont tirés sous forme de nombres entiers. readUint32 lit 4 octets en tant qu'entier non signé de 32 bits (little-endian). unbiasedUint(entropy, n) renvoie un nombre compris entre 0 et n − 1, chacun ayant la même probabilité. Lorsque n est une puissance de deux, il prend directement le reste. Sinon, il n'accepte que les valeurs inférieures au plus grand multiple de n tenant sur 32 bits et relit une valeur si elle est égale ou supérieure, pour qu'aucun résultat ne soit favorisé.
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. Outils d'assistance
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. Vérifier un pari
- Ouvrez le pari depuis votre historique et copiez la graine client (client seed), le nonce et la graine serveur hachée (hashed server seed).
- Renouvelez votre paire de graines pour révéler la graine serveur, et vérifiez que son hachage SHA-256 correspond au hachage que vous avez vu avant le pari.
- Exécutez
deriveGameSeedavec la graine serveur, la graine client et le nonce pour obtenir la graine de la manche (round seed). - Créez une
GameEntropyà partir de la graine de la manche, appliquez la conversion du jeu (voir Conversions) et comparez le résultat avec votre pari.
