Provably fair-spel löser ett problem som har funnits hos onlinecasinon sedan starten: Hur kan en spelare lita på att ett spelresultat inte har manipulerats i efterhand? Svaret är samma verktyg som kryptografer använder när två parter behöver enas om något utan att lita på varandra — ett commit–reveal-system.
Här följer en fullständig genomgång av den process som Cloudbet använder för Mines — från generering av seeds till placering av minor — samt hur alla spelare med grundläggande programmeringskunskaper självständigt kan verifiera samtliga resultat från sina tidigare omgångar.
Den här artikeln är den första i en planerad serie tekniska genomgångar av de provably fair-mekanismer som används i Cloudbet Originals. Om du själv vill verifiera en omgång (även i Mines) kan du använda Cloudbets Provably Fair-kalkylator.
Contents
Resultatet låses innan spelet börjar
Före varje omgång genererar servern ett slumpmässigt server seed på 32 byte och publicerar omedelbart dess SHA3-256-hash — ett commitment-värde. Hashen fungerar som ett enkelriktat fingeravtryck: den bevisar att seed-värdet fanns vid den tidpunkten utan att avslöja själva värdet.
När omgången är över offentliggör Cloudbet det ursprungliga server seed-värdet. Alla kan hasha värdet och bekräfta att det matchar commitment-värdet som publicerades före spelstart. Det bevisar att seed-värdet — och därmed minornas placering — var fastställt innan den första rutan valdes. Casinot har alltså ingen möjlighet att i efterhand byta ut det mot ett mer fördelaktigt värde.
Spelarens entropi blandas in
Om endast ett server seed användes skulle spelarna vara helt beroende av casinots slumpgenerering. Därför läggs ytterligare två indata till: ett client seed (som spelaren anger) och ett nonce-värde (ett heltal som ökar för varje spelomgång).
Alla tre sammanfogas och hashas med SHA3-256 för att skapa omgångssignaturen:
roundSignature = SHA3-256(`${serverSeed}:${clientSeed}:${nonce}`)
Client seed-värdet gör att casinot inte kan förutse resultatet när commitment-värdet skapas, eftersom spelarens indata inte är kända när server seed-värdet genereras. Nonce-värdet gör att varje omgång får en unik signatur även om båda seed-värdena är oförändrade mellan spelomgångarna.
Slumptal genereras med SHAKE256
Omgångssignaturen matas in i SHAKE256, en funktion med utökningsbar utdata (XOF) i SHA-3-familjen. Till skillnad från en standardhash kan en XOF generera en godtyckligt lång pseudoslumpmässig byteström från fasta indata. Det är användbart här eftersom placeringen av flera minor kräver flera oberoende slumpvärden.
Implementationen hämtar 4 byte åt gången från strömmen. När den första utdatan är förbrukad läggs "next" till i tillståndet, varefter ny utdata hämtas. I praktiken ryms dock ett rutnät med 25 rutor och ett realistiskt antal minor väl inom de första 256 bitarna.
Placering av minor utan snedfördelning: förkastningsmetoden
När ett slumpmässigt 32-bitars heltal omvandlas till en position i rutnätet med en enkel modulooperation uppstår ett subtilt men verkligt problem: Om 2^32 inte är jämnt delbart med antalet återstående positioner P, blir vissa positioner marginellt mer sannolika än andra.
Cloudbet eliminerar problemet med hjälp av förkastningsmetoden:
const maxAcceptable = Math.floor(0x100000000 / P) * P;
let rand;
do { rand = rng.next().value; } while (rand >= maxAcceptable);
const index = rand % P;
Alla värden som ligger utanför den största multipeln av P som ryms i 32 bitar förkastas. I stället hämtas nästa värde från SHAKE256-strömmen. Kvar blir en likformig fördelning över exakt P positioner.
Efter att en mina har placerats tas positionen bort ur mängden och P minskas med 1 — en partiell Fisher–Yates-blandning av den endimensionella matris som representerar rutnätets 25 rutor.
Verifiera en omgång
För att verifiera en avslutad omgång behövs fyra värden, som Cloudbet publicerar efter omgången:
serverSeedclientSeednonce- Det angivna värdet för
minePositions
Verifieringen görs så här:
- Hasha
serverSeedmed SHA3-256 och bekräfta att resultatet matchar commitment-värdet som publicerades före spelstart. - Beräkna
roundSignature = SHA3-256(serverSeed:clientSeed:nonce). - Initiera SHAKE256-strömmen med
roundSignatureoch kör förkastningsloopen för att självständigt återskapaminePositions.
En matchning bekräftar att rutnätet inte ändrades efter att omgången hade börjat.
Exempelimplementation (Node.js)
npm install js-sha3
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');
function generateServerSeed() {
const seed = crypto.randomBytes(32).toString('hex');
return { serverSeed: seed, commitment: sha3_256(seed) };
}
function createRoundSignature(serverSeed, clientSeed, nonce) {
return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}
function* createShake256Stream(signature) {
const byteStream = shake256.create(32);
byteStream.update(signature);
while (true) {
const buf = Buffer.from(byteStream.digest({ buffer: true }));
for (let i = 0; i < buf.length; i += 4) {
if (i + 4 <= buf.length) yield buf.readUInt32BE(i);
}
byteStream.update('next');
}
}
function pickUniquePositions(count, totalTiles, rng) {
const available = Array.from({ length: totalTiles }, (_, i) => i);
const result = [];
for (let i = 0; i < count; i++) {
const P = available.length;
const maxAcceptable = Math.floor(0x100000000 / P) * P;
let rand;
do { rand = rng.next().value; } while (rand >= maxAcceptable);
const index = rand % P;
result.push(available[index]);
available.splice(index, 1);
}
return result;
}
const { serverSeed, commitment } = generateServerSeed();
const clientSeed = 'player-provided-seed';
const nonce = 0;
const sig = createRoundSignature(serverSeed, clientSeed, nonce);
const rng = createShake256Stream(sig);
const mines = pickUniquePositions(5, 25, rng);
console.log('Commitment:', commitment);
console.log('Server seed (post-game):', serverSeed);
console.log('Mine positions:', mines);
Några saker som är värda att notera
Varför SHA3 i stället för SHA2? För ett enkelt commitment-värde fungerar båda. SHA3:s svampkonstruktion eliminerar dock längdutökningsattacker. Genom att använda SHA3 genom hela processen — både för commitment-värdet och som grund för SHAKE256 — hålls den kryptografiska lösningen konsekvent.
Varför SHAKE256 i stället för en seedad pseudoslumptalsgenerator (PRNG)? Determinism och möjlighet till oberoende granskning. SHAKE256 är en standardiserad kryptografisk primitiv med en väldefinierad specifikation. Det innebär att oberoende implementationer i vilket programmeringsspråk som helst ger identisk utdata från samma indata. En egenutvecklad PRNG kan få ett implementationsberoende beteende som försvårar tredjepartsverifiering.
Kan en spelare utnyttja systemet genom att välja ett fördelaktigt client seed? I princip ja — en spelare skulle kunna prova olika client seed-värden för att hitta ett som ger ett fördelaktigt rutnät. Detta anses vara acceptabelt i systemets utformning: Spelarens rätt att välja sitt seed-värde är en funktion, inte en sårbarhet. Casinots risk är symmetrisk. Framför allt gör samma mekanism som låter en spelare leta efter ett bra seed-värde det också möjligt att bevisa att casinot aldrig har manipulerat sitt server seed-värde.
Mines är en tydlig introduktion till hur provably fair-system är konstruerade just eftersom spelets RNG-process är relativt avgränsad: en enda commit–reveal-cykel, en okomplicerad byteström och en välkänd urvalsmetod. I takt med att serien fortsätter kommer vi att se hur andra Cloudbet Originals-spel anpassar och vidareutvecklar samma grunder — ibland med ytterligare entropikällor, andra mappningar av utdata eller verifiering i flera steg — samtidigt som kärngarantin bevaras: att inget resultat kan fastställas efter att spelaren har bekräftat sitt val.


