Offline Round Verify
- No network calls
- Web Crypto only
- Works from file://
Paste a finished round’s audit JSON and this page re-derives every card that was dealt on your own machine. If they match, the deck was fixed before anyone acted — and you didn’t have to trust the game server to know it.
-
1
Finish a round. In the game, click 📋 Audit JSON — enabled once the round is over and the server seed is revealed.
-
2
Paste it below. Or open a shared
?audit=…link, which fills in and runs everything for you. -
3
Press Verify. Green means every card came from seeds locked in before the round. Red checks explain themselves.
Round data
Nothing you paste leaves this page — there is no network code in it at all.
dealtCards and pinned power the strongest checks. It still runs without
them — the report just marks those checks as unproven rather than passed.
Enter seeds by hand. Box number is required for each seat — it feeds into the lane key. Leaving a commitment empty makes that box’s check fail, by design: a seed always matches a hash you compute from it, so only a pre-published commitment proves anything.
Result
Every check says what it proves. A ❌ always comes with a note telling you whether it is a gap in your input — you had no pinned copy, say — or a genuine mismatch. Only a genuine mismatch indicts the round.
Lanes — first 12 cards per stream
How it works
What this page does and doesn’t provestart here
✓ Proven here
The server seed is linked to the published chain, every client seed was locked in advance, and every dealt card can be reproduced in order from its lane. Your box’s cards depend only on your seed.✗ Not proven here
Whether an opponent is human or a bot. It doesn’t change your outcome: a bot cannot affect your seed, your cards, or your card flow. What the house can still do is cancel a whole round — visible to everyone through the cancellation rate and burned seeds.Everything runs locally. Save this file and open it with no internet at all, or host your own copy on a domain you control — it will give the same answer.
Glossarythe words used on this page
- Seed
- A random 64-character hex string. The server has one per round; every box (seat) has its own. Cards are computed from these — and nothing else.
- Commitment
- The SHA-256 hash of a seed, published before the round. Hashing is one-way, so the hash proves the seed existed in advance without revealing it.
- Hash chain & chain tip
- The server’s seeds are pre-built as a chain: each seed is the hash of the next one. Only the tip is published at startup, and every revealed seed must hash to the previous link — so the sequence cannot be reordered or replaced mid-game.
- Pinned copy
- A copy of the commitment or chain tip your browser saved before the round. This page checks against that copy — not against whatever a server claims now. If you never pinned one, that check is skipped as unproven (amber), not reported as cheating.
- Lane
- An independent card stream. Each box’s cards come only from its own seed; the dealer lane comes from all revealed seeds. Nobody else’s choices can shift your lane.
- Nonce
- The round number, mixed into the formula so the same seeds can never produce the same cards twice.
- dealtCards
- The log of which card came from which lane, in order. With it, the verifier reproduces every card you actually saw — the strongest check on this page.
How cards are dealtlanes · dealer · Fisher–Yates
There is no shared shoe. Each box and the dealer own an independent 6-deck stream (a lane). Your hits cannot move anyone else’s cards; a house bot cannot steer the dealer by playing another seat.
Step A — Lane keys (HMAC-SHA512)
A lane key is a 128-hex (512-bit) value. It is the input to the shuffle — not a card yet.
The key argument of HMAC is the server seed;
message is the UTF-8 string below.
Step B — Build a fresh 6-deck shoe
Order is fixed and identical on every machine: 6 × (suits ♠♥♦♣) × (ranks A,2…10,J,Q,K) → 312 cards. Inner loops: for each deck, for each suit, for each rank.
Step C — Partial Fisher–Yates + rejection sampling
Only the first count positions are shuffled (prefix-stable: drawing 24
cards yields the same first 5 as drawing 5). Randomness comes from
HMAC-SHA256(laneKey, "fy:" + counter), not from the lane key hex itself.
The counter starts at 1 and increments on every sample attempt, including rejections.
Step D — Who gets which card?
The initial deal and every hit pull the next card from the relevant lane’s precomputed stream
(cursor 0, 1, 2, …). The dealer always uses lane 0; box b uses
lane b. The audit log dealtCards[] records
{ lane, cursor, to, box, rank, suit } so you can replay every card.
Worked example — dealer lane, nonce 10
Common pitfall: the HMAC-SHA512 output (dealerKey) is
not “the cards in hex”. You have to run Step C on it. And don’t confuse
clientCommit = SHA-256(clientSeed) with the lane key — the commit only
proves the seed was locked in before the reveal.
Verification formulathe five checks, condensed
Audit JSON schemav2 is current — v1 still accepted
Current games export version 2: it adds pinned, dealtCards
and per-box seats[]. The in-game Copy proof button exports
kind: "seat-lane-proof" (a single seat); that shape is accepted too, as is
version 1’s clients[].
The shoe formula takes the plaintext client seed, not its hash — the hash is only
for commitment verification. Client seed order does not matter: the verifier applies
sort(), exactly as the server does.
Advanced tool
Fisher–Yates — derive cards from a lane keyseeds → cards, no audit JSON needed
Paste a lane key (a 128-hex HMAC-SHA512 output) and run the same partial Fisher–Yates the server uses. No commitments, no audit JSON — just seeds → cards. Or compute the lane key from seeds first, then draw. After a full verify, the → FY tool button on any lane fills the key in automatically.
This is dealerKey / seatKey / the audit field combinedSeed —
not the raw client seed, and not SHA-256(clientSeed).
HMAC-SHA512(serverSeed, "dealer:" + sort(seeds).join(",") + ":" + nonce)
HMAC-SHA512(serverSeed, "seat:" + box + ":" + clientSeed + ":" + nonce)