Como o provably fair funciona no Limbo

Veja como jogar Limbo na Cloudbet, e como verificar se cada multiplicador é provably fair.

Como jogar Limbo

Limbo é um jogo de multiplicador rápido e simples: antes de cada rodada, você define um multiplicador-alvo — o quão grande é o prêmio que você está buscando. Faça sua aposta, e então um multiplicador de resultado é gerado. Se ele cair igual ou acima do seu alvo, você ganha seu alvo multiplicado pela sua aposta. Se cair abaixo do seu alvo, você perde a rodada.

Quanto mais alto o multiplicador-alvo definido, maior o seu ganho potencial — mas menor a chance de o resultado alcançá-lo. Você pode definir seu alvo tão baixo ou tão alto quanto quiser antes de cada aposta.

Cloudbet Originals Limbo

O Limbo Original da Cloudbet tem RTP de 99%, multiplicador máximo de x1.000.000, e aceita apostas de $0,01 a $20.000.

Os números aleatórios do Limbo são gerados da mesma forma verificável que todos os outros Cloudbet Originals — veja o sistema provably fair da Cloudbet para saber como a Seed do Servidor, a Seed do Cliente e o Nonce se combinam em uma Assinatura da Rodada e depois em um fluxo de números aleatórios via SHAKE256. Esta página cobre o que é específico do Limbo: como esse número aleatório se transforma no multiplicador contra o qual você está competindo.

Como o multiplicador é calculado

O Limbo parte de um número aleatório de 32 bits — gerado pelo SHAKE256, uniformemente distribuído, o que significa que todo valor entre 0 e 2³²-1 é igualmente provável.

Porém, esse não é o formato de que precisamos. Um multiplicador deve cair em valores pequenos com muito mais frequência do que em valores enormes — a chance de um 1,01x deve ser muito maior do que a chance de um 100.000x. Então o número aleatório é remodelado, passo a passo, nessa distribuição:

  1. A amostragem por rejeição é usada, mas de forma leve — apenas para evitar sortear o único maior número possível, já que o número está prestes a ser incrementado em um.
  2. O número é incrementado em um (para que nunca seja zero, o que importa para a divisão na etapa 4).
  3. Ele é reduzido a um decimal entre 0 e 1, dividindo-o pelo seu valor máximo possível.
  4. 0,99 (o RTP do Limbo) é dividido por esse decimal — essa é a etapa que transforma uma distribuição uniforme em uma na qual multiplicadores pequenos são exponencialmente mais comuns do que os grandes.
  5. O resultado é arredondado para baixo, com duas casas decimais.
  6. Por fim, ele é "cortado" para o intervalo válido do Limbo: qualquer valor abaixo de 1 se torna 1 (a aposta mínima é 1,01, então resultados abaixo de 1 são todos apenas uma derrota), e qualquer valor acima de 1.000.000 se torna 1.000.000 (o pagamento máximo do Limbo).

Fluxograma mostrando como o sistema de equidade comprovável do Limbo da Cloudbet transforma, passo a passo, um número aleatório de 32 bits no multiplicador final da rodada.

Uma vez gerado, o multiplicador é fixo e oculto — ele não pode ser alterado depois que sua aposta é feita. Sacar (ou não) antes de alcançá-lo é o que decide a rodada.

Verifique seus próprios jogos de Limbo

O código abaixo é fiel à implementação de Limbo na Cloudbet. Insira sua Seed do Servidor, Seed do Cliente e Nonce para reproduzir seu próprio resultado. A propósito: para revelar a Seed do Servidor de um jogo na Cloudbet, você precisa começar a usar outra Seed do Servidor.

Dependências:

npm install js-sha3

Código:

// 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);
})();

Mais explicações dos jogos provably fair:

  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