Så fungerar provably fair i Limbo

Så här spelar du Limbo på Cloudbet, och så kontrollerar du att varje multiplikator är provably fair.

Så spelar du Limbo

Limbo är ett snabbt, enkelt multiplikatorspel: innan varje omgång ställer du in en målmultiplikator — hur stor vinst du siktar på. Placera din insats, sedan genereras en resultatmultiplikator. Om den landar på eller över ditt mål vinner du din målmultiplikator gånger din insats. Om den landar under ditt mål förlorar du omgången.

Ju högre målmultiplikator du ställer in, desto större är din potentiella vinst — men desto mindre sannolikt är det att resultatet når dit. Du kan ställa in ditt mål så lågt eller högt du vill inför varje insats.

Cloudbet Originals Limbo

Cloudbets egen Originals Limbo har en RTP på 99 %, en maxmultiplikator på x1 000 000, och tar emot insatser från $0,01 till $20 000.

Limbos slumptal genereras på samma verifierbara sätt som alla andra Cloudbet Originals — se Cloudbets provably fair-system för hur Server Seed, Client Seed och Nonce kombineras till en Round Signature och sedan en ström av slumptal via SHAKE256. Den här sidan handlar om det som är specifikt för Limbo: hur det slumptalet blir multiplikatorn du tävlar mot.

Så beräknas multiplikatorn

Limbo utgår från ett slumpmässigt 32-bitars tal — genererat av SHAKE256, jämnt fördelat, vilket betyder att varje värde mellan 0 och 2³²-1 är lika sannolikt.

Det är dock inte den form vi behöver. En multiplikator ska landa på små värden mycket oftare än på enorma — chansen för 1,01x bör vara mycket högre än chansen för 100 000x. Så slumptalet formas om, steg för steg, till den fördelningen:

  1. Rejection sampling används, men lätt — bara för att undvika att dra det enskilt största möjliga talet, eftersom talet strax ska ökas med ett.
  2. Talet ökas med ett (så att det aldrig blir noll, vilket spelar roll för divisionen i steg 4).
  3. Det skalas ner till ett decimaltal mellan 0 och 1, genom att divideras med sitt maximalt möjliga värde.
  4. 0,99 (Limbos RTP) divideras med det decimaltalet — det är det steget som förvandlar en jämn spridning till en där små multiplikatorer är exponentiellt vanligare än stora.
  5. Resultatet avrundas nedåt till två decimaler.
  6. Slutligen "klipps" det till Limbos giltiga intervall: allt under 1 blir 1 (minsta insats är 1,01, så utfall under 1 räknas ändå bara som förlust), och allt över 1 000 000 blir 1 000 000 (Limbos maximala utbetalning).

Flödesschema som visar hur Cloudbets bevisbart rättvisa Limbo-system steg för steg omvandlar ett slumpmässigt 32-bitars tal till omgångens slutgiltiga multiplikator.

När multiplikatorn väl har genererats är den fastställd och dold — den kan inte ändras efter att din insats är placerad. Om du tar ut vinsten (eller inte) innan den nås är det som avgör omgången.

Verifiera dina egna Limbo-spel

Koden nedan motsvarar Cloudbets Limbo-implementation exakt. Ange din Server Seed, Client Seed och Nonce för att återskapa ditt eget resultat. Btw: för att avslöja ett spels Server Seed på Cloudbet måste du börja använda en ny Server Seed.

Beroenden:

npm install js-sha3

Kod:

// If you are running this yourself, then you can input your own data here
// You can then compare:
// 1) Cloudbet's provided Commitment to the Commitment outputted here. This proves that the Server Seed was not altered during gameplay.
// 2) Cloudbet's provided game outcome to the game outcome outputted here. This proves that your game's outcome was determined solely by the pre-game seeds and was not altered afterwards.
let myServerSeed      = 'your_server_seed'; // ✍️YOUR_INPUT✍️: Enter your (unhashed) Server Seed here
let myClientSeed      = 'your_client_seed'; // ✍️YOUR_INPUT✍️: Enter your Client Seed here
let myNonce           = 0;                  // ✍️YOUR_INPUT✍️: Enter your Nonce here
const usingCustomData = false;              // Change this to true if you are using your own data

// Load libraries
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');

// Step 1: Generate a Server Seed and Commitment
// And generate a Client Seed - either by player input or by the player's browser
function generateServerSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myServerSeed; }
  const commitment = sha3_256(seed);
  return { 'serverSeed': seed, 'commitment': commitment };
}
function getClientSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myClientSeed; }
  return seed;
}

// Step 2: Create a Round Signature with SHA3 using a Server Seed, Client Seed and Nonce
function createRoundSignature(serverSeed, clientSeed, nonce) {
  return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}

// Step 3: Create a SHAKE256 stream generator from a Round Signature.
// To simplify this example program, this function doesn't create a stream, but instead a sequence of custom length.
function* createRandomNumberSequence(numbersToGenerate, roundSignature) {
  const numOfBytes = 4 * numbersToGenerate;           // 4 bytes (32 bits) per random number we generate
  const byteStream = shake256.create(8 * numOfBytes); // shake takes bits as input
  byteStream.update(roundSignature);                  // Use the Round Signature to seed the generator
  const buf = Buffer.from(byteStream.digest());       // The Buffer class lets us package every 4 bytes into one 32-bit integer
  for (let i = 0; i < numbersToGenerate; i++) {
    yield buf.readUInt32BE(i * 4);                    // Read buf in 4-byte steps
  }
  throw new Error("No more numbers in the random number sequence! Fix: make 'numbersToGenerate' larger.");
}

// Step 4: Draw from the random number sequence to calculate the game result
function getGameResult(rng) {
  let randomInt32 = rng.next().value;               // 'randomInt32' ranges from 0 and 4294967295
  while (randomInt32 == 0xFFFFFFFF) { randomInt32 = rng.next().value; } // This can basically be ignored

  const fraction = (randomInt32 + 1) / 0xFFFFFFFF;  // 'fraction' ranges from 0 to 1
  const inverse = 0.99 / fraction;                  // 'inverse' ranges from 0 to (basically) infinity
  let crashPoint = Math.floor(inverse * 100) / 100; // Cut off everything past the second decimal place
  // We force the crashpoint to range from 1 to 1,000,000
  if (crashPoint < 1.0) {
    crashPoint = 1.0;
  }
  if (crashPoint > 1000000) {
    crashPoint = 1000000;
  }

  return crashPoint;
}

// Example usage
(function main() {
  let nonce = 1;
  if (usingCustomData) { nonce = myNonce; }

  const { serverSeed, commitment } = generateServerSeed();
  const clientSeed = getClientSeed();

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 1 * 2; // We need 1 random number, but there is an improbable case we'll need two
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const value = getGameResult(rng);

  console.log('📝 Commitment (shown before the game):', commitment);
  console.log('🔒 Server Seed (revealed after the game):', serverSeed);
  console.log('🎲 Client Seed:', clientSeed);
  console.log('🔢 Nonce:', nonce);
  console.log('🔁 Round Signature (can be verified after the game):', roundSignature);
  console.log('⛓️‍💥 Break Point:', value);
})();

Fler förklaringar av provably fair per spel:

  1. Provably fair Mines
  2. Provably fair Pump
  3. Provably fair Plinko
  4. Provably fair Dice
  5. Provably fair Dice: Golden Ticket
  6. Provably fair Roulette
  7. Provably fair Keno