So funktioniert Provably Fair bei Dice: Golden Ticket

So spielst du Dice: Golden Ticket auf Cloudbet, und so überprüfst du, dass jeder Wurf und jede Golden-Ticket-Ziehung provably fair ist.

So spielst du Dice: Golden Ticket

Dice: Golden Ticket (DGT) spielt sich wie Cloudbets klassisches Dice-Spiel, mit einer zusätzlichen Ebene: einem verborgenen Bonusziel, das zusätzlich zu deinem regulären Gewinn einen Bonusmultiplikator auszahlen kann.

Grundspiel: Wähle Roll Over (2,00–99,99) oder Roll Under (0,01–98,00) und platziere deine Wette. Landet der Würfel auf deiner Seite des Ziels, gewinnst du zu der Auszahlung, die dein Ziel impliziert (ein engeres Ziel = größerer Multiplikator).

Der Golden-Ticket-Bonus: In jeder Runde wird zusätzlich ein zweiter, verborgener Wert festgelegt — das Golden Ticket — mit einem eigenen Bonusmultiplikator zwischen 3x und 200x. Landet dein Würfelwurf innerhalb von ±1,75 um die Position des Golden Tickets, gewinnst du sofort und automatisch die Bonusauszahlung zu diesem Multiplikator — ohne zusätzlichen Einsatz, und er addiert sich zu deinem regulären Gewinn.

Du zielst nicht direkt auf das Golden Ticket — es ist nichts, das du anvisieren oder beeinflussen kannst, es befindet sich in jeder Runde einfach irgendwo auf dem Feld, und in seiner Nähe zu landen ist ein Bonus zusätzlich zu deinem regulären Spiel.

Cloudbet Originals Dice: Golden Ticket

Cloudbets eigenes Originals DGT hat einen RTP von 99 %, einen maximalen Würfel-Multiplikator von x6.730 und einen maximalen Gewinn von 1.000.000 $.

Dice: Golden Ticket (DGT) nutzt dasselbe Provably-Fair-Fundament wie jedes Cloudbet Original — siehe Wie Provably Fair funktioniert für die allgemeine Mechanik (Server-Seed, Client-Seed, Nonce, SHAKE256, Rejection Sampling). Die Ermittlung des Würfelwerts funktioniert ebenfalls genauso wie bei normalem Dice — DGT fügt zusätzlich den Golden-Ticket-Wert und -Multiplikator hinzu. Im Folgenden erfährst du, wie diese Zahlenfolge speziell zu deinem DGT-Ergebnis wird.

Zufallszahlen werden zu einem Spielergebnis

DGT muss beweisen, dass zwei Werte durch verifizierbaren Zufall bestimmt werden: der Würfelwert und der Golden-Ticket-Wert.

Der Vorgang ist einfach: Zuerst wird eine ganze Zahl zwischen 0 und 10000 (einschließlich) gezogen, die zum Würfelwert wird. Dann wird eine weitere ganze Zahl zwischen 175 und 9825 (einschließlich) gezogen, die zum Golden-Ticket-Wert wird. Beide Zahlen werden durch 100 geteilt, sodass sie jeweils zwei Nachkommastellen haben — im Bereich von 0 bis 100 (Würfel) und 1,75 bis 98,25 (Golden Ticket).

Der Bereich des Golden Tickets wird bewusst auf 1,75–98,25 begrenzt, nicht auf 0–100, damit unabhängig davon, wo es landet, immer eine volle „goldene Zone" von 3,5 Einheiten Breite um es herum existiert, in der der Würfelwert landen und den Bonus gewinnen kann. Würde der Bereich bis an die Ränder reichen (0 oder 100), hätte ein Golden Ticket, das nahe einem Rand landet, eine kleinere goldene Zone — und schlechtere Gewinnchancen — als eines, das in der Mitte landet.

DGT hat außerdem eine einzigartige Eigenschaft: Der Multiplikator des Golden Tickets wird selbst durch einen provably fairen Prozess generiert. Einfach ausgedrückt: Eine dritte Zufallszahl zwischen 0 und 1 wird gezogen und dann mithilfe einer gestutzten Pareto-Verteilung in einen Multiplikator zwischen 3 und 200 umgewandelt.

Diagramm, das zeigt, wie Cloudbets nachweisbar faires System drei miteinander verbundene Werte in Dice: Golden Ticket erzeugt — das Würfelergebnis, die Golden-Ticket-Position und deren Bonus-Multiplikator.

Technische Details

Die von SHAKE256 generierten Zufallsbytes werden in vorzeichenlose 4-Byte-Ganzzahlen aufgeteilt, im Bereich von 0 bis 2³²-1. Da der Würfelwert 10001 mögliche Ergebnisse hat (0–10000 einschließlich), verwendet das Rejection Sampling P = 10001; der Golden-Ticket-Wert hat 9651 mögliche Ergebnisse (175–9825 einschließlich), daher ist sein P = 9651. (Siehe die Provably-Fair-Seite dafür, warum dieser Schritt wichtig ist.)

Auswahl der Würfel- und Golden-Ticket-Werte: Die Zufallszahl des Würfels wird modulo 10001 genommen, um seinen Wert zu erhalten. Dasselbe geschieht für die Zufallszahl des Golden Tickets mit 9651, wobei das Ergebnis anschließend um 175 erhöht wird (sodass es im Bereich 175–9825 liegt). Beide werden danach durch 100 geteilt.

Auswahl des Golden-Ticket-Multiplikators (etwas Vertrautheit mit Statistik hilft hier): Eine zufällige 32-Bit-Zahl wird gezogen und durch ihren maximal möglichen Wert geteilt, was eine gleichverteilte Zahl zwischen 0 und 1 ergibt. Dieser Wert wird in eine gestutzte Pareto-Verteilung umgewandelt (untere Grenze 3, obere Grenze 200, Alpha 1,343 — so gewählt, dass der Erwartungswert des Multiplikators 9 beträgt, was den 99%-RTP-Zielwert von DGT stützt), und dann auf zwei Nachkommastellen gerundet.

Spielablauf

Sowohl der Würfel- als auch der Golden-Ticket-Wert werden serverseitig generiert und bis zum Rundenende verborgen — sie können nach Spielbeginn nicht unbemerkt verändert werden. Nachdem du deine Wette platziert hast, wird dein gewählter Schwellenwert mit dem Würfelwert verglichen, und der Abstand des Golden Tickets zum Würfelwert wird gegen die 1,75-„goldene Zone" geprüft, um zu sehen, ob du den Bonus gewinnst.

Code zur unabhängigen Verifizierung

Entspricht exakt der DGT-Implementierung auf Cloudbet — gib deinen Server-Seed, Client-Seed und Nonce ein, um deine eigenen Runden zu verifizieren. (Um den Server-Seed einer Runde auf Cloudbet offenzulegen, musst du bereits zu einem neuen Server-Seed übergegangen sein.)

Abhängigkeiten:

npm install js-sha3

Der Code:

// 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           = 1;                  // ✍️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) {
  // Dice
  // Ranges from 0 to 100, with two decimal places
  const diceMinValue  = 0;
  const diceMaxValue  = 10000;
  let diceValue = sampleFromRange(diceMinValue, diceMaxValue, rng);
  diceValue = diceValue / 100;          // Now the value ranges from 0 to 100

  // Golden Ticket
  // Ranges from 1.75 to 98.25 due to fairness
  // (If it landed on 99, for example, then the chance of the dice landing near
  // it would be smaller than the guarantee of 3.5%)
  const gtMinValue  = 175;
  const gtMaxValue  = 9825;
  let goldenTicketValue = sampleFromRange(gtMinValue, gtMaxValue, rng);
  goldenTicketValue = goldenTicketValue / 100;

  // Golden Ticket Multiplier
  const multiplier = getRandomNumberTruncatedPareto(rng);  // Complicated maths, can be ignored ;)

  return { diceValue, goldenTicketValue, multiplier };
}
// Sample a number using the truncated Pareto distribution
function getRandomNumberTruncatedPareto(rng) {
  // Get a uniformly distributed number between 0 and 1
  let randomInt32 = rng.next().value;
  const fraction = randomInt32 / 0xFFFFFFFF;

  // Truncated Pareto distribution. The expected value is 9.00
  const m0 = 3;
  const m1 = 200;
  const alpha = 1.343;

  const r = Math.pow(m0 / m1, alpha);
  const inner = 1 - fraction * (1 - r);
  let randomNumber = m0 / Math.pow(inner, 1 / alpha);
  randomNumber = Math.round(randomNumber * 100) / 100;  // Round to 2 decimal places
  return randomNumber;
}
// Sample a number between 'min' to 'max' (inclusive) using Rejection Sampling
function sampleFromRange(min, max, rng) {
  const P = max - min + 1;
  const maxAcceptable = Math.floor(0x100000000 / P) * P;

  // Rejection sampling
  let rand;
  do {
    rand = rng.next().value;
  } while (rand >= maxAcceptable);

  // Modulo operation
  const sampled_number = rand % P;

  return sampled_number + min;
}

// 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 = 3 * 2; // We need 3 random numbers, however due to rejection sampling we may need more
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const { diceValue, goldenTicketValue, multiplier } = 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('🎯 Dice landed on:', diceValue);
  console.log('🎫 Golden ticket landed on:', goldenTicketValue);
  console.log('⭐ Multiplier:', multiplier);
})();

Weitere Provably-Fair-Spielerklärungen:

  1. Provably Fair Mines
  2. Provably Fair Pump
  3. Provably Fair Plinko
  4. Provably Fair Dice
  5. Provably Fair Limbo
  6. Provably Fair Roulette
  7. Provably Fair Keno