provablyfairplay

verify

How to Verify a Provably Fair Bet, Step by Step

How to verify provably fair results yourself: save the commitment hash, rotate the seed, hash the reveal, recompute the roll. Working Python and JavaScript.

Provably Fair Play Editorial9 min

Verification is seven steps and takes about five minutes the first time. Save the hashed server seed before you bet, set your own client seed, play, rotate the seed to force the reveal, hash the revealed seed and compare it to what you saved, recompute a result with the published formula, and check it matches your history.

The order matters more than the tooling. Step one has to happen before you place a bet, and almost everyone who thinks they verified a session actually skipped it.

Step 1 — Save the commitment before you bet

Open the seed settings panel and copy the hashed server seed for the seed pair you are about to use. Paste it into a note, a text file, anything outside the site. Include the timestamp.

This string is the operator’s commitment. Everything that follows is a comparison against it. If you collect it afterwards, the operator could have generated the server seed after seeing your bets and hashed it then — the arithmetic would check out perfectly and prove nothing at all. The commitment is only evidence if it existed first. That asymmetry is the core of what provably fair means.

Save it somewhere you control. A screenshot of the panel with the site chrome visible is better than a bare string, because it also captures which seed pair and which account.

Step 2 — Set your own client seed

In the same panel, replace the pre-filled client seed with a string you invent. Anything works — a word, a date, keyboard noise.

This does not make the cryptography stronger. What it removes is the possibility that the operator constructed a seed pair knowing both inputs. Once your client seed is one you chose after the server seed was committed, no precomputed sequence can have been tailored to you. The reasoning is covered in more depth in server seed, client seed and nonce.

Do it once per site. There is no benefit to changing it repeatedly, and no such thing as a favourable value.

Step 3 — Play, and note the nonces

Place your bets. The nonce increments by one per bet on that seed pair, and your bet history should display it.

Note the nonce of anything you want to check later — a large loss, a near miss, a result that felt wrong. You do not need to record the results themselves, since you will be regenerating them, but having the site’s version to compare against is the point of the exercise.

Step 4 — Rotate the seed to force the reveal

Use the rotate, change or new seed control. This does two things at once: it generates a new server seed and publishes its hash, and it reveals the previous server seed in plaintext.

Copy the revealed server seed. Also copy the new commitment hash if you intend to keep playing — you are now at step 1 of the next cycle.

Step 5 — Hash the revealed seed and compare

SHA-256 the revealed server seed exactly as it was given to you, with no added whitespace, no case changes, no truncation. Compare the output to the string you saved in step 1.

In Python, using only the standard library:

import hashlib

commitment = "68541ed5e22091050632e1f77813222ea7029a5b9c9c40668efe217a14243183"
revealed   = "3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e"

computed = hashlib.sha256(revealed.encode()).hexdigest()
print("computed:", computed)
print("match:", computed == commitment)

Output:

computed: 68541ed5e22091050632e1f77813222ea7029a5b9c9c40668efe217a14243183
match: True

The same check in Node, no dependencies:

const crypto = require('crypto');

const commitment = '68541ed5e22091050632e1f77813222ea7029a5b9c9c40668efe217a14243183';
const revealed = '3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e';

const computed = crypto.createHash('sha256').update(revealed).digest('hex');
console.log('computed:', computed);
console.log('match:', computed === commitment);

Output:

computed: 68541ed5e22091050632e1f77813222ea7029a5b9c9c40668efe217a14243183
match: true

Substitute your own two strings. A match means the operator held that exact seed before you bet. There is no partial match — SHA-256’s avalanche property means a single altered character produces a completely unrelated digest, so the comparison is binary.

One practical trap: some interfaces hash the seed with a prefix, a suffix, or as raw bytes decoded from hex rather than as an ASCII string. If your hash does not match, re-read the site’s documentation before assuming fraud. Try hashing bytes.fromhex(revealed) as well as revealed.encode().

Step 6 — Recompute a result

The hash check proves the seed is genuine. It does not prove the results you were shown came from it. For that you recompute.

The common construction is HMAC-SHA512(serverSeed, clientSeed + ":" + nonce), then a slice of the digest converted to a number. Here is a dice roll at nonce 1:

import hmac, hashlib

def roll(server_seed, client_seed, nonce):
    digest = hmac.new(
        server_seed.encode(),
        f"{client_seed}:{nonce}".encode(),
        hashlib.sha512,
    ).hexdigest()
    value = int(digest[:8], 16) / 2**32
    return int(value * 10001) / 100

server = "3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e"
client = "provablyfairplay-01"

for n in range(1, 6):
    print(n, roll(server, client, n))

Output:

1 42.73
2 18.23
3 65.43
4 47.16
5 1.42

And in JavaScript:

const crypto = require('crypto');

function roll(serverSeed, clientSeed, nonce) {
  const digest = crypto
    .createHmac('sha512', serverSeed)
    .update(`${clientSeed}:${nonce}`)
    .digest('hex');
  const value = parseInt(digest.slice(0, 8), 16) / 2 ** 32;
  return Math.floor(value * 10001) / 100;
}

const server = '3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e';
const client = 'provablyfairplay-01';

for (let n = 1; n <= 5; n++) console.log(n, roll(server, client, n));

Both print the same five rolls. That agreement across two runtimes is worth noticing — it is the determinism property doing its job, and it is why the operator cannot argue that a mismatch is an environment quirk.

Compare the output against your bet history at the same nonces. If the numbers line up, the sequence you played is the sequence the committed seed produces.

Understanding the float derivation

Almost every implementation follows the same four-stage pattern, and recognising it lets you read an unfamiliar site’s documentation quickly.

Stage What happens Example
1. Digest HMAC produces 128 hex characters (SHA-512) 6d62c5b75ae9205d…
2. Slice to integer Take the first N hex characters, parse as base 16 6d62c5b7 → 1,835,189,687
3. Normalise Divide by the maximum for that width ÷ 2³² → 0.42728839605115354
4. Map to game Scale the 0–1 float into the game’s outcome space roll 42.73

A variant walks the digest byte by byte instead: take four bytes and sum byte ÷ 256, byte ÷ 256², byte ÷ 256³ and byte ÷ 256⁴. That produces the identical number — for the nonce 1 digest above, bytes 109, 98, 197 and 183 sum to 0.42728839605115354, the same value the hex slice gives. The byte version exists because it extends naturally when a game needs many values from one digest, which is how the ball path in plinko and the bomb layout in mines are usually generated.

Step 7 — What a mismatch means, and what to do

A genuine mismatch — after you have ruled out encoding, whitespace and the wrong seed pair — means the revealed server seed is not the one that was committed to. There is no innocent version of that. The commitment exists precisely so that this comparison is meaningful.

If it happens:

  1. Screenshot everything before touching anything else: the saved commitment, the revealed seed, the seed panel, the bet history with nonces, the timestamps.
  2. Save your verification script and its output alongside them.
  3. Withdraw what you can, immediately.
  4. Stop playing there. A failed commitment is not a bug to be resolved by support; it is the one thing the system was built to make impossible.
  5. If the site names a licensing body, its complaints process is the only escalation route that exists, and it may not amount to much.

A result that fails to reproduce while the hash matches is a different signal. It usually means you have the algorithm wrong — check the separator, the nonce base (0 or 1), and whether the game consumes more than four bytes. If you have confirmed the formula against the site’s own documentation and results still do not reproduce, the implementation does not match the specification, which is its own kind of failure.

Why a verification usually fails the first time

Most first attempts fail on formatting rather than fraud. Work through these before drawing any conclusion, because a genuine mismatch is rare and a transcription error is not.

Symptom Likely cause Fix
Hash is completely different Trailing whitespace or newline copied with the seed Trim the string, then rehash
Hash is completely different Seed hashed as ASCII when the site hashes raw bytes Try hashlib.sha256(bytes.fromhex(seed))
Hash is completely different You saved the commitment for a different seed pair Match the pair by its own hash field
Hash matches, results do not Wrong nonce base — the site starts at 0, you started at 1 Shift by one and retry
Hash matches, results do not Wrong message separator (: vs - vs plain concatenation) Copy the format from the site’s documentation
Hash matches, results do not Game consumes more than four bytes, or a cursor field Read the per-game algorithm, not the generic one
Results are close but off by 0.01 Rounding direction — floor versus round Use floor unless the site says otherwise

The pattern worth internalising: a hash failure is about the seed, a reproduction failure is about the algorithm. They point at different parts of the system and they carry different weight. Only the first one is unambiguous evidence of a broken commitment.

Why not just use the site’s verifier

Most operators host a verifier page: paste seeds and a nonce, get a result. It is genuinely useful for learning the input format, and it settles the separator question quickly.

It is not proof. The page is served by the party you are checking, runs code you did not read, and could return whatever it is told to return. Using it to verify the operator is like asking the operator whether the operator is honest — the answer format is right and the epistemics are wrong.

Reproducing the calculation locally costs one script you keep forever. That is the point of the whole scheme: not that verification is available, but that it is independent.

What this procedure does not establish

You have proved that a specific sequence was fixed before you bet and that the operator’s results match it. You have not proved the game is worth playing, and those are unrelated questions. A verified sequence can sit under any payout table at all, which is why the number that actually determines your cost is house edge, not provability.

You also have not proved anything about seeds you never rotated, games on the same site with no seed panel, or the site’s willingness to process a withdrawal. The boundaries are worth knowing precisely, and what provably fair does not prove sets them out. Verify anyway — it is the only claim in online gambling you can check yourself, and a site that fails it has told you everything you need to know in about five minutes.

Frequently asked questions

How do I verify a provably fair bet?

Save the hashed server seed before betting, set your own client seed, play, then rotate the seed pair to force the reveal. Hash the revealed server seed with SHA-256 and compare it to what you saved. Finally, recompute one or more results using the site's published algorithm and check they match your bet history.

What does it mean if the hash does not match?

It means the server seed you were given is not the one that was committed to before you bet. That is a direct contradiction of the fairness claim and there is no benign explanation for a genuine mismatch. Screenshot the commitment, the reveal and your bet history, withdraw what you can, and stop playing there.

Can I verify a bet without rotating my seed?

No. The server seed stays secret while it is in use, and without it you cannot recompute anything. Rotation is the event that triggers the reveal. This is why live verification is impossible by design — you are always checking a sequence that has already finished.

Are casino verifier pages trustworthy?

They are convenient but they are not proof. A verifier hosted by the operator is the operator checking its own arithmetic, and it can be made to output whatever the operator wants. Use it to understand the input format, then reproduce the same calculation locally with your own code.

Do I need to be a programmer to verify?

No. Two short scripts cover it — one SHA-256 hash and one HMAC. Both are five lines in Python or Node, both use standard libraries with nothing to install, and you paste your own seeds in. If you can run a script, you can verify.

How many bets should I verify?

A handful per seed pair is enough, including any result that mattered financially. Because every bet on a seed pair derives from the same revealed seed, a single altered result would fail to reproduce. You are sampling a chain, not auditing every link.

Provably Fair Play Editorial — Provably Fair Play explains the cryptography and the probability behind original casino games, and shows you how to check the numbers yourself instead of taking anyone’s word for them. How we write and review this content.