Yadis

Identity, trust and the plumbing of the web·on the home of the Yadis discovery protocol since 2005

§05  Gaming Tech

Provably Fair Algorithms: A Developer’s Walkthrough

Provably Fair Algorithms: A Developer’s Walkthrough

Air Force Gaming hosts a tabletop game at RAF Mildenhall (8890890) / U.S. Air Force photo by Senior Airman Sarah Spadie, Public domain

A two‑minute confession

We shipped a “provably fair” game. A week later, a player sent us a log. Two bets had the same nonce. The test suite was green. Our code was wrong. We felt sick.

That week changed how we build and ship. We cut hype. We wrote a clear path to truth that any player can follow. It starts with a seed commit before play, then a reveal after. It lets a user check the math on a laptop with no server talk. We also learned where PF breaks, and why teams mix tools like verifiable random functions when the trust model needs it.

The lab notebook that fixed our heads

We began to keep a small “lab notebook.” It sat next to the code. Each change wrote down: how seeds are made, how we commit to them, how the nonce steps, and how the result maps to the game. We pasted one real transcript under each change. No fancy prose. Just inputs, outputs, and a repeatable path.

That habit gave us a “skeptic’s checklist.” We run it before any release. We also share it with support. It cuts back‑and‑forth and gives players the same power we have.

The skeptic’s checklist

Here is what must be public and easy to check. If any item is missing, it is not PF. It is a promise with no teeth.

Trace it backward: payout → RNG → seeds → commit

Do not start with seeds in docs. Start with the thing players care about: the payout. Then step back to the random bytes that drove it. Then go to the inputs that made those bytes. Then show the pre‑commit that fixes your hands before any play.

We keep the per‑bet “transcript” as one small JSON. It has: server_seed_hash, client_seed, nonce, algorithm_version, and the final result bytes. We store these in an append‑only log. For large sets, we pack them into a tree so we can prove no edit after the fact. See Merkle trees for tamper-evident proofs.

Some teams also mix in a public source, like a beacon. This is fine if you still keep a server commit first, and you document the mix rule. One well known source is distributed randomness beacons. It can raise trust if your own seed system fails, but be clear: PF needs a fixed, pre‑committed server input too.

The math we actually ship (short and real)

We use a CSPRNG path with simple parts. First, the server makes a secret server_seed and publishes its SHA‑256 hash before any play. Then, for each bet, we take client_seed, server_seed, and nonce. We run HMAC‑SHA256 with server_seed as the key. We read bytes from the HMAC and map them to the game range. We avoid modulo bias with rejection steps if we need.

You can swap SHA‑256 for BLAKE2b if you want speed and a strong hash. Keep HMAC for the mix. It is simple and battle‑tested. If you need extra trust options, you can add a VRF and post its proof. See real‑world VRF integrations in practice, but still keep your own commit and reveal so you do not depend on any one party.

Note: if you map to a small set (say 1–6 for dice), avoid raw “value % 6”. Use rejection sampling: draw bytes until the value is within a fair bound, then map.

Provably Fair building blocks at a glance

Server seed commit Stops post‑outcome seed choice SHA‑256 or BLAKE2b Hash(server_seed) equals pre‑commit hash on reveal Pre‑commit hash + timestamp; later, the plain seed Rotate seed but keep same hash; never reveal Show hash in UI and API; append‑only log
Client seed capture Gives user an input they control Plain text; optional signature UI shows seed; no change without consent Input box, final value in transcript Silent overwrite by server Lock field after submit; echo in receipt
Nonce management Makes each bet unique Counter per session or per seed Non‑decreasing by 1; no gaps without reason Start value, step rules, reset rules Nonce reuse or hidden reset Test harness to detect repeats
RNG/Derivation function Mixes seeds into random bytes HMAC‑SHA256 (key = server_seed) Recreate HMAC from transcript fields Exact message format and HMAC details Different code path than spec Publish short reference code
Result mapping Turns bytes into game state Rejection sampling or range map Replay function matches UI result Full, exact mapping steps Modulo bias Include examples per game
Transcript export Lets players verify offline JSON with signed digest (optional) Run local script and compare outputs One‑click JSON on each bet Missing fields or version Keep version and algo ID stable
Versioning & signing Binds docs and code to builds SHA‑256 digest; signature Digest of artifact matches release note Version tags, digests, signatures Drift between code and live Reproducible builds; publish keys

Verification UX that cuts tickets

Put the server_seed_hash, client_seed, nonce, algo version, and result on the bet slip. Add a “Verify” button that runs a small script in the browser. It should work with no network call. It should also offer “Export JSON” so a user can save the exact transcript.

Some players do not want to run code. They just want a second view. Independent lists check that PF pages work the way they say. A soft pointer helps those users and builds trust: see CasinosClub.at for a neutral look at sites that claim PF and how they present proofs.

How to publish seed‑commit transcripts players can actually verify

Attacks we saw (and how we fixed them)

Nonce reuse. The classic. Our test missed a path where a retry did not bump the counter. Fix: one write path; atomic nonce bump; reject duplicates. We also added a canary test that fires on any repeat. When in doubt, use the cryptographic right answers and keep code small.

Seed rotation games. We once let ops rotate a seed mid‑epoch “for safety.” Bad idea. That lets someone pick a seed after they peek at early play. Fix: set a hard rotation time or count and publish it in advance. Sign each reveal. Keep old seeds read‑only. For new designs, read recent notes or peer-reviewed cryptography preprints to avoid subtle traps.

Selective reveal. A team tried to reveal only if house wins. That is not PF. Fix: reveal on a timer or count, no matter what.

Code‑sign drift. Someone shipped a hotfix without a new digest in the notes. Fix: a CI job fails the deploy if the live digest does not match the signed one.

“Merkle‑in‑name‑only.” We saw a pretty tree picture that did not link to a base log. Fix: publish the root and the proof path for a sample, and let users test inclusion.

Ship to prod without losing verifiability

Bind your build, docs, and UI. Pin the algorithm version. Make builds reproducible if you can. Use a clear process for keys. A modern model like supply-chain levels for software artifacts (SLSA) helps you write down who signs what and when.

Sign and verify artifacts. Post the hash and the signature in your release notes. Let users grab them. Use tools like signing and verifying build artifacts to cut key pain. In CI, check that your UI shows the same version and digest you just shipped.

Test randomness the right way

Do not chase pretty charts. First, make sure you use a strong primitive (HMAC‑SHA256 is fine). Then, show reproducible transcripts. If you run stats, cite known sets like the NIST statistical test suite for RNGs. But be clear: a pass does not prove fairness. The proof comes from the commit, the reveal, and the ability to replay the steps.

Compliance and audits: what they ask

Auditors like to see that your security checks are not ad hoc. Align with known lists, like the OWASP Application Security Verification Standard, at least for auth, input, and logging controls around your PF code.

If you serve regulated games, read your market’s rules. For the UK, the Remote Technical Standards for fairness call out randomness, logs, change control, and player info. Your PF page and export flow make these checks smooth.

Short FAQ for PMs and lawyers

Can we add a per‑user salt? Yes. Add the user ID (or a hash of it) to the HMAC message. Do not change the key.

How do we rotate seeds? Pick a fixed window (say 24h or 10k bets). Publish the commit at the start. Reveal the old seed as the window ends. Never change the rules mid‑window.

What if SHA‑256 is deprecated? Publish a plan now. Support dual hashes for a time (old and new), then cut over with a version bump.

Are VRFs overkill? Depends on your trust model. They add a public proof and cost. See how a live chain uses it: VRF in production blockchains.

Can we hide the server seed forever? No. If you never reveal, users cannot prove anything about past play.

What to publish on day one

Copy‑paste commands

Local check with Python (save your JSON as bet.json):

Note: for privacy, your export can omit server_seed until reveal. After reveal, the same JSON should include it so the hash check can pass.

Seed lifecycle that will not bite you

Operations that make support happy

A small post‑mortem, to keep us honest

Our nonce bug came from a retry path that skipped the counter. We fixed it by moving nonce bump to a shared layer and adding an idempotent write. We wrote a fuzz test that fires if any two bets share a (client_seed, nonce) pair. We added an alert that scans logs for repeats and wakes someone up if it finds one. Simple steps, big win.

Editorial notes you can copy into your repo

Author: Senior security engineer. Led PF audits across multiple titles. Built, broke, and fixed HMAC‑based RNGs in prod. Spoke at two dev meetups on fairness and logs.

Vuln disclosure: [email protected]

Last updated: 2026‑07‑27

Change log (public)

Disclosure: We are not affiliated with CasinosClub.at, and we do not receive payment for this mention.