52!

Provably fair

How to check that the deck was decided before you played

In any online game where the server knows the answer, there is one question worth asking: could the server be choosing the answer after seeing my guess? For a game whose whole point is that a certain result is impossible, that question matters more than usual — a result nobody can verify is not really a result.

52! uses a commit–reveal scheme so you do not have to trust us on that. You can check any round yourself, with tools you already have.

The problem, stated plainly

The shuffle has to happen on the server. If it happened in your browser, the deck would be sitting in memory where anyone could read it, and every score on the leaderboard would be worthless. So the server shuffles — which means the server knows the order before you do, and you cannot see it.

A dishonest server could exploit that in either direction: it could quietly make sure you never do well, or make a favoured player do suspiciously well. Both are invisible from the outside unless the server is made to commit to something first.

The scheme

  1. Commit. When your round starts, the server shuffles two decks and generates a random nonce (16 random bytes). It then publishes a commitment: the SHA-256 hash of the deck plus the nonce. You receive the hash. You cannot read the deck out of it — that is what a cryptographic hash is for.
  2. Play. You turn cards. The server already knows the answer, but so does the hash you are holding, and the hash was fixed before you touched anything.
  3. Reveal. When the round ends, the server sends you the actual deck order and the nonce.
  4. Verify. Your browser recomputes SHA-256 over the revealed deck and nonce and checks it against the commitment from step 1. If they match, the deck you were shown at the end is exactly the deck that existed at the start.

The security rests on SHA-256 being resistant to second preimages: to cheat, the server would have to find a different deck order that hashes to the same commitment. No such method is known for SHA-256, and finding one by trial would take longer than the universe has existed.

What exactly gets hashed

The input string is the 52 card IDs in shuffled order, comma-separated, then a pipe, then the nonce:

AS,7H,QD,…,2C|3f9a1c7e2b8d4a6f0e5c1d9b7a3f2e8c

Card IDs are rank followed by suit letter: AS is the ace of spades, 10H the ten of hearts, 2C the two of clubs. The commitment is the SHA-256 of that string, in lowercase hexadecimal.

Checking a round yourself

Open your browser’s developer tools on the game page, play a round, and look at the network responses. POST /api/attempts returns the commitment before you play; POST /api/guesses returns the deck and nonce afterwards. Then run this in the console — it is the same computation the game does:

const input = deck.join(',') + '|' + nonce;
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
[...new Uint8Array(buf)].map(b => b.toString(16).padStart(2,'0')).join('');

The result must equal the commitment you saw before playing. The same check works from a terminal:

printf '%s' "AS,7H,…,2C|3f9a…" | sha256sum

The game already does this for you, every round. If a commitment ever failed to verify, the round would be rejected in your browser rather than shown as a result. This page is here so you can repeat the check independently rather than trust that we run it.

What this does and does not prove

It proves:

It does not, by itself, prove:

A fully trustless version would require the player to contribute randomness to the shuffle as well — a client seed mixed into the server seed. That is a reasonable future addition and it is not implemented today.

The other half: making results count

Commit–reveal stops the server cheating. The opposite problem — a player replaying a round after the deck has been revealed, and submitting a perfect guess — is handled separately: each attempt has a unique ID that is locked in the database the first time a guess is submitted for it, inside the same atomic transaction that records the score. A second guess on the same attempt is rejected outright.

And because a round in which all 52 cards match is a 1-in-8×1067 event, any such result is flagged and reviewed by hand before it is ever counted. See the maths for why that number justifies the suspicion.