Fair dice you can check
The dice for every game are sealed before it starts. Nobody, not even us, can change them. When the game ends, your device checks every roll against the seal.

How the dice seal works
- Sealed. Before the first roll, the dice for the whole game are locked in. You see the seal: a small rosette and a short seal code such as
5J8Z-XF1R. The seal fixes the dice without showing them. - Mixed. Each player's device adds its own random number to the mix. Neither player, and not the server alone, can pick the dice.
- Revealed and checked. When the game ends, the secret behind the seal is revealed. Your device rebuilds the seal code from it and replays every roll. The code must match the one you saw before the game, and every roll must match what you played. You see "Every roll checked".
Look for the rosette labelled "Provably fair dice" next to the board. Tap it for these steps; after the game it shows the result of the check.
What this proves, and what it doesn't
- It proves the dice were fixed before the first roll and could not be changed during the game, and that neither player could steer them.
- It doesn't prove that nobody at TavlArena could look at the dice early. The server knows the secret during the game. It cannot change the dice, but in principle it could know them. We say this plainly because it is true of every system like this.
- Games against the computer roll on your own device with the same method. That shows the dice weren't changed after the game began, but since your device holds the secret, it can't protect you from yourself.
"But I keep getting doubles"
Fair dice are streaky. With two dice there are 36 equally likely results, so:
| Roll | Chance | About |
|---|---|---|
| Any double | 6 in 36 | 1 in 6 |
| A particular double, such as 6-6 | 1 in 36 | 2.8% |
| A particular mixed roll, such as 6-5 | 2 in 36 | 5.6% |
| At least one 6 on the two dice | 11 in 36 | 31% |
Over a 5-point match you roll the dice dozens of times, so runs of doubles, or a long wait for one, happen often. The seal lets you check that every roll was the one fixed at the start.
For technical people
The seal is a commit-reveal scheme with contributions from both players (encoding v1). Everything below matches the open verifier, which runs entirely on your machine.
The scheme
- Commit. The server draws a 32-byte secret
seedand publishescommitment = SHA-256(ctxCommit ‖ gameId ‖ settingsHash ‖ seat0Id ‖ seat1Id ‖ seed)to both players before any contribution. The seal code is the first 40 bits of the commitment in Crockford base32. - Contribute. Each seat sends 32 random bytes, kept hidden until both have arrived.
- Derive. Roll
nusesHMAC-SHA256(key = seed, ctxRoll ‖ gameId ‖ contribution0 ‖ contribution1 ‖ u32be(n) ‖ u32be(block)), starting at block 0. Read the digest bytes left to right, reject bytes 252–255, and map each accepted byte tobyte % 6 + 1. The first two accepted bytes are the dice; if a block runs out, continue with block 1. - Reveal. When the game ends in any way, the seed is published and anyone can recompute the commitment and every roll.
Exact encoding (v1)
- Domains are UTF-8 with no terminator:
ctxCommit = "tavlarena-dice-commit-v1",ctxRoll = "tavlarena-dice-roll-v1". - UUIDs are 16 bytes in the order their hex digits appear (not GUID little-endian order). Player ids are ordered by seat.
settingsHash = SHA-256(UTF-8(canonical JSON of the rules version and every rule and match setting)). Canonical JSON sorts keys by UTF-16 code units, has no whitespace and allows only safe integers.- Every consumed roll advances the counter, including opening ties, the Tavla re-roll and discarded rolls. There is no pepper.
Worked example from the test vectors
The vector sequential-byte-seed uses a seed of bytes 00 to 1f and contributions 20–3f and 40–5f:
| settingsHash | ba91d0154f4719ca8065d819a50dcc7d7c3b2e0633422a88be8d4f75c3c7d471 |
|---|---|
| commitment | 2c91febc38f87d46a87ef00cd5cb15525db8a7b00c9dae0adc98d45437f78ad2 |
| seal code | 5J8Z-XF1R |
| roll 0, block 0 | 79acb93aaa5b9f70328a4a542cb481f75f5141076d55e85b0e9e3c9e3ade6712 |
| first dice | bytes 121 and 172 → 121 % 6 + 1 = 2, 172 % 6 + 1 = 5 |
The full vector file has 3 cases, including a rejected first byte, a tied opening and the maximum counter and block values. It was generated by a separate Python implementation that uses only the standard library.
Check a game yourself
- After a game, open the dice seal, then Technical details, and choose Download proof (.json).
- Run the verifier (Node 22 or later):
Pass the seal hash you saw before the game. Addnpx tavlarena-dice --expected-commitment <seal hash> tavlarena-dice-<game>.json--jsonfor machine-readable output or--lang trfor Turkish. Exit status 0 means verified, 1 an invalid proof, 2 bad input.
The verifier is open source (MIT licence): source on GitHub and the npm package.
Questions people ask
Is online backgammon rigged?
We can only speak for TavlArena: every game's dice are sealed before the first roll and checked by your device afterwards, and the method is published so anyone can check it.
Can TavlArena change the dice to help the bot?
No. Bots don't touch the server: in a game against the computer your own device rolls the dice with the same sealed method.
Why do I sometimes roll the same double twice in a row?
Each roll is independent. A particular double comes up once in 36 rolls on average, any double once in 6, so repeats are normal.
Do I need to understand cryptography to use this?
No. The rosette does the check for you and says "Every roll checked". The technical part is for people who want to check it themselves.