Appearance
Provably Fair Cryptography
Overview
The Math Engine provides HMAC-SHA256-based provably fair cryptography for games that require client-side verification. When a game implements this system, every game round can be independently verified by any third party using only publicly available information.
The cryptographic chain ensures:
- The server cannot change the outcome sequence after seeing the client's seed
- The client cannot predict the outcome sequence before the server reveals it
- Neither party can control the outcome alone
Cryptographic Primitives
| Primitive | Implementation |
|---|---|
| Hash function | HMAC-SHA256 via audited cryptographic libraries |
| Secret storage | Secured wrapper, auto-redacted from logs, memory zeroed on drop |
| Hash comparison | Constant-time comparison, timing-attack resistant |
| Sequence shuffle | Fisher-Yates with cryptographic PRNG |
Secret Generation
For games utilizing this system, the following components are generated before each round:
| Component | Source | Format | Example |
|---|---|---|---|
secret | The initial game state or shuffled outcome sequence | Serialized string (e.g., comma-separated values) | "val1,val2,val3,..." |
server_seed | OS CSPRNG | Random alphanumeric string | "aB3xK9mQ2wE5rT8y" |
client_seed | Client-provided or BMM-certified RNG | Integer or string | "42781" |
Hash Computation
The server computes a cryptographic hash BEFORE the client provides their seed:
text
hash = HMAC-SHA256(key = server_seed, message = secret)The hash is a 64-character hexadecimal string. It is shared with the client before the game begins. Since HMAC-SHA256 is preimage-resistant, the client cannot derive the secret from the hash.
Shift Calculation
After receiving the client's seed, the server computes a shift value to alter the starting position of the sequence:
text
client_hash = HMAC-SHA256(key = client_seed, message = secret + server_seed)
shift_value = hex_to_int( last_5_characters(client_hash) ) mod array_lengthThe shift_value is an integer in the range [0, array_length - 1]. It determines how many items are moved from the top of the sequence to the bottom (the "cut").
Why This Is Fair
The shift depends on BOTH:
- The server's secret (unknown to the client until revealed)
- The client's seed (unknown to the server until committed)
Neither party can control the shift alone. The server commits to the secret (via the hash) before seeing the client's seed. The client cannot reverse the hash to discover the secret.
Sequence Cut & Consumption
text
// Server generates and shuffles the outcome sequence
sequence = create_outcome_sequence()
sequence = fisher_yates_shuffle(sequence, seed = OS_CSPRNG)
// Server commits to the sequence via hash
secret = sequence.to_codes() // e.g., "C2,D3,HA,..."
hash = HMAC-SHA256(server_seed, secret) // Sent to client
// Client provides seed -> shift computed
shift_value = hex_to_int(last_5(HMAC(client_seed, secret + server_seed))) % array_length
// Sequence is cut at shift position
sequence = cut(sequence, shift_value) // Move shift_value items from start to end
// Game consumes items sequentially from position 0 (start)
outcome_1 = sequence[0]
outcome_2 = sequence[1]
outcome_3 = sequence[2]
outcome_4 = sequence[3]
// ... additional outcomes as needed by specific game rulesVerification (Post-Game)
After the game, the server reveals the secret and server_seed. Any third party can independently verify the round:
Step 1: Verify Hash
text
computed_hash = HMAC-SHA256(revealed_server_seed, revealed_secret)
IF computed_hash == original_hash (constant-time comparison):
✓ Hash verified, server committed to this sequence before the game
ELSE:
✗ Server changed the sequence, game is NOT fairStep 2: Verify Shift
text
client_hash = HMAC-SHA256(client_seed, revealed_secret + revealed_server_seed)
computed_shift = hex_to_int(last_5_characters(client_hash)) % array_length
IF computed_shift == original_shift:
✓ Shift verifiedStep 3: Verify Outcomes
text
sequence = reconstruct_sequence_from_codes(revealed_secret)
sequence = cut(sequence, computed_shift)
IF consumed_outcomes match sequence[0], sequence[1], sequence[2], sequence[3], ...:
✓ Game verified, all outcomes match the provably fair sequenceConstant-Time Security
All hash comparisons use constant-time algorithms. This is critical: it prevents timing side-channel attacks where an attacker could measure how long the server takes to compare two hashes and deduce bytes of the secret.
| Comparison Method | Timing Behavior | Vulnerable? |
|---|---|---|
Standard (==) | Returns early on first mismatch | Yes, timing leaks information |
| Constant-time | Always compares all bytes | No, uniform timing |
Memory Safety
Provably fair secrets are protected in memory:
| Protection | Implementation |
|---|---|
| Log redaction | Secret wrappers prevent Debug/Display from exposing the raw secret |
| Memory zeroing | Memory is explicitly wiped immediately upon deallocation |
| No garbage collector | The ownership model ensures secrets don't float in heap waiting for GC |
This ensures that even if a memory dump occurs, the secret cannot be extracted from it.
Deterministic Replay
For games utilizing this system, the entire game sequence is deterministic given the three seeds. For audit purposes, any provably fair game can be replayed from scratch:
text
replay(secret, server_seed, client_seed) → (outcomes, scores, winner, payouts)This property allows auditors to independently re-compute and verify any historical game.