Skip to main content

Provably Fair - Implementation

Updated over 2 weeks ago

Generating Random Numbers for Instant Games

Overview

The process for generating random numbers in instant games involves two main steps:

  1. Generate a 512-bit (64-byte) random array using:

    RandomBytes = HMACSHA512(Active_Server_Seed, Active_Client_Seed:Nonce:Cursor)
  2. Extract the needed value—either an integer or a floating point number—from the generated bytes.

📌 Note: This process differs from how random numbers are generated for scheduled games.


Generating Random Bytes

The core function is generateRandomBytes(). Here is the pseudocode:

byte[] byteGenerator(serverSeed, clientSeed, nonce, cursor) {
// 1. Create an HMAC512 hasher with server seed as the key
let hasher = HMAC512(serverSeed)

// 2. Format the message as: clientSeed:nonce:cursor
let message = clientSeed:nonce:cursor

// 3. Hash the message
let randomBytes = hasher.Hash(message)

// 4. Increment the cursor
cursor = cursor + 1

// 5. Return the random bytes
return randomBytes
}

These randomBytes are then passed into either:

  • generateDouble() → for floating point numbers

  • NextPositiveInt32() → for integers


Generating Floating Point Numbers

Floating Point Structure (IEEE 754 - 64-bit double):

  • 1 bit: Sign (positive/negative)

  • 11 bits: Exponent

  • 52 bits: Mantissa/Fraction

The mantissa controls the precision for values between 0 and 1.

Algorithm

  1. Generate 7 random bytes

  2. Combine into a 56-bit number

  3. Mask to keep the lowest 52 bits

  4. Divide by 2^52 → Gives a uniform value between 0 (inclusive) and 1 (exclusive)

Pseudocode:

double generateDouble() {
bytes = generateRandomBytes(7)

bits = 0
for each byte in bytes:
bits = (bits << 8) OR byte

masked = bits AND (2^52 - 1)
randomDouble = masked / 2^52

return randomDouble
}

✅ This ensures maximum precision and equal probability for all outcomes in the range [0, 1).


Generating Integers

Unlike some operators who derive integers from floats (which can cause bias), this method directly generates uniform integers, ensuring fairness and accuracy.

Why This Matters:

  • Mapping floats to integers (e.g., floor(float * N)) introduces rounding issues

  • This method uses rejection sampling to avoid bias

How It Works:

  1. Draw a 32-bit random unsigned integer (value)

  2. Define a rejection limit:

    limit = MAX_UINT32 - (MAX_UINT32 % maxExclusive)
  3. If value < limit, use:

    result = value % maxExclusive
  4. If not, discard and draw new bytes

🛑 Rejection Sampling ensures perfectly uniform results in [0, maxExclusive)


Pseudocode:

int NextPositiveInt32(maxExclusive) {
if maxExclusive not provided:
maxExclusive = MAX_UINT32

limit = MAX_UINT32 - (MAX_UINT32 mod maxExclusive)
byteCount = HashingBitSize / 8

loop forever:
bytes = GenerateNextBytes(byteCount)

for i from 0 to bytes.length - 4 step 4:
value = convert 4 bytes at i to unsigned int

if value < limit:
return value % maxExclusive
}
Did this answer your question?