how-it-works
What Provably Fair Means and How It Actually Works
What is provably fair? The casino commits to a hashed seed before you bet, then reveals it so you can recompute every result yourself. The mechanism, with code.
Provably fair is a commit-reveal scheme. Before you place a bet, the casino publishes the SHA-256 hash of a secret server seed; after you stop using that seed, it publishes the seed itself. You hash the revealed seed, compare it to what you saved, and recompute every result from the seed plus your own client seed and a per-bet counter. If the hash matches and the results reproduce, the operator could not have changed any outcome after seeing your bet.
That is the whole idea. Everything else is implementation detail — which hash function, how the digest becomes a number, how many bets share a seed.
The problem it solves: you cannot see the shuffle
In a physical casino you watch the dealer. You see the deck cut, the wheel spin, the dice leave the hand. The verification is visual and it is continuous.
Online, there is nothing to watch. A crash curve is a number your browser was told to animate. A dice roll is a float that arrived over a websocket. You have no access to the process that produced it, and the operator has every technical opportunity to look at your bet first and pick a result afterwards.
Licensing and third-party RNG audits address this by having someone else look at the code periodically. That is trust delegated, not trust removed — you are trusting the auditor, the sample they tested, and the assumption that the audited build is the one running tonight.
Provably fair removes the delegation for one specific question: was this particular result decided before I bet? It answers that question with arithmetic you can run on your own machine.
The commit-reveal scheme, step by step
- The server generates a server seed — typically a long random hex string — and keeps it secret.
- It publishes
SHA-256(serverSeed), the commitment hash, before you place a bet. - You supply or accept a client seed, a string you control.
- A nonce starts at 0 or 1 and increments by one with every bet on that seed pair.
- Each result is derived from
HMAC-SHA512(serverSeed, clientSeed + ":" + nonce), or an equivalent construction the site documents. - When you rotate the seed, the server reveals the old server seed and commits to a new one.
- You hash the revealed seed and compare. Then you recompute the results.
The three inputs do different jobs, and the details matter enough that they have their own page — server seed, client seed and nonce covers each one properly, including what changing your client seed does and does not buy you.
What SHA-256 and HMAC actually do
You do not need number theory to use this. You need three properties.
Deterministic
The same input always produces the same output, on any machine, in any language. SHA-256("hello") is 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 today and it was that in 2011. This is what makes independent verification possible at all — your Python and their Go agree.
One-way
Given a hash, you cannot practically recover the input. That is why publishing SHA-256(serverSeed) reveals the commitment without revealing the seed. You know the operator has fixed a value; you cannot compute what it is, so you cannot compute upcoming results.
Avalanche
Change one character of the input and the output is unrecognisably different — not slightly different, entirely different. Compare:
| Input | SHA-256 |
|---|---|
provably fair |
ffc224677596698a2d257e6cd5e22ded461e6664d4fd4cd0f9ec297aaa387c51 |
provably fair. |
6b836fb9736ba9f6e79e3fc5094a1336df8930914f8b061483294659b019e5df |
One added full stop. No shared structure in the output. This is why the operator cannot nudge a seed slightly to shift results in its favour — any change at all produces a hash that has nothing to do with the published one.
HMAC is a keyed hash: HMAC-SHA512(key, message). It takes two inputs instead of one and mixes them in a way that resists the length-extension tricks that plain hashing is vulnerable to. In provably fair systems the server seed is the key and the client seed plus nonce is the message. That structure is what lets one secret seed generate an unlimited, unpredictable-to-you but reproducible-to-both sequence of results.
A worked example you can reproduce
Take this server seed:
3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e
Its SHA-256 — the value a casino would publish as the commitment — is:
68541ed5e22091050632e1f77813222ea7029a5b9c9c40668efe217a14243183
Run this yourself and you will get the same string:
import hashlib
server_seed = "3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e"
print(hashlib.sha256(server_seed.encode()).hexdigest())
# 68541ed5e22091050632e1f77813222ea7029a5b9c9c40668efe217a14243183
Now derive a result. With client seed provablyfairplay-01 and nonce 1:
import hmac, hashlib
server_seed = "3f6a1b9c2d8e470a5c1f8b3d6e29a4f70b5d8c1e2a3f4b6c7d8e9f0a1b2c3d4e"
client_seed = "provablyfairplay-01"
nonce = 1
digest = hmac.new(
server_seed.encode(),
f"{client_seed}:{nonce}".encode(),
hashlib.sha512,
).hexdigest()
print(digest[:16]) # 6d62c5b75ae9205d
value = int(digest[:8], 16) # 1835189687
print(value / 2**32) # 0.42728839605115354
The first eight hex characters of that digest are 6d62c5b7, which is 1835189687 as an integer. Divide by 2**32 (4,294,967,296) and you get 0.42728839605115354 — a uniform float in the range 0 to 1. In a dice game that maps to a roll of 42.73.
Change the nonce to 2 and the same seed pair yields 0.18237478891387582, a roll of 18.23. Nonce 3 gives 0.6542643764987588, a roll of 65.43. Nothing about those is guessable from the commitment hash, and all three are fully reproducible once the seed is revealed.
Why the operator cannot change the result after your bet
Suppose you bet on a dice roll under 42.00 at nonce 1 and the seed produces 42.73. You lose. Could the casino have picked that?
To move that specific result, the operator would need a different server seed. Any different server seed produces a different SHA-256 — that is the avalanche property. The hash it published before you bet would no longer match the seed it reveals afterwards. You would see the mismatch the moment you hashed the revealed seed.
The alternative attack is to find a second seed that hashes to the same commitment and also happens to produce favourable results. That is a second-preimage attack on SHA-256, and no practical method for one is known. This is the same assumption that secures software signatures and TLS certificates.
The remaining honest caveat is scope. The commitment binds the operator to a seed and therefore to a sequence. It does not bind them to the mapping function they told you about, unless you verify results against it. That gap, and several others, are the subject of what provably fair does not prove.
How is this different from a certified RNG?
Both are answers to “how do I know the result was not chosen to beat me”, and they answer it in structurally different ways.
A certified RNG is periodically inspected by a testing laboratory, which examines the generator, runs statistical batteries against its output, and issues a certificate. The evidence is a document about a sample of behaviour at a point in time, and you cannot reproduce any of it.
Provably fair produces evidence per bet, on demand, checkable by you, with no third party involved. The evidence is arithmetic.
| Certified RNG | Provably fair | |
|---|---|---|
| Who checks | A testing laboratory | You |
| When | Periodically, on a sample | Any bet, after seed reveal |
| What you hold | A certificate | A hash and a reproducible result |
| Covers randomness quality | Yes, statistically | No |
| Covers per-bet tampering | Only by inference | Directly |
| Requires trusting a third party | Yes | No |
Notice the two rows that do not overlap. Certification tests whether the generator’s output is statistically sound; provably fair does not test that at all, because a committed seed drawn from a weak source still hashes and reveals perfectly. Provably fair tests whether your specific result was decided in advance; certification can only infer that from the code it inspected months ago.
They are complementary rather than competing, and neither subsumes the other. What provably fair uniquely removes is the need to trust anybody’s report — including ours.
Provably fair says nothing about the odds
This is the misunderstanding that matters most. A game can be perfectly, verifiably provably fair and still be a terrible bet.
Consider a coin flip that pays 1.5× on a true 50/50. Every flip is derived from a committed seed and every result verifies. The expected return per $1 staked is 0.5 × 1.5 = 0.75 — a 25% house edge. The cryptography is flawless and the game is awful.
The edge lives in the payout table, not the RNG. A typical dice implementation with a 1% edge pays 0.99 ÷ p on a win chance of p: bet on a 49.5% chance and you are paid 2.00×; bet on a 10% chance and you are paid 9.90×. Both carry the same 1% edge, and both verify identically. If you only read one other page here, make it house edge vs RTP, because that is the number that determines what the game costs you.
| What provably fair proves | What it does not |
|---|---|
| The result was fixed before your bet | That the odds are favourable |
| The sequence matches the commitment | That the site will process a withdrawal |
| Results reproduce from published inputs | That the published algorithm is the one running |
| Your client seed influenced the outcome | That bonuses and side features use the same system |
Where the maths goes from here
Once you accept the derivation as sound, the interesting question stops being “was this rigged” and becomes “what does this game actually pay”. The float in the example above is the raw material; each original game turns it into an outcome differently. Crash converts it into a multiplier through a formula with a deliberate instant-bust probability, which is why crash game maths looks nothing like how dice is constructed despite both starting from the same HMAC digest.
The practical habit is smaller than it sounds. Copy the commitment hash into a note before your session. Set a client seed you chose. Play. Rotate. Hash the revealed seed and check one or two results. Five minutes, once per site, and you know whether the guarantee is real there or decorative.
Frequently asked questions
What does provably fair mean in gambling?
Provably fair means the operator publishes a cryptographic commitment to its random seed before you bet, then reveals that seed later so you can recompute every outcome and confirm nothing was changed mid-session. It is a verification method, not a fairness rating — the underlying game can still carry any house edge the operator chooses.
Is provably fair the same as random?
No. Provably fair proves a result was fixed in advance by inputs you can check, not that those inputs were drawn from a good random source. A deterministic, checkable sequence can still be predictable if the server seed generation is weak. Provability addresses tampering after the fact, not the statistical quality of the source.
Can a casino cheat a provably fair game?
Not by changing a result after seeing your bet, because the revealed server seed must hash to the value published beforehand. It can still set unfavourable payouts, cap wins, use a different algorithm than the one documented, or simply refuse to pay a withdrawal. Those are separate problems that no hash commitment addresses.
Do I need to verify every bet?
No. Verification is a spot check. Recomputing a handful of results per seed pair — including any that mattered financially — is enough to confirm the sequence matches the commitment, because a single altered result would break the chain for that seed. The deterrent works because verification is possible, not because everyone does it.
What is the hashed server seed for?
The hashed server seed is the commitment. It fixes the operator to one specific secret value before betting starts, while hiding the value itself so you cannot compute upcoming results. When the seed is revealed at rotation, hashing it must reproduce the string you saved. If it does not, the seed was changed.