How to Verify Provably Fair Bets: Step-by-Step Mathematical Guide
A complete practical walkthrough for players and researchers on auditing cryptographic bet outcomes using SHA-256 and HMAC algorithms.
Overview: Why Cryptographic Verification Matters
Traditional digital entertainment platforms rely on centralized Random Number Generators (RNGs) tested periodically by third-party laboratories. While regulated, players have no real-time mechanism to verify individual game outcomes.
Provably fair gaming introduces transparent mathematical auditing: before placing a bet, you receive a cryptographic commitment (the hashed server seed). After the game, the revealed secret allows you to reproduce the calculation and verify that the operator could not alter the outcome.
This guide provides a reproducible, step-by-step workflow you can execute in any browser console or Node.js environment.
Step 1: Collect Your Three Game Parameters
To audit any provably fair round, you must extract three critical parameters from your bet history:
- Server Seed Hash (Pre-commitment): The SHA-256 hash provided before the round began.
- Revealed Server Seed: The unhashed secret string disclosed by the operator after the round (or seed rotation).
- Client Seed & Nonce: The seed provided by you (or your browser) and the sequential round counter (nonce).
Verification Rule: The unhashed Server Seed, when passed into
SHA-256(), must exactly match the pre-commitment hash displayed prior to your wager.
Step 2: Verify the Server Seed Commitment
Run the following JavaScript in your browser’s Developer Tools Console (F12 > Console) to ensure the disclosed seed matches the initial commitment:
async function verifyCommitment(serverSeed, expectedHash) {
const encoder = new TextEncoder();
const data = encoder.encode(serverSeed);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const calculatedHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
const isValid = calculatedHash.toLowerCase() === expectedHash.toLowerCase();
console.log(`Calculated: ${calculatedHash}`);
console.log(`Commitment: ${expectedHash}`);
console.log(`Status: ${isValid ? 'VERIFIED' : 'FAILED'}`);
return isValid;
}
// Example Execution
verifyCommitment(
"b84a9e29f3d1c4b8e21a0f983c214e6d78a9b2c1d0e4f8a2b6c0d4e8f2a6b0c4",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
Step 3: Compute the HMAC-SHA256 Result
Once the server seed authenticity is established, the game outcome is derived by combining the Server Seed (key) with the message string: {clientSeed}:{nonce}.
async function calculateGameOutcome(serverSeed, clientSeed, nonce) {
const encoder = new TextEncoder();
const keyData = encoder.encode(serverSeed);
const messageData = encoder.encode(`${clientSeed}:${nonce}`);
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
const hmacHex = Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
// Extract first 4 bytes (8 hex characters) for 0-99.99 roll result
const rawInt = parseInt(hmacHex.substring(0, 8), 16);
const normalizedOutcome = (rawInt % 10000) / 100;
return {
hmac: hmacHex,
roll: normalizedOutcome
};
}
Step 4: Interpret the Verification Matrix
| Parameter | Function | Typical Format |
|---|---|---|
| Server Seed | Secret entropy generated by operator | 64-character hexadecimal string |
| Client Seed | Custom entropy set by player | Customizable alphanumeric string |
| Nonce | Incremental sequence identifier | Integer starting at 1 |
| Hash Algorithm | Deterministic pseudo-random generation | HMAC-SHA256 / HMAC-SHA512 |
Best Practices & Troubleshooting
- Always rotate seeds periodically: Never play hundreds of rounds on a single default client seed.
- Log pre-commitment hashes: Save the server seed hash before placing high-value wagers.
- Check nonce gaps: Ensure the nonce increments by exactly 1 per round; unexplained skips indicate unrecorded internal calculations.