Fairness audit

Fairness Audit — Mental-Poker Shuffle

Verdict: PASS — the shuffle is statistically indistinguishable from a uniformly random 52-card permutation at significance level α = 0.001.


1. What this proves

A poker deal is only fair if, after the shuffle, every card is equally likely to land in every position and no coalition short of all players can predict or bias the outcome. This audit measures exactly that property on the shuffle produced by @poker/engine's multiparty ceremony.

For each of 200,000 independent hands we run the full ceremony: an ordered deck is trivially encrypted under the players' aggregate public key; each player in turn re-encrypts and permutes the whole deck (shuffleDeck); then the deck is fully decrypted using every player's decryption share (decryptionShare + combineShares) and decoded back to card indices. We tally, for every (position, card) pair, how often card v landed at position p, and test that distribution against the uniform expectation with a Pearson chi-square goodness-of-fit test.

2. Trust model (n-of-n)

The deck is encrypted under the aggregate key P = Σ Pᵢ of all seated players. Decryption requires every player's share, so:

This audit reflects both properties. In particular the one-honest-player result below is generated with one seat shuffling honestly and the other applying the identity permutation — the deck is still uniformly mixed.

3. Scope and honesty

This audit certifies the uniformity of the shuffle given honest re-encryption: that the ceremony's re-encryption-and-permute step, followed by full decryption, yields a uniform permutation and never corrupts the deck. It does not by itself force players to re-encrypt honestly on-chain — that is the job of the zero-knowledge verifiable-shuffle proof (Bayer–Groth) checked at the verifyShuffle seam and enforced by the on-chain arbiter. That ZK component is a separate, independently specified part of the system; this report is the statistical half of the fairness claim, not the whole of it.

Method note: crypto vs. fast path

The EC-ElGamal re-encryption shuffle is a plaintext-preserving bijection: the decrypted order depends only on the composed permutations, never on the encryption blinding. We exploit this to make a large sample tractable. A 6-hand sample is run through the full cryptographic round-trip and compared hand-for-hand against the integer permutation composition; they matched: yes. The large 200,000-hand statistical sample is then computed over the verified-equivalent integer path. The committed test suite re-runs both and asserts the equivalence, so the statistics stand on the real ceremony, not a simplified model.

4. Results

Validity

Uniformity — pooled chi-square goodness-of-fit

QuantityValue
Chi-square statistic (X²)2654.42
Degrees of freedom2652
Expected count per cell3846.15
Critical value (α = 0.001)2882.78
p-value4.831e-1
ResultPASS (X² < critical)

The 52×52 table of (position, card) counts is tested against a flat expectation of 3846.15 per cell. Degrees of freedom = 52 × 51 = 2652 (one linear constraint per position row). Because each hand is a permutation the position rows are weakly dependent, so this pooled statistic is a conservative summary rather than an exact joint law; the per-position tests below are exact goodness-of-fit tests.

Uniformity — per-position summary

One honest player suffices

With one seat shuffling honestly and the other applying the identity permutation:

5. Reproduce

# Deterministic gate (fast; asserts the thresholds below):
pnpm --filter @poker/engine run test

# Regenerate this report (larger sample):
pnpm --filter @poker/engine run fairness:report -- --seed 1337 --rounds 200000

Because the audit is fully seeded, re-running with the same seed reproduces these numbers exactly. The committed test src/tests/fairness.test.ts pins seed 1337, 20,000 hands and α = 0.001.

Machine-readable results: latest.json.