CryptoHelper

ResearchGuidesReviews
Cryptographic Verification Beginner Level 8 min read

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.

CH
Curated by CryptoHelper Editorial

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:

  1. Server Seed Hash (Pre-commitment): The SHA-256 hash provided before the round began.
  2. Revealed Server Seed: The unhashed secret string disclosed by the operator after the round (or seed rotation).
  3. 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

ParameterFunctionTypical Format
Server SeedSecret entropy generated by operator64-character hexadecimal string
Client SeedCustom entropy set by playerCustomizable alphanumeric string
NonceIncremental sequence identifierInteger starting at 1
Hash AlgorithmDeterministic pseudo-random generationHMAC-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.