Trust

Provably fair

Updated 8 October 2026

Every round is fixed before the first bet of the room exists, and anyone can check that it was. This page explains how, and how far each check goes.

The commitment

  • When a room is created, the server draws a secret seed and hashes it again and again with SHA-256, building a chain in which each seed is the SHA-256 of the next one. The end of the chain, the terminus, is published before the first round, together with the room's client seed and generator version: GET /fair?room=<room>.
  • Rounds use the chain backwards. The seed of round r is the value whose SHA-256 is the seed of round r − 1, so it is already bound before anyone bets: its hash is the commitment shown while betting is open.
  • When the round ends, its seed is revealed.

Every seed is bound by the terminus before any bet exists. So the server can neither pick a seed after seeing the bets, nor try several seeds and keep a favourable one.

Rooms with several tables

A room can be played on several tables, each a game of its own with its own chain: the commitment above holds table by table, and no two tables ever share a chain. Players are moved between tables only between two rounds, when nobody has an open position.

Moving players could open a door that a single table does not have: a server free to create a table at the moment of a move could create several, look at their chains and send the players to the one it likes. So it is not free to.

  • A room's tables are created before they are needed, each with its terminus written and published at creation, and they are put into play in the order they were created. A new one is created only to replace the one that was just used.
  • GET /fair?room=<room> lists every table of the room, in order, with its terminus, its client seed, the moment it was created and, once it has finished its chain, the moment it retired. The list keeps every table and comes in pages of 50 (tablesAfter).
  • So anyone can check that the table a round was played on existed, with its terminus, before that round — and before the move that brought its players there. A round's page names its table: table 1 has the room's id, the others <room>--t<n>.

A room without tables is simply one table, and the list holds just that one.

From seed to round

The round is derived from its seed, deterministically:

stream_i = HMAC-SHA256(key = seed, message = clientSeed ":" round ":" label ":" i)     i = 0, 1, 2, …

label names the stream (path, events, stomp…): a game draws different things from different streams of the same seed. Big Fish names its streams after the fish (<fish index>:path) and the whale (whale), in place of the round.

The byte stream feeds the game's generator, which draws the round's events in a fixed, documented order. The same seed, client seed, round number, generator version and configuration always give the same round.

  • God Candle: the price path, epoch by epoch, and the rug.
  • Diamond Hands: the stabs and the knife.
  • Big Fish: the whale's beat, and every fish from its own index, fixed when it is dropped.
  • Hypnofrog: the twelve flies, the tongue and the frenzy.

Bets never enter the derivation. The round does not depend on who bet what, and it exists only in the server's memory until it ends: nothing about a round is written before it is over.

Three checks

Check What it proves Who can run it
1 SHA256(seed) equals the commitment the seed is the one committed for that round anyone, with SHA-256
2 hashing the seed as many times as the round number gives the terminus the seed belongs to the chain published before the first round anyone, with SHA-256
3 the round re-derived from the seed is the one that was played and paid the outcome, and every payout, follow from the seed needs the generator

A seed is text. Every seed is 64 lowercase hex characters, and SHA-256 — like the HMAC key above — takes that text, as UTF-8 bytes: not the 32 bytes it spells. To run check 1 by hand, printf %s "$seed" | sha256sum prints the round's commitment; repeat it on its own output to walk the chain towards the terminus.

Checks 1 and 2 happen in the game. After every round the provably-fair panel compares the revealed seed with its commitment, and walks the chain back to the published terminus. The arithmetic runs in the browser: a check that asked the server whether all went well would check nothing.

Check 3 needs our generator, and the server runs it for every round of a persistent room: real-money rooms, and the demo rooms of an operator.

  • GET /fair/<room>/<round> returns every input of a closed round as JSON: seed, commitment, client seed, generator version, the configuration the round ran with, the terminus and the positions.
  • GET /fair/<room>/<round>/verifica is the same verification, for people: a verdict at the top, the three checks explained, every position re-derived in a table, and the verifier's own report line by line.
  • Payouts too. The page recomputes what every position should have been paid — from its stake, its entry, its exit and the room's settlement rule — and compares it with what was paid. A payout above what the round pays is flagged as an error. A payout below it is flagged as a doubt, because a payout cap is part of the room's limits, not of the round.

The public demo rooms of this site keep their rounds in memory, so there is no stored round to re-derive: for a demo round, checks 1 and 2 in the game are the verification.

The generator source code is available to testing laboratories and to partners on request.

The client seed

Each table has one client seed, fixed before its first round and published with the terminus. It comes in one of two forms, and the seed itself says which.

  • A fixed text, chosen when the table is created: <room>-public-client-seed-v1, for example. It adds no entropy that the server does not know: the guarantee rests on the terminus, published before any bet, and that is what rules out choosing seeds.
  • A value from drand, the public randomness beacon run by the League of Entropy, a group of independent organisations: drand:quicknet:<round>:<signature>. The table's chain is written together with the number of a drand round that does not exist yet, and /fair shows the seed as drand:quicknet:<round> until drand produces it. Then the seed becomes that round's signature, and only then does the table play its first round. A chain written before its seed existed cannot have been picked to suit it.

To check a drand seed yourself:

  • the round's signature is public, on any drand mirror: https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/<round>;
  • it verifies against the public key of drand's quicknet network (scheme bls-unchained-g1-rfc9380: the message is the SHA-256 of the round number written as 8 big-endian bytes), so you need not trust the mirror that served it — nor us;
  • round n is produced at 1692803367 + 3 × (n − 1) seconds after 1 January 1970 (UTC), so the round number also says from when the value could be known.

The verification page of every round of such a room checks the signature for you, in a line of its own.

Generator versions

Every round records the version of the generator that produced it. Any change to what a generator draws, or how, is a new version, never a silent edit: a past round is always re-derived with the version and the configuration it ran with. The current version of each game is on its page, under The games, and in the catalog, GET /games.

The command ledger

Every player command of a round — accepted and refused — is stored append-only, with the time the server received it and the step used to price it. A cash-out is priced at the step in which the server received it, never a later one, and the ledger is what shows it. Operators read rounds, bets and the timeline of a single bet through the signed reads of the integration guide.