ENTR

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.

Illustration: two white dice beside a round İznik tile rosette on a navy table.

How the dice seal works

  1. 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.
  2. Mixed. Each player's device adds its own random number to the mix. Neither player, and not the server alone, can pick the dice.
  3. 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:

RollChanceAbout
Any double6 in 361 in 6
A particular double, such as 6-61 in 362.8%
A particular mixed roll, such as 6-52 in 365.6%
At least one 6 on the two dice11 in 3631%

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

  1. Commit. The server draws a 32-byte secret seed and publishes commitment = 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.
  2. Contribute. Each seat sends 32 random bytes, kept hidden until both have arrived.
  3. Derive. Roll n uses HMAC-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 to byte % 6 + 1. The first two accepted bytes are the dice; if a block runs out, continue with block 1.
  4. 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:

settingsHashba91d0154f4719ca8065d819a50dcc7d7c3b2e0633422a88be8d4f75c3c7d471
commitment2c91febc38f87d46a87ef00cd5cb15525db8a7b00c9dae0adc98d45437f78ad2
seal code5J8Z-XF1R
roll 0, block 079acb93aaa5b9f70328a4a542cb481f75f5141076d55e85b0e9e3c9e3ade6712
first dicebytes 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

  1. After a game, open the dice seal, then Technical details, and choose Download proof (.json).
  2. Run the verifier (Node 22 or later):
    npx tavlarena-dice --expected-commitment <seal hash> tavlarena-dice-<game>.json
    Pass the seal hash you saw before the game. Add --json for machine-readable output or --lang tr for 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.

By TavlArena. Updated: