Skip to main content
Audits
LIQD.com/Mines
Provisional audit

Not yet certified. Pre-launch tests passed; final certification requires public launch and an independent recheck of live bets using anonymous, self-funded accounts.

LIQD: Mines Audit

Mines

Provisional

Independent verification, backed by open-source code.

PF-2026-LQ02Audited July–October 2026 · Framework 1.1 · Release v1.0.1July–October 2026 · Framework 1.1 · v1.0.1
AUDIT RESULTPassedPassed 22/22 · Hard fails 0 · Flags 0VERDICT: PROVABLY FAIR — Full Pass, as printed by npm test.
Live Bets Verified
6,900
Parity Rate
100%
Simulated Rounds
24M
Simulated RTP 98.9427%
Proven RTP
99.0000%
22 / 22Verification steps passed

Verification pipeline

Real bets. Independently reproduced.

Captured on LIQD’s pre-launch environment. We rebuilt the game logic from scratch and matched all 6,900 recorded outcomes.

LIQD MINES$0.10 · 3 mines · 25 tiles · Cash out at 5
Safe revealMine
Bet #9pcR2FW8GQmNQ4VusJYDdNonce 0 · Phase E
50 real bets · Phase EBet 1 of 50
✓ Exact match
1.9974×

The casino result, reproduced.

Casino outcome
5 safe reveals · Cashed out
Our reproduction
5 safe reveals · Cashed out
Recorded payout
$0.1997
Open source

Run this audit yourself

Open GitHub ↗

The repo is the credential. You don’t have to trust us — every scored check ships as code. Run npm test to re-run the full audit: 49 unit tests and the 22 verification steps — 6,900 live bets and the 1,000-round supplement re-verified, all 24M simulated rounds replayed — with nothing installed but Node.js 22.

npm testCopy includes clone & installView reproduction commands ↓
Mines Audit Overview

This audit independently validates the Mines game operated by LIQD under the ProvablyFair.org Audit Framework 1.1. We placed 6,900 real bets across 138 seed epochs — every mine count, two stakes, ten custom client seeds and a multi-reveal phase — and recomputed every outcome with our own implementation, written from a specification without LIQD’s code. A 1,000-round interactive multi-reveal supplement, captured on 3 October 2026, is scored alongside it. The capture ran on LIQD’s QA (pre-production) environment, qa.liqd.com, so the certificate is provisional: it extends to liqd.com after an anonymous production capture passes review.

Results at a glance

Audit Verdict

CheckResultEvidence
Overall StatusPassPROVABLY FAIR — Full Pass · Passed 22/22 · Hard fails 0 · Flags 0. Provisional: captured on LIQD’s QA (pre-production) environment, qa.liqd.com; certification extends to liqd.com after an anonymous production capture passes review
RTP VerifiedPass99.0000% Proven RTP · 98.9427% simulated (24M rounds) · RTP claim rule: LIQD’s published 99% holds on all 300 settings, worst gap 0.000000 pp against the ±0.1 pp band (RTP-CLAIM 1.2.0, Step 10)
Live ↔︎ Verifier ParityPass100% — 6,900 / 6,900 mine layouts reproduced in draw order, and the ProvablyFair.org verifier matches the same 6,900 (Step 5); 1,000 / 1,000 multi-reveal supplement rounds recompute (Step 22)
Commit-Reveal SystemPass138 / 138 revealed seeds hash to their commitments; the next-seed chain is consistent on 137 / 137 rotations plus the pre-capture link into epoch 0 (Steps 1–3)
Client SeedPassPlayer-controlled and customizable — a wrong client seed changes 6,891 / 6,900 layouts, and 500 / 500 custom-seed bets recompute (Steps 6, 14)
RNG AnalysisPassOur own implementation, written from the specification: HMAC-SHA256 Fisher-Yates on a 25-tile grid, values at or above maxFair rejected — worst-case modulo bias 0 (Step 5)
Payout LogicPassAll 6,900 settlements verified — 3,104 / 3,104 wins credit floor8(stake × exact multiplier) with zero tolerance and 3,796 losses pay 0 (Step 8); 393 / 393 supplement wins settle at the same rule (Step 22)
Anti-CircularityPasswinChance × exact multiplier = 0.99 at all 300 (mine count, reveals) settings from combinatorics alone; a counting anchor matches the closed form on 110 cases with integer equality (Steps 10, 13)
Fairness IntegrityPass31 fairness integrity tests across 11 categories — 27 pass, 4 remediated (payout precision, Medium — full-precision crediting, re-verified across the audited capture; input hygiene, fixed by LIQD; recovery under aggressive disconnects, clean on a broader re-test), none open
DeterminismPassFull reproducibility confirmed — 6,900 / 6,900 rounds recompute from the recorded inputs (Step 5)
Overall StatusPass
PROVABLY FAIR — Full Pass · Passed 22/22 · Hard fails 0 · Flags 0. Provisional: captured on LIQD’s QA (pre-production) environment, qa.liqd.com; certification extends to liqd.com after an anonymous production capture passes review
RTP VerifiedPass
99.0000% Proven RTP · 98.9427% simulated (24M rounds) · RTP claim rule: LIQD’s published 99% holds on all 300 settings, worst gap 0.000000 pp against the ±0.1 pp band (RTP-CLAIM 1.2.0, Step 10)
Live ↔︎ Verifier ParityPass
100% — 6,900 / 6,900 mine layouts reproduced in draw order, and the ProvablyFair.org verifier matches the same 6,900 (Step 5); 1,000 / 1,000 multi-reveal supplement rounds recompute (Step 22)
Commit-Reveal SystemPass
138 / 138 revealed seeds hash to their commitments; the next-seed chain is consistent on 137 / 137 rotations plus the pre-capture link into epoch 0 (Steps 1–3)
Client SeedPass
Player-controlled and customizable — a wrong client seed changes 6,891 / 6,900 layouts, and 500 / 500 custom-seed bets recompute (Steps 6, 14)
RNG AnalysisPass
Our own implementation, written from the specification: HMAC-SHA256 Fisher-Yates on a 25-tile grid, values at or above maxFair rejected — worst-case modulo bias 0 (Step 5)
Payout LogicPass
All 6,900 settlements verified — 3,104 / 3,104 wins credit floor8(stake × exact multiplier) with zero tolerance and 3,796 losses pay 0 (Step 8); 393 / 393 supplement wins settle at the same rule (Step 22)
Anti-CircularityPass
winChance × exact multiplier = 0.99 at all 300 (mine count, reveals) settings from combinatorics alone; a counting anchor matches the closed form on 110 cases with integer equality (Steps 10, 13)
Fairness IntegrityPass
31 fairness integrity tests across 11 categories — 27 pass, 4 remediated (payout precision, Medium — full-precision crediting, re-verified across the audited capture; input hygiene, fixed by LIQD; recovery under aggressive disconnects, clean on a broader re-test), none open
DeterminismPass
Full reproducibility confirmed — 6,900 / 6,900 rounds recompute from the recorded inputs (Step 5)
Audit scope & reference

What Was Audited

✓ Audited

  • The RNG is deterministic: every mine layout reproduces from server seed, client seed, nonce and mine count
  • Server seeds are committed by SHA-256 hash, and each rotation pre-commits the next seed’s hash
  • The client seed is player-controlled and changes the layout
  • Nonces run 0–49 in every seed epoch with no gaps or reuse
  • Mine positions come from an HMAC-SHA256 Fisher-Yates shuffle with bias-free rejection sampling
  • Single- and multi-reveal wins credit the exact multiplier, floored at the 8th decimal; the on-screen multiplier is floored to 2 decimals
  • The mine layout is fixed at round start and unchanged by reveal order or cash-out timing
  • Proven RTP is 99.0000% at all 300 (mine count, reveals) settings (flat 1.00% house edge)
  • LIQD’s published 99% RTP holds on all 300 settings under the RTP claim rule (±0.1 pp)
  • Mine layouts follow the same wager-free derivation at both captured stakes, $0.10 and $10
  • Players can independently verify every bet

What Audit Excludes

  • Infrastructure or server security
  • Wallet, payments, or operational systems outside game logic
  • Cross-account sampling
What Audit Covers
2 columns
AreaDescription
Commit-Reveal SystemSHA-256 server seed hashing, pre-bet commitment, next-seed pre-commitment, reveal on rotation
Client Seed OriginPlayer-controlled seed set after the server commitment — our own random seeds in 128 epochs and 10 custom seeds in Phase D
Seed HandlingClient seed control, nonce lifecycle, seed epoch rotation
RNG AnalysisHMAC-SHA256 Fisher-Yates verification, rejection sampling, bias analysis
Payout LogicClosed-form multiplier, exact-value crediting at the 8th decimal, multi-reveal cash-outs (Phase E and the 1,000-round supplement), bet-size invariance (Phase C)
Live ParityIndependent mine-layout recomputation vs live results across all capture phases, plus the ProvablyFair.org verifier on the same 6,900 rounds
RTP ValidationAnti-circularity proof, the RTP claim rule over all 300 settings, simulated RTP (24M rounds), cherry-pick test on the served-config return
Fairness IntegrityIntegrity tests — 31 across 11 categories, including race conditions, input validation, idempotency, authorization, outcome and payout fields, payout precision, nonce and seed handling, and the provably fair surface
What Audit Guarantees
  • Outcomes are deterministic and reproducible from the recorded inputs
  • Live game results match independent recomputation for the verified sample — 6,900 / 6,900 audited rounds and 1,000 / 1,000 supplement rounds
  • The first-draw, pairwise and serial screens find no sign of placement bias across 24M rounds simulated with our implementation
  • RTP is proven analytically: winChance × exact multiplier = 0.99 at all 300 (mine count, reveals) settings, by combinatorial cancellation
  • Simulated RTP of 98.9427% across 24 million rounds (1M per mine count) is consistent with the proven 99.0000%, 1.66 standard errors below it
  • The mine layout is fixed at round start and independent of which tiles the player reveals
  • The client seed is a genuine, player-controlled input — a wrong seed changes 6,891 of 6,900 layouts
  • The house edge is a flat 1.00% at every setting — every captured win is credited at the exact multiplier, floored at the 8th decimal ($0.000005868 across $2,670 staked)
  • All fairness integrity tests passed or were remediated and re-verified at audit time — none is open

References

Mines — Game Rules7 sections▶

How LIQD Mines plays, exactly as verified by this audit — the interactive board below is wired to the audited multiplier formula.

Game Rules

Mines is played on a 5×5 grid of 25 tiles. Before the round starts you choose how many mines — 1 to 24 — are hidden on the board; the platform places them server-side with an HMAC-SHA256-driven Fisher-Yates shuffle seeded by (serverSeed, clientSeed, nonce). You then reveal tiles one at a time: every safe reveal grows the multiplier, and you can cash out after any of them. Reveal a mine and the round ends at zero. The layout is fixed before your first click — your choices decide how far you push, never where the mines are.

How to Play

1. Choose mine count — select between 1 and 24 mines. More mines means fewer safe tiles and a steeper multiplier curve.
2. Place your bet — the stake is locked and the layout is already fully determined by the active seed pair and nonce.
3. Reveal tiles — each safe tile bumps the multiplier to the exact closed-form value for your depth.
4. Cash out or bust — bank stake × exact multiplier, floored at the 8th decimal, at any point; opening a mine ends the round at zero.
5. Verify — after rotating your seed pair, recompute any round from the revealed seed (Section 6 walks it).

Timing note: cash-out is available after every safe reveal — there is no minimum depth. The payout keys off the number of tiles you actually opened: verified live at depths 1 and 5 in the audited dataset and at depths 2, 3, 4, 5, 6, 8, 10 and 15 across 20 settings in the multi-reveal supplement; every other depth follows from the same closed form.
Win Conditions

A round settles one of two ways, and this audit re-derived the label for all 6,900 captured rounds:

3 columns
OutcomeConditionExample (mines=3, k=5)
Win (cash out)You cash out with no mine revealedFive safe tiles → 1.99736842× exact, 1.99× displayed
Loss (bust)You reveal a mine — always the final tile you openedMine on the fifth pick → payout 0
ImpossibleA loss with no mine opened, or a win with oneNever observed — 0 violations in 6,900 rounds
The win/loss label is not taken on trust: it is recomputed from the seed-derived board for every round in the dataset.
Risk vs Reward

Every setting returns the same 99.0000% — the mine count changes the shape of the risk, never the fairness of the price:

  • Low mine counts (1–5): frequent small wins — at 1 mine a single reveal wins 96% of the time for 1.03125×
  • Mid counts (6–15): balanced curves — each extra reveal multiplies the odds against you and the payout for you, in exact proportion
  • High counts (16–24): rare big hits — at 24 mines one safe tile pays 24.75× on a 1-in-25 chance
  • Depth is the second dial: at 3 mines, five reveals lifts the multiplier from 1.125× to 1.99736842×
Parameters
3 columns
ParameterValueNotes
Grid25 tiles (5×5)Fixed
Mine count1 – 24Player-selected per round; all 24 captured live
House edge1.00% flatApplied once inside the exact multiplier — no depth scaling
Reveal depth1 – (25 − mines)Cash out after any safe reveal; depths up to 15 exercised live
Seed epochUntil you rotateThe audit rotated the pair every 50 bets (nonces 0–49)
Settlement8-decimal unit, rounds downCapture-wide shortfall $0.000005868 — disclosed in S4
Seed Formats

The three verification inputs, exactly as they appear in LIQD's panel and the bet record:

4 columns
Seed TypeFormatExamplePurpose
Server seed32-char hex (16 bytes)f6f3c5efd80a81c02a4033a3a370e6b9HMAC key (decoded); committed via SHA-256 of the string before play
Client seedfree-form stringaudit4d3ce43da272Player-editable entropy — mixed into every draw
Nonceinteger from 00The bet counter under the active pair (0–49 in this capture)
Two hashing domains: the commitment hashes the hex string (UTF-8); the HMAC keys the decoded bytes. Both conventions are verified in S1–S2.
Multiplier & Payout

The payout is closed-form — no paytable file exists. Raw fair odds times the single 1% edge:

multiplier(k, m) = C(25, k) / C(25−m, k) × 0.99
3 columns
Reveals (k)Mines (m)Multiplier
111.03125000
131.12500000
331.47857143
531.99736842
553.39258901
12424.75000000
Each win is credited stake × the with-edge multiplier, floored at the 8th decimal; the multiplier shown on the result screen is floored to 2 decimals. Checked on all 3,104 captured wins and the 393 supplement wins.
Why Provably Fair Matters▶

Traditional online casinos require players to trust that games are fair. Provably fair systems eliminate that requirement — three properties, all verified in this audit, make trust unnecessary:

  • The casino commits to the inputs that determine a result before you play — the SHA-256 commitment locks the server seed while it is still secret
  • A source of randomness exists that the casino cannot predict — your client seed, editable after the commitment is published
  • Anyone can verify the outcome after the fact — the revealed seed plus public code reproduces every round exactly
High-Level Overview8 sections▶

The whole system in one pass — from commitment to payout, with the capture design that tested it.

Checklist Reference

Commit-Reveal

2 columns
CheckWhat it proves
Server seed hash shown before playWith the hash shown first, the casino cannot change the layout after you bet
SHA-256 of the revealed seed matches the commitmentThe revealed seed is the one that was committed
Next epoch's hash pre-committed at rotationThe casino cannot mint a fresh seed in response to your play
One commitment per epoch, no mid-epoch switchingEvery bet is pinned to a single verified commitment

Seed System

2 columns
CheckWhat it proves
Client seed is player-editable after the commitment existsAn unpredictable seed you set after the commitment cannot be pre-evaluated
Client seed changes the layout when substitutedYour seed genuinely enters every derivation
Nonces run contiguously 0–49 in every epochNo skipped bet where a discarded outcome could hide

RNG & Parity

2 columns
CheckWhat it proves
Layout recomputes from (serverSeed, clientSeed, nonce, mineCount)No hidden inputs influence the board
Rejection sampling below maxFairThe specified draw makes every tile exactly equally likely
24M-round bias screens clean at BonferroniThe screens find no sign of placement bias at scale

Payout & RTP

2 columns
CheckWhat it proves
Wins credit floor8(stake × exact multiplier) exactlyThe advertised 1.00% edge is the real edge
Display field = floor2(exact), never above the creditThe on-screen number can only understate your payout
winChance × exact multiplier = 0.9900 at all 300 settingsThe RTP is an identity, not a sampled estimate

Integrity

2 columns
CheckWhat it proves
Losses pay exactly 0 and bust on the final opened tileNo round settles against a board that didn't bust
Same wager-free derivation at $0.10 and $10.00Both captured stakes follow the same layout rule
Dataset SHA-256 pinned (a mismatch stops the suite); every output artifact pinned and re-hashed (Step 19)The evidence itself is tamper-evident
High-Level Flow

One round of LIQD Mines, from commitment to settlement:

1. Commit — the server generates a 32-hex server seed and publishes SHA-256(utf8(seed)) as its Active Server Seed (Hashed). You see only the hash.
2. Seed pair active — your client seed (platform default or your own) and a nonce starting at 0 pair with the committed seed.
3. Bet — you pick a mine count and stake. The layout is already fully determined by (serverSeed, clientSeed, nonce).
4. Play — each safe reveal grows the exact multiplier; a mine ends the round. The mine set never moves mid-round.
5. Settle — cash-outs credit stake × exact multiplier, floored at the 8th decimal; the panel displays the multiplier floored to 2 decimals. The nonce increments by 1.
6. Rotate & verify — rotation reveals the outgoing seed (and pre-commits the next). Check the hash, recompute any round.

High-Level Flow — illustrated flow
Provably Fair Model

The model splits control of the randomness. The operator controls the server seed but must commit to it before play; the player controls the client seed and can change it after seeing the commitment; the nonce is a public counter. Because the layout is a pure function of those three inputs plus the mine count, neither side can steer an outcome without the other side's input — and everything needed to check the result is disclosed by design.

Commit-Reveal Model

Three phases, each independently verified by this audit:

Commit-Reveal Model — illustrated flow

Commit phase
The server generates a 32-hex seed and publishes SHA-256 of that string (UTF-8), recorded in the dataset as hashedServerSeed. The raw seed stays secret. Verified for all 138 epochs (Step 1).

Bet phase
Bets settle under the committed pair; the nonce increments 0–49. The layout is fixed per (seed, clientSeed, nonce) before any reveal. Verified by full recomputation (Step 5).

Reveal phase
Rotation reveals the outgoing seed and pre-commits the next epoch's hash — the chain is consistent on 137 / 137 transitions, plus the pre-capture link into epoch 0 (Step 2).

Verify phase
Anyone re-hashes the reveal against the commitment and recomputes every round it served — the panel, the ProvablyFair.org verifier, or the standalone scripts in S6 all work.

Player-Controlled Client Seed

The client seed is the mechanism that structurally contains server-seed cherry-picking — when it is unpredictable and set after the commitment — and this audit exercised the control live:

  • Editable at any time in the fairness panel — including immediately after a new commitment is published
  • Mixed into every HMAC message (clientSeed:nonce:cursor) — it mathematically cannot be ignored
  • Phase D played 500 rounds under 10 auditor-chosen pfaudit- seeds — all recomputed exactly
  • Whole-dataset control: a wrong client seed changes 6,891 of 6,900 layouts
Nonce Lifecycle

The nonce is the bet counter under an active pair. In this capture the audit rotated the pair after every 50 bets: the nonce starts at 0 and increments by 1 per bet. A gap or reuse would be the fingerprint of a discarded outcome — the audit found neither, anywhere.

  • Contiguous 0–49 in every one of the 138 epochs
  • 0 gaps, 0 duplicates, 0 resets across 6,900 bets
  • The window is declared in code (src/pins.ts) and each seed record’s nonceStart / nonceEnd is checked against it (Step 4)
seed record · nonce window · Verified
"epoch": 131,"nonceStart": 0,"nonceEnd": 49,"hashedServerSeed": "140cbc408754457f6767662eb1a02b5a…"
Determinism Guarantee

Determinism is what turns a fairness claim into a checkable fact: the same inputs must always produce the same board. LIQD's derivation is a pure function — verified by reproducing every captured round with an independent implementation, byte-for-byte in draw order.

the pure function · Verified
revealMines(serverSeed, clientSeed, nonce, mineCount)  → [tile, tile, …]   // the mines, in draw order — nothing else in, nothing else out
Phase Map

The capture is engineered — five phases, each isolating one claim, plus a multi-reveal supplement:

3 columns
PhaseRoundsWhat it isolates
A4,800The full grid — all 24 mine counts at 200 bets each, $0.10, 1 tile
B1,000The highest-variance tail — mineCount 24, thickened for the statistics
C200Stake independence — the identical game at $10.00
D500Client-seed influence — 10 auditor-chosen seeds, live
E400The multi-reveal payout path — 5 planned tiles at mineCount 3
Supplement1,000Interactive multi-reveal — 20 settings (mine counts 1, 3, 5, 8, 12 × 4 depths, up to 15 tiles), captured 3 October 2026 and scored by Step 22
Why phases matter: a random sample measures averages, but a designed capture tests mechanisms. Each phase removes one degree of freedom that an unfair implementation would need — all 24 mine counts at volume, the high-variance tail thickened, stake varied 100×, ten auditor-chosen seeds, 400 deep multi-reveal rounds, and 1,000 interactive cash-outs across 20 settings.
Technical Glossary7 categories▶

Every term used in this report, defined the way the audit uses it.

Core Concepts
2 columns
TermDefinition
Provably FairA cryptographic scheme in which the game outcome can be independently verified by the player after the fact, using seeds committed to before the round. The player cannot alter the server seed; the server cannot alter it after committing to its hash; neither party can change the mine layout without detection.
Commit-RevealThe mechanism underlying provably fair. The server publishes the seed’s hash (a commitment, hashedServerSeed in the dataset) before any bet, then reveals the plaintext serverSeed after the seed rotates. The auditor checks SHA-256(utf8(serverSeed)) == hashedServerSeed for every epoch. All 138 revealed seeds in this dataset passed.
House EdgeThe operator’s cut. LIQD’s game settings list houseEdge 1 (in percent) — an advertised label. The effective edge is derived independently and is a clean 1.00%, because the credited multiplier is exact — settlement rounds down only at the platform’s 8-decimal unit (the largest observed shortfall is 8.571e-9 per win; see S4), not at the 2 decimals the display field shows.
RTP (Return to Player)1 − houseEdge. For LIQD Mines the proven RTP is a flat 99.0000% at all 300 (mine count, reveals) settings, because the exact multiplier makes the win probability and the multiplier’s fair factor cancel. Proven analytically (anti-circular combinatorics); the 24M-round simulation is consistent with it. Live-bet RTP (99.5361%) is informational only.
Anti-CircularityThe requirement that the proven RTP and simulation expectations use independent first-principles combinatorics (C(25−m,k)/C(25,k)), never operator-supplied probability tables — so the RTP claim cannot validate itself.
RTP Claim RuleThe rule that holds a casino to the RTP it publishes: the exact RTP at every setting the game offers must sit within ±0.1 percentage points of the published figure. A setting below that band on the house side reads RTP FIGURE OVERSTATED (exit code 5). Applied as RTP-CLAIM 1.2.0, scored on Step 10.
Seed System
2 columns
TermDefinition
Server SeedA 32-character (16-byte) hexadecimal string generated by LIQD. Kept secret during betting, revealed after epoch rotation. Used as the HMAC key in its hex-decoded form: Buffer.from(serverSeed, 'hex').
Commitment / Server Seed HashSHA-256(utf8(serverSeed hex string)) — the text-hash convention: LIQD hashes the UTF-8 bytes of the 32-character hex string, not the 16 decoded bytes. Published before any bet under the pair. A revealed seed that fails this check would prove tampering. Distinct from the HMAC key, which uses the decoded bytes.
Client SeedA string controlled by the player (or generated by the platform), mixed into the HMAC message as ${clientSeed}:${nonce}:${cursor}. Changing it changes every mine layout under the same server seed. When the player sets an unpredictable seed after the server-seed commitment is published, it is the primary structural defence against server-seed cherry-picking; a predictable seed does not deliver that protection.
NonceAn incrementing integer identifying a bet’s position within an epoch, starting at 0. Part of the HMAC message. The audit rotated every 50 bets, so every epoch in this dataset has contiguous nonces 0–49 — no gaps.
EpochThe bets under one (serverSeed, clientSeed) pair, from one rotation to the next; rotating reveals the outgoing server seed. The audit rotated every 50 bets (nonces 0–49); this dataset covers 138 epochs.
Cryptographic Functions
2 columns
TermDefinition
HMAC-SHA256Hash-based Message Authentication Code using SHA-256. Produces a 256-bit (32-byte) deterministic output from a key and message. Here it generates the uniform integer for each shuffle swap: the digest is scanned in 4-byte big-endian uint32 chunks, the first chunk below the modulo-bias ceiling is taken % range.
Fisher-Yates ShuffleThe standard in-place algorithm for producing a uniformly random permutation. Mines shuffles the 25-tile array [1…25], drawing one uniform integer per position; the first mineCount tiles of the completed shuffle are the mines, in draw order.
CursorIn Mines the cursor is the draw index: the 0-indexed shuffle position (0…24). One HMAC call is made per cursor, driving one swap of the Fisher-Yates shuffle. The draw range shrinks 25 → 1 as the cursor advances.
Modulo-Bias RejectionDiscarding uint32 values at or above maxFair = floor(2³²/range)·range before taking % range, so every tile is exactly equally likely at each shuffle step. Without it, low tiles would be marginally over-represented.
Verification Terms
2 columns
TermDefinition
Win ProbabilityC(25−m, k) / C(25, k) — pure combinatorics: the probability that all k opened tiles are safe when m mines are placed uniformly. Computed independently of any operator-supplied probability (anti-circular).
Exact MultiplierThe multiplier LIQD credits: (1 − houseEdge) × Π_{i=0}^{k-1} (25−i)/(25−m−i), applied at full precision. The product is the reciprocal of the probability of clearing k safe tiles; (1 − 0.01) applies the 1% edge. Every winning winningAmount equals floor8(stake × exact) with zero tolerance (Step 8).
Displayed MultiplierThe multiplier field in LIQD’s settle response, shown on the result screen (E14), floored to 2 decimals (floor(exact × 100)/100). Cosmetic: it understates the credited amount (player-favourable) and is not used to compute the payout. Distinct from the exact multiplier.
Game Mechanics
2 columns
TermDefinition
Mine Count (mineCount)The number of mines placed on the grid, an integer in [1, 24]. The only game-specific input; there are no risk levels or variants. 24 configurations in total.
Mine Tiles (mineTiles)The mine positions on the 25-tile grid, recorded and verified in draw order. Compared with an ordered array equality, not a set comparison — the order the mines were drawn is part of the verified output.
Reveal / Open TileA tile the player opens. Each safe reveal raises the multiplier; opening a mine busts the round. reveals is the planned count, openTiles the ordered list. Phases A–D open 1 tile; Phase E opens up to 5; the supplement opens up to 15.
Audit Terms
2 columns
TermDefinition
First-Draw UniformityA simulation test: the first drawn mine tile is a single uniform outcome per round, so a chi-squared with df = 24 against N/25 is correct here (unlike the full per-tile layout). Directly tests the HMAC-mod-25 draw. 0/24 fail at Bonferroni across 24M rounds.
Pairwise Co-Occurrence (Joint) TestFor every unordered tile pair {i, j}, how often both are mines, compared against the hypergeometric fair expectation N × C(23, m−2)/C(25, m). Catches joint bias that per-tile marginals would miss. Defined for m ≥ 2; 0/23 fail at Bonferroni.
Constrained MultinomialThe mine-placement distribution: exactly m mines among 25 tiles, so the per-tile counts are negatively correlated and are not 25 independent cells. This is why a naive per-tile chi-squared is the wrong test and the three targeted properties are used instead.
Chi-Squared TestCompares observed frequencies to expected under a null. Here p-values come from the exact regularized incomplete gamma function (src/stats.ts), not the Wilson-Hilferty approximation. Cells with expected count < 5 are pooled inward from the two ends before the test — at 50 draws the 25 cells become 21 (expected [6, 2×19, 6], 20 degrees of freedom); interior cells are not pooled.
Bonferroni CorrectionControls the family-wise error rate across many simultaneous tests by tightening the per-test threshold to α / k. For 24 configs, α/24 = 4.167e-4. All three simulation families report 0 failures at this threshold.
Lag-1 AutocorrelationPearson correlation between consecutive values in a sequence; ≈ 0 for an independent RNG. The mixed-config live series shows r₁ = 0.3548. The capture groups bets by configuration, so adjacent bets share a mineCount and thus a win rate, and the live series reflects that grouping. The per-config serial screen in simulation Pass 1 tests the generator itself.
Wald-Wolfowitz Runs TestA non-parametric serial-independence test counting runs of like outcomes against the count expected under independence. Applied per config to the first-drawn tile values in simulation Pass 1.
Parametric BootstrapThe method used to build the Pass 2 early-window null: 10,000 fair replicates of 50 first-draws per mineCount give the empirical distribution of the first-draw chi² under fairness, because the analytic chi² is invalid at only 50 draws. Every replicate seed derives from a recorded master seed (mines-seed-derivation-v1), so the null itself replays — Step 17 rebuilds all 240,000 statistics on every run.
Cherry-Picking (Server-Seed)The residual risk in any beacon-free commit-reveal scheme: an operator with foreknowledge of the upcoming client seed could pre-evaluate candidate server seeds and commit to an unfavourable one. Contained structurally when the operator commits before learning an unpredictable client seed. Pass 2 adds two statistical checks — the served-config return test (0 / 138 epochs flagged, pooled z = 0.723) and a first-draw diagnostic (4 flags against 6.555 expected, P = 0.8976) — which find no sign of seed selection.
Selective OrphaningA distinct residual channel: an operator observing a round server-side and orphaning or re-issuing an unfavourable result rather than surfacing it. In this capture the nonces run contiguously 0–49 in all 138 epochs (Step 4). An external-entropy beacon does not close the channel in general; independently verifiable acceptance and settlement records — an externally anchored append-only log, or player-signed bet acknowledgements — do.
Hard FailAny FAIL on a scored step — a broken commitment or chain link, a layout, label or payout mismatch, an undeclared nonce gap, a population or artifact-integrity fault. A hard fail ends the verdict as NOT PROVABLY FAIR (exit 1). None triggered: 22 PASS, 0 FLAG, 0 FAIL.
Served-Config TestThe load-bearing cherry-pick statistic: each epoch’s 50 served configurations are replayed over its revealed seed for 10,000 nonces, and the return of the captured window (nonces 0–49) is ranked against the seed’s own 199 later windows. 0 / 138 epochs flagged at p ≤ 0.01; pooled z = 0.723 (Step 17).
Capture FlagA gap the capture itself declares — for example a nonce skipped by a capture retry — reported as a disclosed flag that does not move the verdict. A gap the capture does not declare hard-fails the nonce audit. This capture declares none (Step 4).
Data Formats
2 columns
TermDefinition
winningAmountThe settle-response field holding the amount credited on a win (floor8(betAmount × exactMultiplier)) or 0 on a loss. The audit’s payout evidence; wallet balances were not recorded.
Phase A / B / C / D / EThe five capture phases. A: 4,800 bets, all 24 mineCounts × 200 (full grid, 1 tile). B: 1,000 bets, mineCount 24 (deep high-variance sample). C: 200 bets at $10 (stake independence, mineCount 3). D: 500 bets, 10 custom pfaudit- client seeds. E: 400 bets, mineCount 3, 5 tiles planned (multi-reveal).
Multi-Reveal SupplementA separate 1,000-round capture of 3 October 2026, played interactively (place bet → open tiles → cash out) across 20 settings at mine counts 1, 3, 5, 8 and 12; hash-pinned with its journal and scored by Step 22.
01

Seed, Nonce & Determinism

Can the casino change your outcome after you bet?

Every Mines round on LIQD is generated from three inputs: server seed, client seed, and nonce. LIQD commits to its server seed by publishing a SHA-256 hash before the seed is used — and pre-commits the next epoch’s hash at every rotation. After you rotate your seed, the server reveals the actual seed, and anyone can check it against the hash. A matching hash shows which seed goes with which bets and in what order, and our capture records show each hash was published before its bets — so the casino cannot change the mine layout after you bet.

Commit-Reveal Cryptographic Guarantee

138 / 138seeds verified

What we verified

  • Every revealed server seed hashes to the commitment published for its epoch — 138 / 138
  • The next server seed’s hash is pre-committed at every rotation — the chain is consistent on 137 / 137 links, plus the pre-capture link into epoch 0
  • Client seed is player-controlled and set after the server commitment
  • Players can set or change their client seed at any time via the fairness panel
  • Nonce increments by 1 per bet with no gaps or duplicates across 138 seed epochs — 0 capture flags
  • Mine positions are fully determined by the seed inputs before any tile is revealed
  • Identical inputs always produce the same mine layout — confirmed across all 6,900 bets, in draw order
  • Your client seed is a genuine input — changing it changes the mine layout

What this means for you

  • With the commitment shown first, the casino cannot change the mine layout after you bet
  • Set your own unpredictable client seed after the commitment is shown, and the server cannot pre-select against it
  • Every bet has its own derivation — the nonce guarantees no two bets share the same inputs
  • Any result can be independently verified using the public tools and repo
  • Outcomes stay verifiable long after the round — the seeds and the code are all you need
  • Server-seed cherry-picking is structurally contained when you set an unpredictable client seed after the commitment is published — the operator cannot pre-evaluate server seeds against a seed it cannot predict
138 / 138seeds verified
  • Every revealed server seed hashes to the commitment published for its epoch — 138 / 138
  • The next server seed’s hash is pre-committed at every rotation — the chain is consistent on 137 / 137 links, plus the pre-capture link into epoch 0
  • Client seed is player-controlled and set after the server commitment
  • Players can set or change their client seed at any time via the fairness panel
  • Nonce increments by 1 per bet with no gaps or duplicates across 138 seed epochs — 0 capture flags
  • Mine positions are fully determined by the seed inputs before any tile is revealed
  • Identical inputs always produce the same mine layout — confirmed across all 6,900 bets, in draw order
  • Your client seed is a genuine input — changing it changes the mine layout
HMAC-SHA256 Pipeline — seed inputs through a sequential Fisher-Yates shuffle to the mine layout, in draw order — illustrated flow
CheckResultEvidence
Seed hash integrityPass138 / 138 revealed seeds hash to their commitments — SHA-256 of the seed text, 0 mismatches (Step 1)
Next-seed chainPassConsistent on 137 / 137 rotations, plus the pre-capture commitment that links into epoch 0 (Step 2)
Recorded orderPassOn the capture’s own clock, each epoch’s commitment was recorded before its first bet — 138 / 138 (Step 2)
One commitment per epochPassAll 50 bets in each of 138 epochs carry the same commitment, and LIQD’s own serverSeedId is one value per epoch, 138 distinct (Step 3)
Nonce sequencingPassNonces 0–49 in every epoch: 0 gaps, 0 duplicates, 0 capture flags (Step 4)
Client seed originPassSet after the server commitment — our own random audit… seeds in 128 epochs and 10 custom pfaudit- seeds in Phase D
Client seed controlPassPlayer can set or change the client seed at any time — 500 / 500 Phase D bets recompute under their custom seeds (Step 14)
Client seed influencePassA wrong client seed changes 6,891 / 6,900 layouts; an empty one reproduces only 7 / 6,900 (Step 6)
Deterministic outputPassSame (serverSeed, clientSeed, nonce, mineCount) always produces the same layout — 6,900 / 6,900 confirmed, in draw order (Step 5)
Seed hash integrityPass
138 / 138 revealed seeds hash to their commitments — SHA-256 of the seed text, 0 mismatches (Step 1)
Next-seed chainPass
Consistent on 137 / 137 rotations, plus the pre-capture commitment that links into epoch 0 (Step 2)
Recorded orderPass
On the capture’s own clock, each epoch’s commitment was recorded before its first bet — 138 / 138 (Step 2)
One commitment per epochPass
All 50 bets in each of 138 epochs carry the same commitment, and LIQD’s own serverSeedId is one value per epoch, 138 distinct (Step 3)
Nonce sequencingPass
Nonces 0–49 in every epoch: 0 gaps, 0 duplicates, 0 capture flags (Step 4)
Client seed originPass
Set after the server commitment — our own random audit… seeds in 128 epochs and 10 custom pfaudit- seeds in Phase D
Client seed controlPass
Player can set or change the client seed at any time — 500 / 500 Phase D bets recompute under their custom seeds (Step 14)
Client seed influencePass
A wrong client seed changes 6,891 / 6,900 layouts; an empty one reproduces only 7 / 6,900 (Step 6)
Deterministic outputPass
Same (serverSeed, clientSeed, nonce, mineCount) always produces the same layout — 6,900 / 6,900 confirmed, in draw order (Step 5)
✓  Commit-reveal verified

All 138 revealed seeds match their commitments, and the next-seed chain is consistent across 137 rotations plus the pre-capture link into epoch 0. Nonces run 0–49 in every epoch with no gaps. Outcomes are fully deterministic — the same server seed, client seed, nonce and mine count always produce the same layout. With each commitment shown before its bets, as our capture records show, the casino cannot change your result after you bet.

How It Works — Seed, Nonce & Determinism7 sections
1.1Server Seed Commitment

Before any bet is placed under a seed pair, LIQD shows the SHA-256 hash of a secret 32-character hex server seed as its Active Server Seed (Hashed), recorded in the dataset as hashedServerSeed. The commitment hashes the seed text as a UTF-8 string, not its 16 decoded bytes: SHA-256(utf8(serverSeed)). When the pair rotates, the server seed is revealed and anyone can recompute the hash. A match shows the revealed seed is the committed one: a substitute would need a second preimage of SHA-256, and would fail every recomputation that follows.

Server Seed Commitment — illustrated flow
src/rng.ts · commitHash · Verified
/** SHA-256 of a byte buffer, lowercase hex. */export function sha256Buffer(buf: Buffer): string {  return createHash('sha256').update(buf).digest('hex');} /** * §1.2: the published commitment is SHA-256 over the seed's characters as * UTF-8 text (the ASCII string itself), not over the bytes the hex encodes. * Output is lowercase hex. */export function commitHash(serverSeedHexString: string): string {  return sha256Buffer(Buffer.from(serverSeedHexString, 'utf8'));}
Result: 138 / 138 revealed server seeds hash to their published commitment (Step 1). Zero mismatches.

Real Example from Live Data:

mines-master-6900bets.json · seed record (epoch 131), selected fields
{  "epoch": 131,  "phase": "E",  "clientSeed": "audit4d3ce43da272",  "hashedServerSeed": "140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27",  "nextHashedServerSeed": "fc05c3fc452c5c760597cde92169ff7b583b4430f234431948f72854d55d1ea9",  "serverSeed": "f6f3c5efd80a81c02a4033a3a370e6b9",  "nonceStart": 0,  "nonceEnd": 49}

Verification:

verify-seed.js · Verified
// verify-seed.js — check any revealed LIQD server seed against its commitmentconst { createHash } = require('node:crypto');const serverSeed = 'f6f3c5efd80a81c02a4033a3a370e6b9';            // revealed 32-hex seed (epoch 131)const committed  = '140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27';const recomputed = createHash('sha256').update(serverSeed, 'utf8').digest('hex');console.log(recomputed === committed ? 'MATCH — seed is the committed one' : 'MISMATCH');
1.2Next-Seed Pre-Commitment Chain

At every seed rotation, the nextHashedServerSeed LIQD pre-commits during an epoch must equal the hashedServerSeed that becomes active after the rotation, so the successor seed is fixed before the previous epoch ends. Step 2 walks all 137 epoch transitions and checks each link, and also checks the pre-capture record: the seed active before the first audited bet (893be227…) hashes to its own commitment and its pre-committed next hash is epoch 0’s hashedServerSeed (7ec1c408…). LIQD’s own seeds panel shows the pre-committed next pair beside the active one (evidence screenshots, S6). A hash chain shows the values are consistent and fixes their order; it does not carry a date. Step 2 therefore also reads the capture’s own clock: on 138 / 138 epochs the commitment was recorded before the epoch’s first bet, and every rotation was recorded after the retiring epoch’s last bet. Those capture records show each commitment was published before the bets it covers.

tests/steps/commitment.ts · Step 2 · Verified
  // ── Step 2: Next-seed pre-commitment chain ──────────────────────────────────  // Each epoch's nextHashedServerSeed must equal the following epoch's hashedServerSeed.  const byEpoch = [...seeds].sort((a, b) => a.epoch - b.epoch);  let promoChecked = 0, promoFails = 0;  for (let i = 0; i + 1 < byEpoch.length; i++) {    promoChecked++;    if (byEpoch[i].nextHashedServerSeed !== byEpoch[i + 1].hashedServerSeed) promoFails++;  }  // …  const preRevealOk = !!pc && typeof pc.revealedServerSeed === 'string'    && commitHash(pc.revealedServerSeed) === pc.hashedServerSeed;  const preChainOk = !!pc && typeof pc.nextHashedServerSeed === 'string'    && pc.nextHashedServerSeed === firstEpochHash;  // …    const commitOk = commitRec.next === s.hashedServerSeed && tAt(commitRec.at) < firstBet;    const recordOk = tAt(s.at) < firstBet;  // …  const s2 = step(2, 'Next-Seed Pre-Commitment Chain',    promoFails === 0 && promoChecked === expectedTransitions && preOk && chronoOk ? 'PASS' : 'FAIL',
Result: 137 / 137 next-seed transitions consistent, plus the pre-capture link into epoch 0; on the capture clock, 138 / 138 commitments recorded before their epoch’s first bet and 137 / 137 rotations after its last (Step 2).
1.3Hash Consistency Within an Epoch

Each bet record carries its epoch’s hashedServerSeed — the commitment recorded when the pair became active, which the capture writes onto every row. The hash arm of Step 3 groups the 6,900 bets by epoch and confirms that all 50 bets in each epoch carry the same commitment. LIQD’s own settle responses carry a seed reference, serverSeedId, and Step 3 checks it too: one value across every bet of an epoch, and 138 distinct values across the 138 epochs. More than one value within an epoch would indicate a mid-epoch seed substitution. What binds each bet to its seed is the recomputation: the seed record’s hash matches the reveal (Step 1), and the revealed seed reproduces every layout in the epoch (Step 5).

tests/steps/commitment.ts · Step 3 · Verified
  // ── Step 3: Hash consistency within epoch ────────────────────────────────────  // Group by epoch (NOT by hash) so this can genuinely FAIL if any epoch mixed  // hashedServerSeed values. Grouping by hash would be tautological.  const betsByEpoch = new Map<number, typeof bets>();  for (const b of bets) {    const arr = betsByEpoch.get(b.epoch) ?? [];    arr.push(b);    betsByEpoch.set(b.epoch, arr);  }  let epochsMultipleHashes = 0;  for (const [, epochBets] of betsByEpoch) {    const hashes = new Set(epochBets.map(b => b.hashedServerSeed));    if (hashes.size !== 1) epochsMultipleHashes++;  }  // …  const distinctIds = new Set(idOfEpoch.values()).size;  const idsOk = idMissing === 0 && epochsMultipleIds === 0 && idOfEpoch.size === EXPECTED_SEEDS && distinctIds === EXPECTED_SEEDS;
Result: 138 / 138 epochs — one commitment and one LIQD serverSeedId across all 50 bets of every epoch, 138 distinct serverSeedId values. Zero mid-epoch changes (Step 3).
1.4Client Seed Origin & Control

Players have full control over their client seed through the LIQD fairness panel, allowing them to view, modify, or rotate it at any time — including after the server-seed commitment is already published. The client seed is an input to every HMAC-SHA256 computation — changing it produces a completely different mine layout. LIQD generates a default value, and the server commits to its seed before the client seed for that epoch is set, which structurally contains cherry-picking when that seed is unpredictable: an operator that cannot know the upcoming client seed cannot pre-evaluate candidate server seeds against it. In this capture 128 epochs used auditor-random “audit”-prefixed seeds; the 10 Phase D seeds follow a timestamp pattern (pfaudit-<timestamp>-<epoch>), chosen to show a custom seed is consumed exactly as entered; Step 17’s cherry-pick test covers their epochs too. During capture the audit exercised this control directly:

  • Player-editable at any time via the fairness panel — no wager required to change it
  • Rotatable after the server-seed commitment is published — the commitment is fixed before a seed you set then
  • Mixed into every draw as the HMAC message clientSeed:nonce:cursor — never decorative
  • Phase D set 10 auditor-chosen pfaudit- seeds; all 500 rounds recompute under them (Step 14)
  • Whole-dataset controls: a wrong seed changes 6,891 / 6,900 layouts; an empty seed reproduces only 7 / 6,900 (Step 6)
Result: Client seeds are player-controlled and vary across all phases and epochs — our own audit… seeds in 128 epochs (e.g. audit4d3ce43da272 in epoch 131, audit59c2b0bf86aa in epoch 8), all of the disclosed CSPRNG form and all distinct (Step 6), plus the 10 custom pfaudit- seeds of Phase D. When an unpredictable seed is set after the commitment exists, the server cannot pick a server seed against it. Evidence: mines-master-6900bets.json
1.5Nonce Incrementation

The nonce begins at 0 and increments by 1 for each bet under the same seed pair. In Mines, the nonce advances once per round — every bet uses one nonce value, shared across all reveals within that round. The nonce resets to 0 when the player rotates their seed, starting a new epoch. In this audit we rotated every 50 bets as our chosen sampling cadence (nonces 0–49 per epoch, 138 epochs); epoch length is not enforced by the casino — players can rotate at any time. Step 4 audits every epoch for gaps, reuse, reordering and resets: a skipped nonce is where a discarded unfavourable outcome would hide, so the sequence must be contiguous. The window 0–49 is declared in code (src/pins.ts), and each seed record’s nonceStart / nonceEnd is checked against it. A gap the capture itself declares — a capture-retry gap — is a capture flag: disclosed, and it does not move the verdict; any other gap hard-fails. This capture declares none and has none.

Nonce Incrementation — illustrated flow
tests/steps/commitment.ts · Step 4 · Verified
  // ── Step 4: Nonce audit ──────────────────────────────────────────────────────  // Per epoch: a single client seed, and the nonces EXACTLY NONCE_BASE … NONCE_BASE+EPOCH_SIZE−1  // with no gap, repeat or reorder.  // …  const windowStart = NONCE_BASE;  const windowEnd = NONCE_BASE + EXPECTED_EPOCH_SIZE - 1;  // …  for (const [hash, epochBets] of byHash) {    const sorted = [...epochBets].sort((a, b) => a.nonce - b.nonce);    const nonces = sorted.map(b => b.nonce);    // …    const clientSeeds = new Set(sorted.map(b => b.clientSeed));    if (clientSeeds.size !== 1) {      hardFailures.push(`Epoch ${epochNum}: ${clientSeeds.size} distinct client seeds`);    }    // …    const nonceSet = new Set(nonces);    // Nonce-reuse guard: a duplicated nonce leaves the window looking complete, so uniqueness is    // asserted directly — Set.size must equal the bet count or a reused nonce is invisible.    if (nonceSet.size !== sorted.length) {      hardFailures.push(`Epoch ${epochNum}: ${sorted.length - nonceSet.size} duplicate nonce(s) (reuse)`);    }    // …    const missing: number[] = [];    for (let n = windowStart; n <= windowEnd; n++) if (!nonceSet.has(n)) missing.push(n);    // …  }  // …  if (gapCount !== EXPECTED_CAPTURE_RETRY_GAPS) {    hardFailures.push(`${gapCount} nonce gap(s) found, ${EXPECTED_CAPTURE_RETRY_GAPS} capture-retry gap(s) declared (src/pins.ts EXPECTED_CAPTURE_RETRY_GAPS) — an undeclared gap is not attributable to the capture`);  }
Result: 0 gaps, 0 duplicates, 0 capture flags across all 138 epochs. Nonces run 0–49 contiguously in every epoch — 6,900 bets in a complete, ordered ledger, one client seed per epoch (Step 4).
1.6Deterministic Mapping

The RNG is fully deterministic: given the same server seed, client seed, nonce and mine count, revealMines always produces the exact same mine layout. The algorithm is a sequential Fisher-Yates shuffle of the tiles [1 … 25] with HMAC-SHA256 — one draw per swap position — returning the first mineCount tiles in draw order. No timing, no bet amount, no UI state enters the derivation. Our own implementation, written from the specification, reproduced all 6,900 captured mineTiles arrays with an order-sensitive comparison. The bet record below is the featured bet used throughout this report — recompute it yourself with the snippet.

Deterministic Mapping — illustrated flow
src/rng.ts · revealMines · Verified
export function revealMines(serverSeed: string, clientSeed: string, nonce: number, mineCount: number): number[] {  if (!Number.isInteger(mineCount) || mineCount < 1 || mineCount > GRID - 1) {    throw new RangeError(`mineCount must be an integer in 1..${GRID - 1}, got ${mineCount}`);  }  const a: number[] = [];  for (let t = 1; t <= GRID; t++) a.push(t);  for (let i = 0; i < GRID; i++) {    const r = GRID - i;    const j = i + generateProvablyFairNumber(serverSeed, clientSeed, nonce, i, r);    const tmp = a[i] as number;    a[i] = a[j] as number;    a[j] = tmp;  }  return a.slice(0, mineCount);}
Result: All 6,900 bets with revealed seeds: the sequential Fisher-Yates recomputation matches mineTiles in draw order (Step 5). Zero mismatches, zero skipped. No external entropy is mixed into the Mines RNG.

Real Bet Verified:

mines-master-6900bets.json · bet 9pcR2FW8GQ… (epoch 131, nonce 0), selected fields · Verified
{  "id": "9pcR2FW8GQmNQ4VusJYDd",  "phase": "E",  "epoch": 131,  "nonce": 0,  "mineCount": 3,  "clientSeed": "audit4d3ce43da272",  "mineTiles": [    14,    24,    1  ],  "openTiles": [    2,    3,    4,    5,    6  ],  "result": "won",  "multiplier": 1.99,  "winningAmount": 0.19973684}

Verification:

verify-mines.js · Verified
// verify-mines.js — recompute a LIQD Mines layout from its seedsconst { createHmac } = require('node:crypto');function draw(seed, client, nonce, cursor, range) {  const digest = createHmac('sha256', Buffer.from(seed, 'hex'))    .update(`${client}:${nonce}:${cursor}`).digest();  const maxFair = Math.floor(0x1_0000_0000 / range) * range;  for (let o = 0; o + 4 <= digest.length; o += 4) {    const chunk = digest.readUInt32BE(o);    if (chunk < maxFair) return chunk % range;  }  throw new Error('all 8 chunks rejected: not specified (docs/ALGORITHM.md O1)');}const mines = Array.from({ length: 25 }, (_, i) => i + 1);for (let c = 0; c < 25; c++) {  const j = c + draw('f6f3c5efd80a81c02a4033a3a370e6b9', 'audit4d3ce43da272', 0, c, 25 - c);  [mines[c], mines[j]] = [mines[j], mines[c]];}console.log(mines.slice(0, 3)); // → [14, 24, 1]
1.7Client Seed Influence

To confirm the client seed is a genuine input to the HMAC-SHA256 computation, Step 6 recomputes the entire dataset — not a sample — under two control seeds. With a fixed wrong client seed, 6,891 of 6,900 bets (99.87%) produced a different mine layout; with an empty client seed, only 7 of 6,900 (0.10%) reproduced the recorded layout. Those survivors are chance collisions, mostly at mine count 1 (a single mine among 25 tiles): the model predicts 9.27 in draw order, and every survivor count sits inside its gate. Phase D goes further: 10 auditor-chosen pfaudit- client seeds across 500 live rounds, all 500 recomputing exactly (Step 14).

tests/steps/determinism.ts · Step 6 · Verified
  // ── Step 6: Client seed influence (whole-dataset controls) ────────────────────  // Recompute EVERY bet's layout under (a) an empty client seed and (b) a fixed wrong client seed,  // and count how many still reproduce the captured mineTiles — ordered (draw order, what Step 5  // compares) and as a set. The survivor counts are gated at the rate the MODEL predicts: E + 5σ  // of chance collisions, E = Σ 1/P(25,m) ordered and Σ 1/C(25,m) as a set over the tested bets'  // own mine counts. 0% change is a hard fail by construction (every bet would survive).  // …  for (const b of bets) {    const ss = seedMap.get(b.hashedServerSeed);    if (!ss) continue;    tested++;    testedMineCounts.push(b.mineCount);    const empty = revealMines(ss, '', b.nonce, b.mineCount);    const wrong = revealMines(ss, WRONG_CLIENT, b.nonce, b.mineCount);    if (sameMines(empty, b.mineTiles)) {      emptyReproduce++;      if (b.mineCount === 1) emptyMineCount1++;    }    if (sameMines(wrong, b.mineTiles)) wrongReproduce++;    // …  }
Result: 6,891 / 6,900 bets (99.87%) produce a different layout with an alternate client seed; only 7 / 6,900 (0.10%) survive an empty seed. Client seed is a genuine, material input.
Technical Evidence & Verification5 sections
1.8Evidence Coverage Summary6 checks
3 columns
Verification AreaCoverageResult
Seed hash integrity (Step 1)138 / 138 revealed seeds hash-verifiedPass
Next-seed pre-commitment chain and recorded order (Step 2)137 / 137 transitions; 138 / 138 commitments recorded before their first betPass
Hash and serverSeedId consistency (Step 3)138 / 138 epochs (50 bets each)Pass
Nonce audit (Step 4)0 gaps, 0 duplicates, 0 capture flags across 138 epochsPass
Mine-layout recomputation (Step 5)6,900 / 6,900 — full layout verified, in draw orderPass
Client seed influence (Steps 6, 14)6,891 / 6,900 (99.87%) changed by a wrong seed; 500 / 500 Phase DPass
1.9Code References7 source files
2 columns
FilePurpose
tests/verify.ts22-step verification runner (Steps 1–6 cover S1)
tests/steps/commitment.tsSteps 1–4: commit-reveal integrity, recorded order and nonce audit
tests/steps/determinism.tsSteps 5–6: mine-layout recomputation and client seed influence
src/rng.tsHMAC-SHA256 sequential Fisher-Yates (revealMines, generateProvablyFairNumber, commitHash)
docs/ALGORITHM.mdThe written specification src/rng.ts is built from
src/loader.tsDataset loading + SHA-256 hash guard
capture/capture-mines.reference.mjsSanitized capture methodology record (Phases A–E)
1.10Datasets UsedSource artifacts
Primary: data/mines-master-6900bets.json
2 columns
PropertyValue
SourceLive Mines game data from LIQD’s QA (pre-production) environment, qa.liqd.com — 6,900 real bets across 5 phases, USDC
Capture environmentQA (pre-production) host qa.liqd.com · one account (auditor-attested)
Total Records6,900 bets across 138 epochs (50 bets each)
Captured30 July 2026 (16:23–18:05 UTC)
SHA-256c7c2e8a930549e11e3e564a962aea0ca2875b6eb94bc1f4347cc703bc410b222

Fields used: serverSeed, hashedServerSeed, nextHashedServerSeed, serverSeedId, clientSeed, nonce, at, mineCount, mineTiles, openTiles, result, multiplier, winningAmount, betAmount

src/loader.ts recomputes the SHA-256 above and aborts the suite before any step runs on a mismatch. The capture methodology ships as a reference record (capture/capture-mines.reference.mjs — authentication removed; it documents the capture, it does not replay it). Every computational claim downstream of the dataset is reproducible from the file itself.

1.11Verified Invariants11 invariants
2 columns
InvariantResult
SHA-256(utf8(serverSeed)) = hashedServerSeed for all 138 revealed seedsPass
Next-seed pre-commitment chain consistent for all 137 transitions, plus the pre-capture link into epoch 0Pass
Each epoch’s commitment recorded before its first bet on the capture clock (138 / 138)Pass
hashedServerSeed constant within epoch for all 138 epochs — no mid-epoch seed switchingPass
LIQD’s serverSeedId: one value per epoch, 138 distinctPass
Zero nonce gaps within any epoch (contiguous 0–49), 0 capture flagsPass
Zero nonce duplicates within any epochPass
Every epoch contains exactly 50 bets (138 / 138)Pass
Same inputs produce same mine layout, in draw order, for all 6,900 betsPass
Wrong client seed changes the layout in 99.87% of bets (6,891 / 6,900)Pass
Empty client seed reproduces the layout in only 0.10% of bets (7 / 6,900)Pass
1.12Reproduction InstructionsRun locally

Clone the repository, install dependencies, and run the verification suite:

reproduce-s1.sh · 5 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-mines.gitcd liqd-mines && git checkout v1.0.1npm cinpm run verify# Expected output: Steps 1–6 all PASS
Steps 1–6 cover all Seed, Nonce & Determinism checks. Expected output:

  [PASS] Step 1 — Seed Hash Integrity
  [PASS] Step 2 — Next-Seed Pre-Commitment Chain
  [PASS] Step 3 — Hash Consistency Within Epoch
  [PASS] Step 4 — Nonce Audit
  [PASS] Step 5 — Mine-Layout Recomputation
  [PASS] Step 6 — Client Seed Influence
02

RNG & Entropy Model

Is the randomness genuinely random, or could it be rigged?

This section verifies that LIQD’s Mines layouts come from the disclosed inputs alone, through a construction that is unbiased by design: a sequential Fisher-Yates shuffle with HMAC-SHA256 on a 25-tile grid, each swap taking its index from a 4-byte HMAC chunk with rejection sampling below the maxFair ceiling. Our own implementation, written from a specification without LIQD’s code, reproduces all 6,900 live layouts; LIQD’s own validation vectors serve only as a cross-check.

Cryptographic Randomness Verification

24 / 24configs verified

What we verified

  • HMAC-SHA256 produces cryptographically sound, unpredictable output for each shuffle step
  • Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
  • The specified draw removes modulo bias exactly — every tile is equally likely at every draw of the implementation that reproduces the live layouts
  • The first-draw, pairwise and serial screens find no sign of placement bias across 24M rounds simulated with our implementation — zero corrected failures
  • The serial screens find no sign of dependence between consecutive first-drawn tiles
  • 99.87% of layouts change with a different client seed

What this means for you

  • Each mine position is drawn without modulo bias
  • The specified draw gives every tile the same chance of hiding a mine — no positional bias
  • No hidden randomness or server-side tricks influence where the mines are placed
  • Past results don’t affect future outcomes — each round has its own derivation
  • The algorithm depends only on seeds you can verify
24 / 24configs verified
  • HMAC-SHA256 produces cryptographically sound, unpredictable output for each shuffle step
  • Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
  • The specified draw removes modulo bias exactly — every tile is equally likely at every draw of the implementation that reproduces the live layouts
  • The first-draw, pairwise and serial screens find no sign of placement bias across 24M rounds simulated with our implementation — zero corrected failures
  • The serial screens find no sign of dependence between consecutive first-drawn tiles
  • 99.87% of layouts change with a different client seed
RNG Pipeline — HMAC-SHA256 → uint32 → rejection sampling → Fisher-Yates swap → draw-order mine layout — illustrated flow
CheckResultEvidence
RNG derived only from disclosed inputsPassHMAC-SHA256(serverSeed, clientSeed:nonce:cursor) — no hidden entropy
Entropy purityPassNo timestamps, external APIs, Math.random, or server-side state
Our own implementationPassWritten from the specification without LIQD’s code; reproduces all 6,900 layouts in draw order (Step 5)
LIQD’s validation vectorsPassLIQD’s own validation vectors pass 3 / 3 against our implementation — a cross-check, not the source
Modulo biasPassRejection ceiling discards the biased top of the 32-bit range (21 values at range 25) — 0 of the capture’s 172,500 draws reach it (2.024e-4 expected); the branch is exercised on a fixed vector (Step 5)
Key encoding verifiedPassServer seed hex-decoded to bytes for the HMAC key; the commitment hashes the seed text — both confirmed via 6,900-bet recomputation
Serial independencePassFirst-drawn tile: lag-1 and runs screens clear at Bonferroni for all 24 configs at 1M rounds each (Step 16)
Client seed influencePass99.87% of layouts change with an alternate client seed — confirmed across the full dataset
RNG derived only from disclosed inputsPass
HMAC-SHA256(serverSeed, clientSeed:nonce:cursor) — no hidden entropy
Entropy purityPass
No timestamps, external APIs, Math.random, or server-side state
Our own implementationPass
Written from the specification without LIQD’s code; reproduces all 6,900 layouts in draw order (Step 5)
LIQD’s validation vectorsPass
LIQD’s own validation vectors pass 3 / 3 against our implementation — a cross-check, not the source
Modulo biasPass
Rejection ceiling discards the biased top of the 32-bit range (21 values at range 25) — 0 of the capture’s 172,500 draws reach it (2.024e-4 expected); the branch is exercised on a fixed vector (Step 5)
Key encoding verifiedPass
Server seed hex-decoded to bytes for the HMAC key; the commitment hashes the seed text — both confirmed via 6,900-bet recomputation
Serial independencePass
First-drawn tile: lag-1 and runs screens clear at Bonferroni for all 24 configs at 1M rounds each (Step 16)
Client seed influencePass
99.87% of layouts change with an alternate client seed — confirmed across the full dataset
✓  Disclosed Inputs Only, Unbiased by Construction

Every captured layout comes from the disclosed inputs alone, and our own implementation — written from the specification — reproduces all 6,900. Its draw is unbiased by construction, and the first-draw, pairwise and serial screens find no sign of placement bias or serial dependence across 24M simulated rounds. The client seed is a genuine input — 99.87% of layouts change with a different seed.

How It Works — RNG & Entropy Model7 sections
2.1RNG Function Implementation

Each Mines bet places m mines on a 25-tile grid using a sequential Fisher-Yates shuffle. Our implementation iterates from position 0 to 24 (25 draws); only the first mineCount draws decide the mines. At each position, an HMAC-SHA256 digest is computed with the hex-decoded server seed as key and clientSeed:nonce:cursor as the message, where cursor equals the position. The 32-byte digest is scanned in up to eight 4-byte big-endian chunks; the first chunk below the maxFair ceiling is reduced % range to give the swap index. The specification leaves the case of all eight chunks rejected open; our implementation stops with an error there, and no captured draw comes near it. After all 25 swaps, the first mineCount tiles in draw order are the mines.

2 columns
ComponentDetail
Hash functionHMAC-SHA256 (32-byte digest)
KeyBuffer.from(serverSeed, 'hex') — 16 raw bytes (distinct from the commitment domain, which hashes the seed text)
MessageclientSeed:nonce:cursor — UTF-8, literal colons, no padding
Extraction4-byte big-endian chunks → readUInt32BE → uint32, up to 8 per digest
Rejectionchunk < maxFair where maxFair = floor(2³² / range) × range — bias-free ceiling per range
Reductionuint32 % range → offset in [0, range); swap position j = cursor + offset
Exhaustion pathAll 8 chunks rejected — no further draw defined; our implementation stops with an error. Below 1e-66 per draw, never reached
ShuffleSequential Fisher-Yates: position 0 to 24, cursor = position
OutputFirst mineCount tiles in draw order → mine layout
src/rng.ts · maxFairFor · drawDigest · drawDetail · Verified
/** * §2 step 2: LIMIT = floor(2^32 / r) * r. A chunk value v is accepted iff v < LIMIT. */export function maxFairFor(range: number): number {  assertRange(range);  return Math.floor(TWO_POW_32 / range) * range;} /** §2 step 1: the digest for one draw, keyed by the decoded server seed. */function drawDigest(serverSeed: string, clientSeed: string, nonce: number, cursor: number): Buffer {  const msg = getProvablyFairHmacSalt(clientSeed, nonce, cursor);  return createHmac('sha256', serverSeedKey(serverSeed)).update(Buffer.from(msg, 'utf8')).digest();}// …  const limit = maxFairFor(range);  const digest = drawDigest(serverSeed, clientSeed, nonce, cursor);  for (let c = 0; c < CHUNKS_PER_DIGEST; c++) {    const v = digest.readUInt32BE(c * CHUNK_BYTES);    if (v < limit) {      return { value: v % range, firstRejected: c !== 0 };    }  }  // §2 step 2 / O1: the spec gives no rule for all eight chunks rejected; the  // interface asks for a throw.  throw new Error(    `no accepted chunk in digest for nonce ${nonce}, cursor ${cursor}, range ${range}`,  );
Result: Our own implementation — written from the specification docs/ALGORITHM.md without LIQD’s code — matches all 6,900 live bets with zero layout mismatches (Step 5). LIQD’s own validation vectors pass 3 / 3 as a cross-check; LIQD publishes no algorithm specification of its own.
2.2Entropy Sources

All randomness derives exclusively from the deterministic HMAC-SHA256 function combining three cleanly separated inputs — each visible in the fairness panel or the bet record:

Entropy Sources — illustrated flow
3 columns
SourceControlled ByPurpose
Server SeedCasinoBase randomness — the HMAC key, committed via SHA-256 hash of the seed text before betting
Client SeedPlayerPlayer-contributed entropy — mixed into every HMAC message
NonceSystemUniqueness per bet (0–49 within each epoch, audited gap-free)
CursorSystemPer-draw isolation (0 to 24, one per Fisher-Yates position) — an index, not an entropy source
No mixed entropy sources detected. Run the same inputs multiple times — results are always identical. Timing, bet amount, balance, and session state are all absent from the derivation. 6,900/6,900 confirmed.

How we know: all 6,900 bets recompute from the declared inputs alone — a hidden input that moved any captured layout would show up as a mismatch, and none does.

Verified absent: No timestamps, no Math.random(), no external APIs, no server-side mutable state, and no external beacon (no drand round, block hash, or VRF). Only: HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce:cursor). Because no external entropy is used, cherry-pick containment is structural rather than cryptographic — the commitment plus the player-controlled client seed carry the fairness; S4 covers exactly what that means and how it is checked.

2.3Modulo Bias Analysis

Each draw's swap index is determined by chunk % range, where range runs from 25 down to 1 across the 25 Fisher-Yates positions. Modulo bias occurs when 2^32 is not evenly divisible by the range — the remainder classes are unevenly sized and low indices are favoured. The algorithm eliminates this bias entirely via rejection sampling: a ceiling maxFair = floor(2^32 / range) × range ensures only unbiased chunks are used. Any chunk ≥ maxFair is discarded. The worst case is the opening draw: range 25 has the most dead values (21):

range                = 25
2^32                 = 4,294,967,296
floor(2^32 / 25)     =   171,798,691
maxFair = 171,798,691 × 25 = 4,294,967,275
dead values          = 2^32 − maxFair = 21
P(chunk rejected)    = 21 / 2^32      ≈ 4.889e-9
draws in this capture    = 25 × 6,900 = 172,500
expected first-chunk rejections ≈ 2.024e-4
observed rejections      = 0  (census over all 172,500 draws, Step 5)
Result: Zero modulo bias in the draw: maxFair is a whole multiple of every range, so each accepted chunk maps to a uniform swap index for all 25 Fisher-Yates positions (Step 5).

Below maxFair, each of the 25 residues owns exactly 171,798,691 values — perfectly uniform. For every range in the shuffle, maxFair is a whole multiple of the range, so the swap index is exactly uniform once a chunk is accepted (Step 5 prints maxFair for all 25 ranges). The capture never reaches the rejection branch — 0 of 172,500 draws, against 2.024e-4 expected — so Step 5 exercises it on a fixed vector instead: nonce 760455, cursor 12, range 13 rejects its first chunk and draws 6, where deleting the branch would give 1. That confirms the branch draws exactly as specified; the capture, as expected, never needed it.

2.4RNG Isolation

Each draw within a single bet uses a unique cursor value in the HMAC message (clientSeed:nonce:cursor), ensuring per-draw outputs are cryptographically independent. Each bet uses a unique nonce, ensuring per-bet outputs are independent. HMAC-SHA256 is a pseudorandom function — knowing one draw's output gives no usable information about other draws’ outputs. There is no shared state between draws, between bets, or between epochs.

RNG Isolation — illustrated flow
Result: RNG isolation confirmed. No state leakage between draws, bets, or epochs. Each HMAC call is independently keyed and messaged.

Evidence: The revealMines implementation in 1.6 confirms this — it is a pure function with no class state, no external calls, and no cross-bet memory. It takes exactly four arguments (serverSeed, clientSeed, nonce, mineCount) — no wager, no timestamp, no account identifier — and returns an array. Same inputs always produce the same output; if any hidden input influenced the layout, the reference implementation (which lacks that input) could not reproduce 6,900 / 6,900 rounds. The strongest cross-phase evidence: Phase C repeats the game at $10.00 (100× the $0.10 stake) and Phase D swaps in ten auditor-chosen client seeds — both recompute under the identical four-argument function.

2.5Monte Carlo Simulation (24M Rounds)

A 24,000,000-round Monte Carlo simulation (1,000,000 rounds per config, all 24 mine counts, a fresh random seed pair per config, each pair kept in its result row) checks that the algorithm produces the expected mine-placement distribution at scale. The whole experiment replays from those saved seeds: every verify run regenerates all 24,000,000 rounds and recomputes every statistic (Step 16), and npm run deep-replay does the same stand-alone (24 rows, 0 differences).

2 columns
MetricValue
Rounds24,000,000 — 24 configs × 1,000,000, fresh seeds per config, replayed in full by every verify run
First-draw uniformity (χ², df = 24)0 / 24 configs fail at Bonferroni α/24
Pairwise co-occurrence (hypergeometric, max |z| over 300 pairs)0 / 23 configs fail (m ≥ 2); largest |z| = 3.72
First-drawn-tile serial independence (lag-1 + runs)0 / 24 at Bonferroni (1 / 24 uncorrected — ≈1.4 expected by chance)
Mean simulated RTP98.9427% vs the proven 99.0000% — 1.66 standard errors below
src/simulate.ts · simulation parameters · Verified
const ROUNDS_PER_CONFIG = REPLAY ? REPLAY.rounds : (Number(process.env.ROUNDS_PER_CONFIG) || 1_000_000);const PASS2_NONCES = REPLAY ? REPLAY.nonces : (Number(process.env.PASS2_NONCES) || 10_000);const PASS2_EARLY = 50;                 // real served depth per epoch (nonces 0..49)const BOOTSTRAP_REPS = REPLAY ? REPLAY.reps : (Number(process.env.BOOTSTRAP_REPS) || 10_000);
Result: 24M rounds simulated and replayed. All 24 configs pass the first-draw, pairwise and serial screens at Bonferroni; the screens find no sign of departure from the uniform Fisher-Yates expectation across the 25-tile grid (Step 16).

All 24 configurations pass the first-draw uniformity, pairwise co-occurrence and first-drawn-tile serial screens at the Bonferroni-corrected threshold. Zero corrected failures across 24M rounds.

Methodology: Mine placement is a constrained multinomial — exactly m mines among 25 tiles — so the 25 per-tile counts are negatively correlated and a naive per-tile chi-squared applies the wrong null (at high mine counts it is extremely conservative, though it still rejects gross biases). Pass 1 therefore tests three families that are each properly calibrated: the first-drawn tile is one uniform outcome per round (chi-squared, df = 24); every tile pair is a Binomial count against the hypergeometric C(23, m−2) / C(25, m) expectation, standardized and tested via max |z| with a per-pair Bonferroni correction; and the sequence of first-drawn tiles is screened for serial structure via lag-1 autocorrelation and the Wald-Wolfowitz runs test.

2.6Serial Independence

Serial independence means consecutive outcomes are not correlated — winning or losing on one bet does not affect the next. Two tests were applied to the first-drawn tile sequence of each configuration in the Pass 1 simulation (1,000,000 rounds per config). The screen is an OR of the two arms, so about 1.4 uncorrected trips across 24 configs are expected from a perfectly fair generator:

Lag-1 autocorrelation: Measures correlation between consecutive first-drawn tile values. Expected r ≈ 0 for independent sequences. One config (mineCount 15) trips the uncorrected α = 0.05 arm at |z| = 2.32 — within the ≈1.4 trips expected by chance; 0 / 24 fail at Bonferroni.

Wald-Wolfowitz runs test: Tests whether the sequence of above/below-median outcomes has the expected number of runs. p < 0.01 indicates non-random structure. Clear at every config — minimum p = 0.0715; no sequence shows clustering or alternation structure.

src/stats.ts · lag1Autocorrelation · Verified
export function lag1Autocorrelation(series: number[]): number {  const n = series.length;  let mean = 0;  for (let i = 0; i < n; i++) mean += series[i];  mean /= n;  let num = 0, den = 0;  for (let i = 0; i < n - 1; i++) num += (series[i] - mean) * (series[i + 1] - mean);  for (let i = 0; i < n; i++) den += (series[i] - mean) ** 2;  return den === 0 ? 0 : num / den;}
Result: 0 / 24 configurations fail serial independence at Bonferroni. Lag-1 autocorrelation values are near zero and every runs-test p-value is above 0.01 (minimum 0.0715). The screens find no sign of serial dependence in the first-drawn tile (Step 16).
2.7Worked Example — Full RNG Trace

Real bet from dataset — bet 9pcR2FW8GQmNQ4VusJYDd (Phase E, epoch 131), mineCount 3, nonce 0. Verified from mines-master-6900bets.json. With mineCount = 3, the first three draws are the mines:

serverSeed = f6f3c5efd80a81c02a4033a3a370e6b9
clientSeed = audit4d3ce43da272
nonce      = 0
mineCount  = 3
message    = audit4d3ce43da272:0:<cursor>   (one HMAC per cursor 0 … 24)
7 columns
icursorrangeHMAC[:8]uint32jswap
00253dba941c1,035,637,78813tile 14 → slot 0
11244f4918261,330,190,37423tile 24 → slot 1
2223551c6b571,427,925,84713tile 1 → slot 2
3322dfeac0823,756,703,8743tile 4 → slot 3
…4 … 2321 … 2………20 more swaps
24241c49858733,298,318,45124tile 22 → slot 24
Parity verified: Bet 9pcR2FW8GQ… — mine layout matches sequential Fisher-Yates recomputation exactly. The loop index and the cursor coincide (cursor = i); the first three draws decide the mines — [14, 24, 1], in draw order, exactly as the live game recorded.
Live Game[14, 24, 1]
Verifier[14, 24, 1]
Technical Evidence & Verification5 sections
2.8Evidence Coverage Summary8 checks
3 columns
Verification AreaCoverageResult
Our implementation (Step 5)6,900 / 6,900 rounds — written from the specification; its 98 specification checks passPass
Key encoding (hex vs UTF-8)HMAC key = the decoded bytes, confirmed by recomputing all 6,900 layouts (Step 5); commitment = SHA-256 of the seed text, confirmed on all 138 seeds (Step 1)Pass
Modulo bias analysismaxFair a whole multiple of every range 1–25; 0 of 172,500 captured draws reach the rejection branch (2.024e-4 expected); branch exercised on a fixed vectorPass
External entropy non-participation (Step 5)6,900 / 6,900 reproduced from disclosed inputs alonePass
First-draw uniformity (Step 16)0/24 configs fail at Bonferroni α/24Pass
Pairwise co-occurrence (Step 16)0/23 configs fail (m ≥ 2) — 300 tile pairs vs the hypergeometricPass
Serial independence (Step 16)0/24 configs fail at Bonferroni (lag-1 + runs on the first-drawn tile)Pass
Client seed influence (Steps 6, 14)6,891 / 6,900 (99.87%) + 500 / 500 Phase DPass
2.9Code References9 source files
2 columns
FilePurpose
src/rng.tsHMAC-SHA256 sequential Fisher-Yates (generateProvablyFairNumber, revealMines), written from docs/ALGORITHM.md
docs/ALGORITHM.mdThe written specification of the RNG, commitment and payout
src/bias.tsDraw-bias bound for every range, built on maxFairFor
src/simulate.ts · src/seed-derivation.tsMonte Carlo simulation (24M rounds, two passes) and the Pass 2 seed derivation
src/replay.ts · src/deep-replay.tsFull replay of the committed Pass 1 from its saved row seeds (inside every verify run, and stand-alone)
src/stats.tsChi-squared, max-|z| joint test, lag-1 autocorrelation, Wald-Wolfowitz runs test
tests/steps/determinism.tsSteps 5–6: mine-layout recomputation, draw-bias census and client seed influence
tests/steps/simulation.tsSteps 16–17: Pass 1 replay and scoring, Pass 2 re-derivation and bootstrap replay
tests/mines/rngTests.ts · rngSpecTests.ts · qaRegressionTests.ts49 unit tests — LIQD’s validation vectors and reference cases (10), the specification’s 98 vector and property checks (1), and the regression suite (38)
2.10Verified Invariants12 invariants
2 columns
InvariantResult
HMAC-SHA256 output matches live game for all 6,900 betsPass
Key is hex-decoded (16 bytes, not UTF-8) — wrong encoding produces wrong layoutsPass
Message format is clientSeed:nonce:cursor — literal colons, no paddingPass
Chunks scanned big-endian, 4 bytes, first accepted winsPass
Rejection sampling eliminates modulo bias for all ranges (1–25) — every residue below maxFair owns exactly floor(2³² / range) valuesPass
0 of 172,500 captured draws reach the rejection branch (2.024e-4 expected); the branch is exercised on a fixed vectorPass
No external entropy sources required for layout computationPass
First-draw uniformity screen clears for all 24 configs (1M rounds each, Bonferroni)Pass
Pairwise co-occurrence matches the hypergeometric expectation for all 23 configs with m ≥ 2 (max |z| 3.72)Pass
Lag-1 autocorrelation near zero and runs test p > 0.01 for all 24 configs at BonferroniPass
Pooled simulated RTP 98.9427% is consistent with the proven 99.0000% (1.66 standard errors below)Pass
Client seed change produces a different layout in 99.87% of bets (6,891 / 6,900)Pass
2.11Datasets UsedSource artifacts
Simulation: outputs/simulation-results.json — 24M rounds across 24 mine-count configs (Pass 1) + 138 casino seeds × 10K nonces (Pass 2)
Primary dataset: data/mines-master-6900bets.json — 6,900 live bets for mine-layout recomputation (Step 5)
Simulation output: outputs/simulation-results.json — Pass 1 (24M rounds, per-config first-draw, pairwise and serial tests, each row’s seed pair) + Pass 2 (138 casino seeds × 10K nonces, bootstrap null derived from the recorded master seed) · SHA-256 8854fc57e7f22f19…
Verification output: outputs/verification-results.json — the 22 scored steps, including Step 16’s full Pass 1 replay
Replay reports: outputs/deep-replay-report.json — 24 rows, 24,000,000 rounds recomputed from the saved Pass 1 seeds, 0 differences · outputs/bootstrap-replay-report.json — independent Python rebuild of all 240,000 Pass 2 null statistics and 138 early p-values, 0 mismatches
Replay inputs: outputs/simulation-provenance.json (master seed, derivation version, parameters, hashes) + outputs/bootstrap-nulls.float64le (the retained sorted null, 10,000 × 24, SHA-256 477c5826…)
Reference vectors: tests/mines/rngTests.ts, rngSpecTests.ts and qaRegressionTests.ts — 49 known-input unit tests: LIQD’s validation vectors, the specification’s vectors and properties, the draw, the shuffle, the commitment, the first-draw fast path, the exact/display multiplier grid, seed derivation and bootstrap-artifact validation
2.12Reproduction InstructionsRun locally

Clone the repository, install dependencies, and run the verification and the bootstrap replay:

reproduce-s2.sh · 5 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-mines.gitcd liqd-mines && git checkout v1.0.1npm cinpm run verify           # Step 16 replays all 24M Pass 1 rounds from their saved seedsnpm run bootstrap-replay # independent Python rebuild of the Pass 2 null (Python 3.9+)
S2-related steps:

  [PASS] Step 5 — Mine-Layout Recomputation
  [PASS] Step 6 — Client Seed Influence
  [PASS] Step 14 — Phase D — Client Seed Variation
  [PASS] Step 16 — Simulation — Pass 1 (First-Draw · Joint · Serial, FWER)
03

Verifier Parity

Does the live game actually follow its own rules?

This section validates that our implementation produces the exact same mine layout — in draw order — as the live game for every bet: all 92,010 mine positions across the 6,900 boards, at every mine count from 1 to 24. It also re-derives every win/loss label from the recomputed board and confirms every credited payout equals the exact multiplier, floored at the 8th decimal. A separate 1,000-round multi-reveal supplement is held to the same rules. Any mismatch would invalidate the fairness guarantee.

Live ↔︎ Verifier Parity

6,900 / 6,900bets matched

What we verified

  • Every bet independently recomputed from seeds — the full mine layout verified in draw order, not just the payout
  • Payout correctness: all 3,104 wins credit floor8(bet × exact multiplier); all 3,796 losses pay 0
  • Win/loss label re-derived from the recomputed layout for every round — 0 mislabelled settlements
  • Bet amount is not an input to the RNG — the layout depends only on seeds, nonce, and mine count
  • All five capture phases and the 1,000-round multi-reveal supplement recomputed identically

What this means for you

  • The verifier isn’t a simulation — it produces the exact same mine layout as the live game
  • Every bet you play can be independently recomputed by anyone
  • At both captured stakes the layout follows the same derivation, whichever tiles you open
  • The game served on LIQD’s QA environment matches the specification on every captured round
6,900 / 6,900bets matched
  • Every bet independently recomputed from seeds — the full mine layout verified in draw order, not just the payout
  • Payout correctness: all 3,104 wins credit floor8(bet × exact multiplier); all 3,796 losses pay 0
  • Win/loss label re-derived from the recomputed layout for every round — 0 mislabelled settlements
  • Bet amount is not an input to the RNG — the layout depends only on seeds, nonce, and mine count
  • All five capture phases and the 1,000-round multi-reveal supplement recomputed identically
Parity Verification Flow — seeds → recompute → compare → exact match — illustrated flow
6,900Live Bets Tested
100%Parity Rate
$0.10 & $10Bet Sizes Tested
0Mismatches
CheckResultEvidence
Mine-layout recomputationPass6,900 / 6,900 exact match — mine layout verified in draw order for every bet (Step 5)
ProvablyFair.org verifierPassThe site verifier’s Mines module, pinned by hash, reproduces 6,900 / 6,900 layouts in draw order (Step 5)
Payout correctnessPassAll 3,104 wins: credit = floor8(bet × exact multiplier) with zero tolerance; all 3,796 losses pay 0 (Step 8)
Displayed multiplierPassThe on-screen multiplier is the exact value floored to 2 decimals — cosmetic, and it can only understate what was paid
Reveal-sequence consistencyPassWins never open a mine; losses bust on exactly the final opened tile
Bet-size independencePassBet amount is absent from the RNG input — same layout at $0.10 and $10. Tested in Phase C (200 / 200)
Config completenessPassAll 24 mine counts (1–24) covered in live data — 200 bets each in Phase A
Multi-reveal coveragePass400 / 400 multi-reveal rounds recompute — 107 distinct tile sets, all 25 first picks exercised
Multi-reveal supplementPass1,000 / 1,000 layouts and 3,314 / 3,314 reveals recompute; 393 / 393 wins credited at floor8(stake × exact) and displayed at floor2(exact) (Step 22)
Mine-layout recomputationPass
6,900 / 6,900 exact match — mine layout verified in draw order for every bet (Step 5)
ProvablyFair.org verifierPass
The site verifier’s Mines module, pinned by hash, reproduces 6,900 / 6,900 layouts in draw order (Step 5)
Payout correctnessPass
All 3,104 wins: credit = floor8(bet × exact multiplier) with zero tolerance; all 3,796 losses pay 0 (Step 8)
Displayed multiplierPass
The on-screen multiplier is the exact value floored to 2 decimals — cosmetic, and it can only understate what was paid
Reveal-sequence consistencyPass
Wins never open a mine; losses bust on exactly the final opened tile
Bet-size independencePass
Bet amount is absent from the RNG input — same layout at $0.10 and $10. Tested in Phase C (200 / 200)
Config completenessPass
All 24 mine counts (1–24) covered in live data — 200 bets each in Phase A
Multi-reveal coveragePass
400 / 400 multi-reveal rounds recompute — 107 distinct tile sets, all 25 first picks exercised
Multi-reveal supplementPass
1,000 / 1,000 layouts and 3,314 / 3,314 reveals recompute; 393 / 393 wins credited at floor8(stake × exact) and displayed at floor2(exact) (Step 22)
✓  Live game and verifier fully aligned

All 6,900 bets matched our recomputation exactly — mine layouts verified across all five capture phases, and the ProvablyFair.org verifier agrees on every one. Payout math is exact at both stakes, with the exact multiplier credited on every win. The 1,000-round multi-reveal supplement settles all 393 of its wins at the same rule.

How It Works — Verifier Parity9 sections
3.1Why Parity Matters

If the verifier produces results that differ from the live game, players cannot trust the verification — the entire provably fair system becomes meaningless. Commit-reveal shows the seed didn’t change; parity shows the game the player experienced is the game the seed dictates. For every captured round the audit rebuilds the mine layout from raw inputs with our own code, re-derives the win/loss label from which tiles were opened, and recomputes the payout from the closed-form multiplier — then compares all three against what LIQD actually served and credited. 100% parity is required: a single mismatch anywhere in 6,900 rounds would indicate a bug in the verification logic, manipulation in the live game, or an inconsistent RNG implementation.

Why Parity Matters — illustrated flow
3.2Five-Phase Collection Design

Data was collected across five structured phases, each designed to test a specific fairness property. The phases are complementary — together they cover configuration breadth, the highest-variance edge case, bet-size invariance, client seed variation, and the multi-reveal payout path.

5 columns
PhaseBetsConfigBet AmountPurpose
A — Configuration coverage4,80024 configs (mineCount 1–24, 200 per config), 1 tile$0.10Verify formula correctness across every mine count
B — High-mine edge case1,0001 config (mineCount 24), 1 tile$0.10Maximum variance; only 1 safe tile in 25
C — Stake equivalence2001 config (mineCount 3), 1 tile$10.00Confirm bet amount is not an RNG input (100× the Phase A stake)
D — Client-seed variation500All 24 mineCounts cycled (20–21 each), 10 custom seeds$0.1010 custom pfaudit-prefixed client seeds; confirms seed control is player-side
E — Multi-reveal4001 config (mineCount 3), 5 tiles planned$0.10Multi-reveal payout path — five planned reveals (the supplement goes to 15)
Total: 6,900 bets across 138 seed epochs of exactly 50 bets each — phase labels and volumes verified by Steps 11 and 12. Total wagered: $2,670.00 ($480.00 Phase A + $100.00 Phase B + $2,000.00 Phase C + $50.00 Phase D + $40.00 Phase E). A separate 1,000-round multi-reveal supplement ($100.00 staked) is scored by Step 22 (3.8).
3.3Mine-Layout Recomputation (Step 5)

For every bet, the verifier computes the mine layout with revealMines(serverSeed, clientSeed, nonce, mineCount) from the epoch’s revealed server seed and compares it with the server-reported mineTiles using an ordered array comparison. Draw order is part of the verified output: the shuffle produces the mines in a specific sequence, and LIQD records that sequence. Step 5 also runs the ProvablyFair.org site verifier’s Mines module — vendored byte for byte and pinned by SHA-256 — on every round, and requires it to agree.

Mine-Layout Recomputation (Step 5) — illustrated flow
tests/steps/determinism.ts · Step 5 · Verified
  // ── Step 5: Mine-layout recomputation (all 6,900 bets) ────────────────────────  let mismatches = 0, skipped = 0;  for (const b of bets) {    const ss = seedMap.get(b.hashedServerSeed);    if (!ss) { skipped++; continue; }    const mines = revealMines(ss, b.clientSeed, b.nonce, b.mineCount);    if (!sameMines(mines, b.mineTiles)) mismatches++;  }  // …  const s5PopulationOk = bets.length === EXPECTED_BETS;  // …      const got = site.verifier.deriveMines({ clientSeed: b.clientSeed, serverSeed: ss, nonce: b.nonce, mineCount: b.mineCount }).minePositions;      siteChecked++;      if (!sameMines(got, revealMines(ss, b.clientSeed, b.nonce, b.mineCount))) {        siteMismatch++;  // …  const s5 = step(5, 'Mine-Layout Recomputation',    mismatches === 0 && skipped === 0 && s5PopulationOk && biasOk && siteOk ? 'PASS' : 'FAIL',
Result: 6,900 / 6,900 bets verified against the population pinned in src/pins.ts (not the file’s own row count), 0 skipped. Zero layout mismatches, in draw order — including every Phase B tail round and every Phase E multi-reveal round — and the ProvablyFair.org verifier matches on all 6,900 (Step 5).
3.4Payout Math + Win-Condition (Step 8)

For each of the 6,900 bets, the verifier replays the money and the label together. Wins: winningAmount must equal floor8(betAmount × minesMultiplierExact(mineCount, openTiles.length)) exactly, in BigInt rational arithmetic with zero tolerance — a ±1e-8 over- or under-credit fails — and must already be a whole 1e-8 unit before any rounding. The exact closed-form value is the reference, never the floored display field. Losses: winningAmount must be exactly 0. The label itself is re-derived: a round is won only if none of its opened tiles is in the recomputed mine set, lost only if one is. LIQD settles at an 8-decimal unit, rounding down; the total shortfall against the unrounded value across the whole capture is $0.000005868 on $2,670 staked.

Payout Math + Win-Condition (Step 8) — illustrated flow
tests/steps/payouts.ts · Step 8 · Verified
  // ── Step 8: Payout math (uses the EXACT multiplier that is CREDITED) + label check ─  // …  const S8_TOL = 2e-8;  // …  for (const b of bets) {    // …    const mineSet = new Set<number>(b.mineTiles);    const openedMine = b.openTiles.some((t: number) => mineSet.has(t));    if (b.result === 'won') {      if (openedMine) labelViolations++;              // won yet opened a mine      const exact = minesMultiplierExact(b.mineCount, b.openTiles.length);      const bet = Number(b.betAmount);      // …      const residual = bet * exact - winAmt;          // > 0 ⇒ credited below the exact value      // …      // Exact-floor8 identity (0 tolerance, rational): winningAmount in 1e-8 units must EQUAL      // floor8(stake × multExact). Unlike the ±2e-8 band below, this rejects a uniform ±1e-8      // under/over-credit. Only meaningful for a finite credit at a legal depth.      if (Number.isFinite(winAmt) && Number.isFinite(bet)) {        const credUnits = floor8CreditUnits(bet, b.mineCount, b.openTiles.length);        if (credUnits === null || BigInt(Math.round(winAmt * 1e8)) !== credUnits) floor8Violations++;      }      // …    } else {      if (!openedMine) labelViolations++;             // lost yet opened no mine      // …      if (!Number.isFinite(winAmt) || winAmt !== 0) payErrors++;    }  }
Result: All 3,104 wins: winningAmount = floor8(stake × exact multiplier) in exact rational arithmetic — 0 identity violations, 0 off-grid credits (largest residual against the unrounded value 8.571e-9); all 3,796 losses pay exactly 0; every label matches the recomputed board (Step 8).
3.5Displayed Multiplier Field (Step 7)

The multiplier field in LIQD’s settle response — the value shown on the result screen — is a display value: floor2(minesMultiplierExact), truncated to 2 decimals. Step 7 verifies that convention on every win and confirms the field can only under-report — the credited amount (checked in Step 8) uses the exact value. On 2,662 of the 3,104 wins the display sits strictly below what was paid; it never sits above. On losses the field is absent entirely.

2 columns
MetricCount
Wins where multiplier == floor2(exact) at 1e-93,104 / 3,104
Wins where the display understates the credited amount2,662
Wins where the display overstates the credited amount0
Losses carrying a multiplier key0 / 3,796 (absent, and winningAmount == 0)
tests/steps/payouts.ts · Step 7 · Verified
  // ── Step 7: Displayed multiplier field ────────────────────────────────────────  // …  // For LOST bets there is no payout: `multiplier` is omitted and winningAmount == 0.  // …  for (const b of bets) {    if (b.result === 'won') {      wonChecked++;      const display = minesMultiplier(b.mineCount, b.openTiles.length);      // floored      const exact = minesMultiplierExact(b.mineCount, b.openTiles.length);   // credited      if (!Number.isFinite(display) || !Number.isFinite(exact)) { s7NonFiniteRef++; continue; }      if (b.multiplier === undefined || !Number.isFinite(b.multiplier) || Math.abs(display - b.multiplier) > 1e-9) wonErrors++;      if (b.multiplier !== undefined && b.multiplier + 1e-9 < exact) displayBelowPaid++;    } else {      lostChecked++;      if ((b.multiplier ?? 0) !== 0 || Number(b.winningAmount) !== 0) lostErrors++;    }  }
Result: Every displayed multiplier equals floor2(exact) (3,104 / 3,104 at 1e-9); 0 wins where the display overstates the credit. The on-screen value is cosmetic and player-favourable — the payout driver is the exact formula.
3.6Reveal-Sequence Consistency (Step 20)

The capture records final settles, so Step 20 checks mid-round consistency from the final state: in a legitimate round, a win's opened tiles contain no mine, and a loss's opened tiles contain exactly one mine — the last tile opened, with every earlier open safe. Any other pattern (a mine opened mid-sequence without a bust, a bust with a clean board) would mean the served game diverged from the committed layout. All 6,900 rounds conform.

3 columns
ScopeCoverageVerified
Wins — no opened tile is a mine3,104 rounds3,104 / 3,104
Losses — bust on exactly the final opened tile3,796 rounds3,796 / 3,796
Earlier-tile mine violations (either direction)All 6,900 rounds0
Result: The reveal outcome is consistent with the recomputed mine layout in every round — the final-state equivalent of mid-round mine-set invariance.
3.7Phase C — Bet-Size Invariance (Step 18)

Phase C placed 200 bets at $10.00 on mineCount 3 — the same configuration tested at $0.10 elsewhere in the dataset. All 200 mine layouts were recomputed correctly from revealed seeds, in draw order (0 mismatches). The structural point: revealMines takes no wager argument, so in our implementation the layout cannot depend on stake — and the live game matched it at both captured stakes. The only place betAmount appears is the payout multiplication, and the only stakes in the dataset are exactly $0.10 and $10.00.

Result: Phase C: 200/200 layouts recomputed correctly at $10.00; every win credits exactly stake × 1.125 = $11.25 (Step 8 covers the money). Bet amount is not an input to the RNG.
Variance context: Phase C's 200-bet sample at mineCount 3 produced 176 wins — exactly the 22/25 = 88% single-tile expectation, landed to the integer. At N=200 this is small-sample luck and structurally irrelevant: the claim verified here is layout invariance, not RTP. The 99.0000% RTP is proven analytically in S4.
3.8Multi-Reveal Supplement (Step 22)

The audited dataset’s multi-reveal path is Phase E: 400 rounds at mine count 3 with five planned reveals. A separate supplement takes the payout deeper and plays it the way a player does — 1,000 interactive rounds (place bet → open tiles → cash out), captured on 3 October 2026 in 20 epochs of 50, at mine counts 1, 3, 5, 8 and 12 with four reveal depths each, up to 15 tiles, the reveal order shuffled per round and journalled before each bet. The file and its journal are pinned by SHA-256, and Step 22 holds it to the same rules as the audited dataset: commitments, chain and nonces; every layout in draw order; every served mine/safe status; and every cash-out against the one settlement rule — credit floor8(stake × exact), display floor2(exact). LIQD’s reveal responses carry no running multiplier, so the cash-out amount is what is checked. The supplement enters no RTP, simulation or phase figure.

2 columns
CheckDetail
IntegrityDataset and journal SHA-256 match their pins; 1,000 / 1,000 journal bet records equal the dataset rows; every reveal order journalled before its bet
Commitments20 / 20 commitments verify, 19 / 19 chain links, pre-capture anchor links into epoch 0; 0 nonce violations
Layouts and reveals1,000 / 1,000 layouts recompute in draw order; 3,314 / 3,314 served mine/safe statuses equal the recomputed layout
Cash-outs393 wins, 607 losses; 393 / 393 credits equal floor8(stake × exact) — 0 above, 0 below; 393 / 393 displays equal floor2(exact)
Order independence20 / 20 settings with more than one reveal order carry one multiplier and one credit; first picks span 25 / 25 positions
Coverage20 cash-out settings witnessed — together with the audited dataset, 45 of the 300 settings carry a captured winning payout
Result: PASS. Every supplement round recomputes, and every one of its 393 wins is credited and displayed exactly at the rule (Step 22).
3.9Worked Example — Full Parity Verification

Real bet from Phase E — bet 9pcR2FW8GQmNQ4VusJYDd (epoch 131), mineCount 3, nonce 0, 5 safe reveals. Verified from mines-master-6900bets.json:

serverSeed = f6f3c5efd80a81c02a4033a3a370e6b9
clientSeed = audit4d3ce43da272
nonce      = 0
mineCount  = 3
betAmount  = $0.10
openTiles  = [2, 3, 4, 5, 6]
recorded   = mineTiles [14, 24, 1] · multiplier 1.99 · winningAmount 0.19973684
commitment = 140cbc408754457f6767662eb1a02b5a…
3 columns
StepProcessOutput
1Recompute the commitment — SHA-256(utf8(serverSeed))Matches the pre-published hash
2Run revealMines(serverSeed, clientSeed, 0, 3)[14, 24, 1] — equals the recorded mineTiles, in order
3Check the five opened tiles against the mine setNo overlap — the won label is correct
4Compute minesMultiplierExact(3, 5)1.99736842… exact (panel displays the floored 1.99×)
5Multiply by the stake0.10 × 1.99736842 = 0.19973684 — the credited winningAmount, verbatim
HMAC-SHA256 recomputation + formula · Verified
commitHash(serverSeed)                    → matches the published commitment ✓revealMines(seed, client, 0, 3)           → [14, 24, 1]  (draw order) ✓openTiles ∩ mineTiles                     → ∅  — all five reveals safe → won ✓minesMultiplierExact(3, 5)                → 0.99 × (25·24·23·22·21)/(22·21·20·19·18) = 1.99736842betAmount × exact = 0.10 × 1.99736842     → 0.19973684  (display shows floor2 = 1.99×)
Parity verified: Bet 9pcR2FW8GQ… — commitment, mine layout, win label, exact multiplier, and credited payout all match exactly between the live game and our recomputation. This example exercises Steps 1, 5, 7, 8, 15 end-to-end, and is the same bet used for the RNG trace in S2.
Live Game$0.19973684
Verifier$0.19973684
Technical Evidence & Verification5 sections
3.10Evidence Coverage Summary12 checks
3 columns
Verification AreaCoverageResult
Mine-layout recomputation (Step 5)6,900 / 6,900 rounds — full layout verified, in draw order; ProvablyFair.org verifier 6,900 / 6,900Pass
Payout math — wins (Step 8)3,104 / 3,104 winning bets credit floor8(stake × exact multiplier), zero tolerancePass
Payout math — losses (Step 8)3,796 / 3,796 losing bets pay exactly zeroPass
Displayed multiplier convention (Step 7)3,104 wins at floor2(exact), 1e-9 — never above the creditPass
Win/loss labels vs recomputed boards (Steps 8, 20)0 violations in 6,900 roundsPass
Config completeness (Step 9)All 24 mine counts covered — 200 bets each in Phase APass
Phase D client-seed variation (Step 14)500 bets, 10 custom client seeds — 500 / 500 matchPass
Phase E multi-reveal (Step 15)400 / 400 layouts · 216 / 216 winning multipliersPass
Phase C bet-size invariance (Step 18)200 / 200 at $10.00Pass
Reveal-position independence (Step 21)107 distinct open-sets · all 25 first picksPass
Multi-reveal supplement (Step 22)1,000 / 1,000 layouts · 3,314 / 3,314 reveals · 393 / 393 credits and displays at the rulePass
Multi-phase coverage5 phases: A (4,800) + B (1,000) + C (200) + D (500) + E (400)Pass
3.11Code References7 source files
2 columns
FilePurpose
tests/steps/determinism.tsStep 5: Mine-layout recomputation (ordered comparison)
tests/steps/payouts.tsSteps 7–9: Display field, payout + win-condition replay, config completeness
tests/steps/phase-d.tsStep 14: Phase D custom client-seed variation
tests/steps/phase-e.tsStep 15: Phase E multi-reveal verification
tests/steps/standardization.tsSteps 18, 20, 21: Bet-size invariance, reveal-sequence consistency, reveal-position independence
src/config.tsminesMultiplierExact, minesMultiplier — the closed-form formulas every payout is replayed against
tests/steps/supplement.tsStep 22: the multi-reveal supplement — integrity, commitments, layouts, reveals and cash-outs
3.12Datasets UsedSource artifacts
Primary dataset: data/mines-master-6900bets.json — 6,900 live bets across Phases A–E (138 epochs), one hash-pinned capture
Phase E slice: Epochs 130–137 — 400 multi-reveal rounds inside the primary dataset (no separate file)
Multi-reveal supplement: data/liqd-mines-capture-v3-2026-10-03T11-58-54-479Z.json and its journal — 1,000 interactive rounds, SHA-256 a7737572394215b0…, scored by Step 22
Game config: Multiplier formula 0.99 × C(25,k)/C(25−m,k) in src/config.ts — algebraic, not a lookup table
Verification output: outputs/verification-results.json — Steps 5, 7–9, 11, 14–15, 18, 20–22
3.13Verified Invariants14 invariants
2 columns
InvariantResult
Computed mine layout matches live mineTiles, in draw order, for all 6,900 betsPass
winningAmount = floor8(betAmount × minesMultiplierExact(m, openTiles.length)) in exact rational arithmetic, for all 3,104 winsPass
winningAmount = 0 for all 3,796 lossesPass
Displayed multiplier = floor2(exact) on all 3,104 wins; absent on losses; never above the creditPass
Win condition holds: won ⇔ no opened tile is a mine, for all 6,900 betsPass
Loss condition holds: lost ⇔ the final opened tile is the only opened minePass
Planned reveals equals openTiles.length on every win (3,104 / 3,104)Pass
Payout keys off executed depth (openTiles.length), never the planPass
Phase C ($10.00) layouts recompute under the same wager-free derivation as the $0.10 bets at mineCount 3 (200 / 200)Pass
No hidden inputs beyond (serverSeed, clientSeed, nonce, mineCount) — stakes present are exactly {$0.10, $10.00}Pass
All 24 mine-count configurations (1–24) present in live data, 200 bets eachPass
Phase D: 500/500 layouts verified across 10 custom pfaudit client seedsPass
Phase E spans 107 distinct opened-tile sets and all 25 first picksPass
Supplement: 393 / 393 cash-outs credited at floor8(stake × exact) and displayed at floor2(exact)Pass
3.14Reproduction InstructionsRun locally

Clone the repository, install dependencies, and run the verification suite:

reproduce-s3.sh · 5 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-mines.gitcd liqd-mines && git checkout v1.0.1npm cinpm run verify# Expected output: Steps 5, 7–9, 11, 14–15, 18, 20–22 all PASS
S3-related steps:

  [PASS] Step 5 — Mine-Layout Recomputation
  [PASS] Step 7 — Displayed Multiplier Field (floored, cosmetic)
  [PASS] Step 8 — Payout Math + Win-Condition (wins: betAmount × EXACT multiplier == winningAmount; losses: 0; label ⇔ mine opened)
  [PASS] Step 9 — Config Completeness (Phase A: 24 mineCounts × 200)
  [PASS] Step 11 — Phase Labels
  [PASS] Step 14 — Phase D — Client Seed Variation
  [PASS] Step 15 — Phase E — Multi-Reveal Verification
  [PASS] Step 18 — Bet-Size Invariance
  [PASS] Step 20 — Reveal-Sequence Consistency
  [PASS] Step 21 — Reveal-Position Independence
  [PASS] Step 22 — Multi-Reveal Supplement (capture v3, 1,000 rounds)
04

RTP & Payout Logic

Is the house edge what the casino claims?

This section verifies that the flat 1.00% house edge is exactly what’s advertised at every one of the 300 settings — each mine count with every reveal depth. The key test is anti-circularity: we prove the RTP from first principles using combinatorial cancellation — the win-chance ratio and the payout-multiplier ratio cancel algebraically to exactly 0.99, independent of any casino-supplied probability data. We hold LIQD’s published 99% to every setting under the RTP claim rule, compare it against 24 million simulated rounds, and test whether the casino pre-selected seeds against the player.

Return to Player Verification

99.0000%theoretical RTP

What we verified

  • House edge is exactly 1.00% — flat across all 300 settings (24 mine counts × every reveal depth) and both captured stakes
  • RTP proven from first principles: winChance × exact multiplier = 0.99 at every setting — by algebraic cancellation, not summation
  • LIQD’s published 99% RTP holds on all 300 settings — worst gap 0.000000 pp against the ±0.1 pp band of the RTP claim rule
  • The exact multiplier is credited on every one of the 3,104 captured wins and the 393 supplement wins — settlement rounds down only at the 8th decimal
  • 24M-round simulation: mean 98.9427%, consistent with the proven 99.0000% (1.66 standard errors below)
  • Cherry-pick test: the configurations LIQD served on its 138 seeds show no early-window return shortfall (0 / 138 flagged, pooled z = +0.723)
  • Mine placement follows the same wager-free derivation at $0.10 and $10 (Phase C)

What this means for you

  • The house edge on Mines is a flat 1.00%, whichever mine count, depth or bet size you choose
  • The 99% LIQD advertises is what the formula returns at every setting
  • The RTP proof is derived independently — it doesn’t rely on trusting the casino
  • The casino’s seeds show no sign of being chosen against the player
  • At both captured stakes, the bet amount made no difference to where the mines were placed
  • The multiplier on screen is floored to 2 decimals for display, but the credited amount uses the exact value, floored only at the 8th decimal
99.0000%theoretical RTP
  • House edge is exactly 1.00% — flat across all 300 settings (24 mine counts × every reveal depth) and both captured stakes
  • RTP proven from first principles: winChance × exact multiplier = 0.99 at every setting — by algebraic cancellation, not summation
  • LIQD’s published 99% RTP holds on all 300 settings — worst gap 0.000000 pp against the ±0.1 pp band of the RTP claim rule
  • The exact multiplier is credited on every one of the 3,104 captured wins and the 393 supplement wins — settlement rounds down only at the 8th decimal
  • 24M-round simulation: mean 98.9427%, consistent with the proven 99.0000% (1.66 standard errors below)
  • Cherry-pick test: the configurations LIQD served on its 138 seeds show no early-window return shortfall (0 / 138 flagged, pooled z = +0.723)
  • Mine placement follows the same wager-free derivation at $0.10 and $10 (Phase C)
INDEPENDENT DERIVATION
winChance(m, k) × multiplier(m, k)= 0.99
Combinatorial cancellation of C(25−m, k) / C(25, k) against the payout formula · The uncapped formula returns exactly 99% at all 300 settings — 24 mine counts, every reveal depth
99.0000%Theoretical RTP (all 300 settings — every mine count, every depth)
98.9427%Simulated (24M rounds)
1.00%House Edge (flat)
300 / 300Settings meet LIQD’s published 99%
CheckResultEvidence
Anti-circularityPasswinChance × exact multiplier = 0.99 for every (mine count, reveals) setting — proven by algebraic cancellation of C(25−m, k) / C(25, k) against the payout formula, no casino data used; a counting anchor matches the closed form on 110 cases with integer equality (Step 13)
House edge auditPassFlat 1.00% house edge: 28 / 28 scored cases and the full sweep of 300 / 300 settings evaluate to 99.0000%; the exact multiplier rises strictly with depth and mine count on 552 / 552 adjacent pairs (Step 10)
RTP claim (RTP-CLAIM 1.2.0)PassLIQD publishes 99% RTP (Edge 1.00%) on the Mines game page; all 300 settings return exactly 99.000000% — worst gap 0.000000 pp against the ±0.1 pp band (Step 10)
Simulated RTP (Pass 1)Pass24M rounds, mean 98.9427% (1.66 standard errors below 99.0000%); 0 / 24 first-draw, 0 / 23 pairwise and 0 / 24 serial failures at Bonferroni; all 24M rounds replayed on every run (Step 16)
Cherry-pick test (Pass 2)PassServed-config return over 138 seeds × 10,000 nonces: 0 / 138 epochs flagged, pooled z = +0.723 against the −3.09 gate; detects a 20% early-window shortfall with 100% power, 10% with 64.1% (Step 17)
Bet-size invariancePassBet amount is not an input to the RNG — same layout derivation at $0.10 and $10. Tested in Phase C (200 / 200, Step 18)
Multiplier formulaPassExact closed-form value credited on every win (3,104 / 3,104 and 393 / 393 supplement); the displayed field is that value floored to 2 decimals — 0 exceptions (Steps 7, 8, 22)
Anti-circularityPass
winChance × exact multiplier = 0.99 for every (mine count, reveals) setting — proven by algebraic cancellation of C(25−m, k) / C(25, k) against the payout formula, no casino data used; a counting anchor matches the closed form on 110 cases with integer equality (Step 13)
House edge auditPass
Flat 1.00% house edge: 28 / 28 scored cases and the full sweep of 300 / 300 settings evaluate to 99.0000%; the exact multiplier rises strictly with depth and mine count on 552 / 552 adjacent pairs (Step 10)
RTP claim (RTP-CLAIM 1.2.0)Pass
LIQD publishes 99% RTP (Edge 1.00%) on the Mines game page; all 300 settings return exactly 99.000000% — worst gap 0.000000 pp against the ±0.1 pp band (Step 10)
Simulated RTP (Pass 1)Pass
24M rounds, mean 98.9427% (1.66 standard errors below 99.0000%); 0 / 24 first-draw, 0 / 23 pairwise and 0 / 24 serial failures at Bonferroni; all 24M rounds replayed on every run (Step 16)
Cherry-pick test (Pass 2)Pass
Served-config return over 138 seeds × 10,000 nonces: 0 / 138 epochs flagged, pooled z = +0.723 against the −3.09 gate; detects a 20% early-window shortfall with 100% power, 10% with 64.1% (Step 17)
Bet-size invariancePass
Bet amount is not an input to the RNG — same layout derivation at $0.10 and $10. Tested in Phase C (200 / 200, Step 18)
Multiplier formulaPass
Exact closed-form value credited on every win (3,104 / 3,104 and 393 / 393 supplement); the displayed field is that value floored to 2 decimals — 0 exceptions (Steps 7, 8, 22)
✓  RTP Behaves as Advertised

The 99.0000% RTP is proven mathematically from combinatorial cancellation — winChance × exact multiplier = 0.99 at every one of the 300 settings, so LIQD’s published 99% holds at every setting with zero gap. This is a first-principles proof, not a statistical estimate; 24M simulated rounds are consistent with it, and the cherry-pick test finds no sign of seed selection. The house edge is flat at 1.00% — the exact multiplier is credited, and the only money-side approximation is rounding down at the 8th decimal.

How It Works — RTP & Payout Logic8 sections
4.1Anti-Circularity Proof (Step 13)

The anti-circularity proof establishes the 99.0000% RTP from first principles without using any casino-supplied probability data. Mines' proof is an algebraic identity rather than a summation — the win chance and payout multiplier both contain the same combinatorial ratio, which cancels exactly. Step 13 derives the win probability from raw combinatorics, cross-checks it against the equivalent telescoping product, verifies that the k = 1 outcome space sums to exactly 1.0, and anchors the closed form with a counting leg — every k-subset of the 25 tiles is enumerated outright and tallied as integers against C(25, k) and C(25−m, k), a method in which a coefficient error is inexpressible — before multiplying against the payout formula:

Anti-Circularity Proof (Step 13) — illustrated flow
3 columns
ComponentFormulaSource
Win probabilityC(25−m, k) / C(25, k)Pure combinatorics — computed by the audit
Cross-checkΠ (25−m−i) / (25−i)Telescoping product — max deviation 2.220e-16
Counting anchorExhaustive k-subset tally — integer equality with C(25, k) and C(25−m, k)110 (m, k) cases (all 24 mine counts, k ≤ 5) — 0 mismatches, no tolerance
Exact multiplier(1 − 0.01) × Π (25−i) / (25−m−i)Closed form — validated against every captured win
RTPwinProbability × minesMultiplierExactThe product — the fair terms cancel
5 columns
Mines (m)winChance (k=1)Multiplier (with edge)winChance × MultiplierRTP
124/25 = 0.9600001.031250000.99000099.0000%
322/25 = 0.8800001.125000000.99000099.0000%
520/25 = 0.8000001.237500000.99000099.0000%
1213/25 = 0.5200001.903846150.99000099.0000%
205/25 = 0.2000004.950000000.99000099.0000%
241/25 = 0.04000024.750000000.99000099.0000%
Result: All 300 settings — 24 mine counts × every reveal depth: RTP = 99.0000% exactly, by algebraic cancellation — no summation, no convergence. The telescoping cross-check deviates by at most 2.22 × 10⁻¹⁶ (floating-point epsilon), and the counting anchor matches the closed form with integer equality on all 110 enumerated (m, k) cases. The theoretical RTP proof is non-circular and closed-form (Steps 10, 13).

Anti-Circularity Verification:

tests/steps/anti-circularity.ts · Step 13 · Verified
/** * Step 13: Anti-circularity — win probability is pure combinatorics. * * winProbability(m, k) = C(25-m, k) / C(25, k): the fair "all k opened tiles are * safe" probability. It never touches operator data. We prove it here three ways: *   (1) at k=1, Σ over the two mutually-exclusive outcomes (safe / mine) sums to 1: *       P(safe) + P(mine) = (25-m)/25 + m/25 = 1 for every m. *   (2) winProbability(m,k) equals the product form Π (25-m-i)/(25-i), i=0..k-1, *       which is manifestly independent of any payout table. *   (3) COUNTING ANCHOR: legs (1) and (2) are algebra about the same closed form — *       both are ours and both would be wrong together if the form itself were. Leg *       (3) enumerates every k-subset of the 25 tiles outright and counts, as *       integers, the total (must equal C(25,k)) and the subsets avoiding a fixed *       m-tile mine set (must equal C(25-m,k)). Nothing is multiplied or divided — *       a binomial-coefficient error is inexpressible in a tally. Integer equality, *       no tolerance. Covers every m at the depths the game data exercises *       (k ≤ min(5, 25−m); Phase E's maximum reveal depth is 5). */ import type { StepResult } from './context';import { step } from './context';import type { VerifyContext } from './context';import { winProbability, combination, GRID, MINE_COUNTS } from '../../src/config';

Why this proof is non-circular: winProbability(m, k) and the fair multiplier are literally reciprocals of the same combinatorial ratio C(25, k) / C(25−m, k), scaled by the edge factor 0.99. They cancel exactly — no summation, no approximation, no “converges to”. The identity holds by inspection for every (m, k) pair where k ≤ 25 − m, so the 99.0000% RTP is preserved however many tiles a player reveals before cashing out; Step 10 evaluates it at all 300 settings. What the audit adds is evidence that LIQD’s implementation honours the algebra — the exact multiplier is what gets credited on every captured win (Steps 8 and 22) — and that the probability side is derived without touching a single operator-supplied number.

4.2RTP Claim Rule (Step 10)

The RTP claim rule (RTP-CLAIM 1.2.0) is part of the audit method: the RTP a casino publishes must hold at every setting the game offers. A published figure counts wherever a player can find it — on the game page, the fairness page, the help page, the bet panel, the rules, or in the game settings the site loads. Each setting’s RTP is computed exactly, as a rational number, from combinatorics and the credited multiplier — never from the casino’s figure — and must sit within ±0.1 percentage points of the claim. A setting below that band on the house side is a miss: the verdict then reads RTP FIGURE OVERSTATED and the suite exits with code 5, even when every draw check passes. A setting above the claim never fails. The rule is scored on Step 10, the house-edge step.

2 columns
ItemDetail
Claim99% RTP at every setting — a flat 1.00% house edge
Where LIQD publishes itThe Mines game page, liqd.com/originals/mines: header "Edge: 1.00%", page text "99% RTP at every setting", meta description "99% RTP" — read logged out on 1 October 2026 (evidence/rtp-page-labels-2026-10-01.json)
Settings tested300 — every mine count 1–24, each with cash-out after every legal depth k = 1 … 25 − m
MethodExact rational: C(25 − m, k) / C(25, k) × the credited multiplier 0.99 × Π (25 − i) / (25 − m − i)
Band±0.1 percentage points (house side fails, player side never fails)
True minimum · maximum99.000000% · 99.000000%
Worst gap0.000000 pp
Settings beyond the band0 on the house side · 0 on the player side
Scored onStep 10 (profile check MINES-19), rule enforced
ResultPass — the claim holds at every setting; no fix needed
7 columns
Mine countDepthsSettingsExact RTPRTPGapResult
1 minek = 1 … 242499/10099.000000%0.000000 ppPass
2 minesk = 1 … 232399/10099.000000%0.000000 ppPass
3 minesk = 1 … 222299/10099.000000%0.000000 ppPass
4 minesk = 1 … 212199/10099.000000%0.000000 ppPass
5 minesk = 1 … 202099/10099.000000%0.000000 ppPass
6 minesk = 1 … 191999/10099.000000%0.000000 ppPass
7 minesk = 1 … 181899/10099.000000%0.000000 ppPass
8 minesk = 1 … 171799/10099.000000%0.000000 ppPass
9 minesk = 1 … 161699/10099.000000%0.000000 ppPass
10 minesk = 1 … 151599/10099.000000%0.000000 ppPass
11 minesk = 1 … 141499/10099.000000%0.000000 ppPass
12 minesk = 1 … 131399/10099.000000%0.000000 ppPass
13 minesk = 1 … 121299/10099.000000%0.000000 ppPass
14 minesk = 1 … 111199/10099.000000%0.000000 ppPass
15 minesk = 1 … 101099/10099.000000%0.000000 ppPass
16 minesk = 1 … 9999/10099.000000%0.000000 ppPass
17 minesk = 1 … 8899/10099.000000%0.000000 ppPass
18 minesk = 1 … 7799/10099.000000%0.000000 ppPass
19 minesk = 1 … 6699/10099.000000%0.000000 ppPass
20 minesk = 1 … 5599/10099.000000%0.000000 ppPass
21 minesk = 1 … 4499/10099.000000%0.000000 ppPass
22 minesk = 1 … 3399/10099.000000%0.000000 ppPass
23 minesk = 1 … 2299/10099.000000%0.000000 ppPass
24 minesk = 1199/10099.000000%0.000000 ppPass
All 300 settingsevery legal depth30099/10099.000000%0.000000 ppPass — 300 / 300
Result: Pass — LIQD’s published 99% is met exactly by all 300 settings: the true minimum and maximum are both 99.000000%, so the worst gap is 0.000000 pp against the ±0.1 pp band (Step 10). The full per-setting table ships as outputs/rtp-claim-table.json, pinned by SHA-256 and re-derived on every run.
4.3House Edge & Settlement Rounding (Step 10)

Step 10 computes winProbability(m, k) × minesMultiplierExact(m, k) from first principles at 28 scored cases — every mine count at k = 1, plus the Phase E depths k = 2…5 at mine count 3 — and sweeps the same formula across all 300 legal settings (k = 1 … 25 − m): every one evaluates to 99.0000% within 1e-9. Because LIQD credits the exact multiplier, the product is identically 1 − 0.01 = 0.990000 everywhere — no setting with a worse return, no depth with a hidden penalty — and the exact multiplier rises strictly with depth and with mine count across all 552 adjacent pairs of settings.

The one money-side approximation is settlement rounding: LIQD credits floor8(stake × exact), rounding down at its 8-decimal unit, so the settled RTP can sit a hair under 99%. At the smallest captured stake, $0.10, the worst setting (m = 2, k = 3) settles at 98.9999934% — 99.0000% to four decimals. Across the 3,104 captured wins, 1,214 credits sit below the unrounded value, by at most 8.571e-9 each: a total of $0.000005868 on $2,670 staked (2.2e-9 of the amount wagered). The displayed multiplier field is floored to 2 decimals; it is a label, not the payout driver, and it can only understate what was credited.

Result: Effective edge = 1.00% flat — the formula returns 99.0000% at every setting (Step 10: 28 / 28 scored cases, 300 / 300 in the sweep), and every captured win is credited at the exact multiplier (Steps 8 and 22).
Finding — payout precision (Medium, remediated): The original build credited the multiplier floored to 2 decimals (floor2(exact)), so wins paid slightly under the formula wherever the exact multiplier has more than two decimals: a $0.10 win at 4 mines and one tile paid 0.117 where the exact amount is 0.11785714. Across the 24 single-reveal settings that returns 98.7767% on average against the advertised 99% (98.28%–99.00% per setting; outputs/audit-figures.json → flooredDisplayCounterfactual). LIQD moved to full-precision crediting, and every figure in this section is measured on the fixed build. Section 5 lists the finding as LIQD-MINES-PREC-002, remediated.
4.4Simulation Pass 1 — Our Own Seeds (Step 16)

Section 4.1 proves RTP = 99.0000% mathematically. Does our implementation of the same algorithm produce that in practice? We simulated 24 million rounds — fresh crypto.randomBytes seed pairs for each mine count, 1,000,000 single-reveal rounds each, every pair kept in its result row — and every verify run replays all 24,000,000 rounds from those saved seeds and recomputes every statistic (Step 16: 24 / 24 rows, 0 differences). Pass 1 is scored as three families at the Bonferroni threshold α/24:

Simulation Pass 1 — Our Own Seeds (Step 16) — illustrated flow

First-draw uniformity: The first drawn mine tile is one uniform outcome per round (χ², df = 24 — correct for a single draw). 0 / 24 configs fail at Bonferroni.

Pairwise co-occurrence: Each of the 300 tile pairs is a Binomial count against the hypergeometric C(23, m−2) / C(25, m); tested via max |z| with a per-pair correction. 0 / 23 configs fail (m ≥ 2); largest |z| = 3.72.

First-drawn-tile serial independence: Lag-1 autocorrelation + runs test on the first-drawn tile sequence. 1 / 24 uncorrected trips (≈1.4 expected by chance); 0 / 24 at Bonferroni.

5 columns
Mine countTheoretical RTPSimulated RTP (1M)Deviationz
199.0000%98.9871%−0.0129 pp−0.64
299.0000%99.0612%+0.0612 pp+2.10
399.0000%98.9917%−0.0083 pp−0.23
499.0000%98.9437%−0.0563 pp−1.30
599.0000%99.0724%+0.0724 pp+1.46
699.0000%98.9767%−0.0233 pp−0.42
799.0000%99.1128%+0.1128 pp+1.83
899.0000%98.9538%−0.0462 pp−0.68
999.0000%98.9759%−0.0241 pp−0.33
1099.0000%99.0688%+0.0688 pp+0.85
1199.0000%98.8998%−0.1002 pp−1.14
1299.0000%99.0196%+0.0196 pp+0.21
1399.0000%99.0347%+0.0347 pp+0.34
1499.0000%98.9003%−0.0997 pp−0.89
1599.0000%99.1285%+0.1285 pp+1.06
1699.0000%99.0242%+0.0242 pp+0.18
1799.0000%98.8865%−0.1135 pp−0.79
1899.0000%98.9042%−0.0958 pp−0.60
1999.0000%99.1737%+0.1737 pp+0.99
2099.0000%98.9535%−0.0465 pp−0.24
2199.0000%99.0210%+0.0210 pp+0.09
2299.0000%98.8507%−0.1493 pp−0.56
2399.0000%98.1993%−0.8007 pp−2.38
2499.0000%98.4852%−0.5148 pp−1.06
All 24 (mean)99.0000%98.9427%−0.0573 pp−1.66
src/simulate.ts · Pass 1 core loop · Verified
  const firstDrawFreq = new Array(GRID).fill(0);          // index 0 = tile 1, ... tile 25  const firstDrawSeq = new Uint8Array(ROUNDS_PER_CONFIG); // first-drawn tile VALUE per round  // …  for (let nonce = 0; nonce < ROUNDS_PER_CONFIG; nonce++) {    const mines = revealMines(serverSeed, clientSeed, nonce, mineCount);    const first = mines[0];    firstDrawFreq[first - 1]++;    firstDrawSeq[nonce] = first;                           // 1..25 tile value    // …    // k=1 strategy: open FIXED_TILE, win iff it is NOT a mine.    let fixedIsMine = false;    for (const t of mines) if (t === FIXED_TILE) { fixedIsMine = true; break; }    if (!fixedIsMine) wins++;    // …  }
Result: 24M rounds. Mean simulated RTP 98.9427% — 1.66 standard errors below the proven 99.0000%, consistent with it. 0 / 24 first-draw, 0 / 23 pairwise and 0 / 24 serial failures at Bonferroni: the screens find no sign of placement bias across the 25-tile grid (Step 16).

What is a Monte Carlo simulation? Instead of relying on algebra alone, we simulate millions of rounds. Each round computes a mine layout from scratch with our implementation of the HMAC-SHA256 sequential Fisher-Yates shuffle, reveals one tile, and records whether the round won and what it paid. The aggregate return is then compared with the proven 99.0000%; a result consistent with it shows the implementation behaves as the algebra says.

Why these three tests and not a per-tile chi-squared: mine placement is a constrained multinomial — exactly m mines among 25 tiles — so per-tile counts are negatively correlated and a naive 25-cell test applies the wrong null (it loses power as mine density rises, though it still rejects gross biases). First-draw, pairwise-joint and serial are each valid against that structure, and together they cover position, interaction and memory.

Reading the deviations: Each row is an independent 1M-round experiment at that mine count’s own variance, which grows with mine count (the single-reveal payout gets rarer and larger). The largest gap is mine count 23 at −0.80 pp, 2.38 standard errors; mine count 2 sits at +2.10. With 24 independent rows, one or two beyond 2 standard errors is expected — per-mine-count RTP is informational here; the scored tests are the Bonferroni-gated families above.
4.5Cherry-Pick Test — Pass 2 (Step 17)

Could LIQD have chosen server seeds that play worse for players? Pass 2 takes every server seed LIQD actually used — all 138 revealed seeds — and replays each one far beyond the captured window. The load-bearing statistic is the served-config test: for each epoch, the 50 configurations LIQD actually served (mine count and tiles requested, in nonce order) are replayed over its revealed seed for 10,000 nonces. The captured window, nonces 0–49, is the actual play — its replayed outcomes equal the 6,900 recorded ones — and its return is ranked against the same seed’s 199 later windows of the same configuration sequence. A seed chosen to play badly early would show a low early-window return. The rule — flag a seed at p ≤ 0.01, a family gate on the flag count, and a pooled z gate at −3.090 — was fixed in code before the statistic was first computed. A first-draw diagnostic runs beside it, against a bootstrap null that rebuilds from a recorded master seed:

Primary defence — structural: The client seed is player-editable and can be set after the server-seed commitment is published. An operator that cannot predict your seed cannot pre-evaluate candidate server seeds against it.

Secondary check — statistical: The served-config test above detects a 20% early-window return shortfall with near-certain power, backing the structural defence above.

2 columns
TestDetail
Seeds tested138 — every revealed server seed in the capture, each replayed for 10,000 nonces
Served-config test0 / 138 epochs flagged at p ≤ 0.01 (family gate ≤ 6 at family α 0.001)
Pooled early-window return6,974.1278 against an exact expectation of 6,831.0000 — z = +0.723 against the −3.09 gate: no return shortfall
Detection power (pooled test)100.0% for a 20% early-window return shortfall · 64.1% at 10% · 8.6% at 5%
First-draw diagnostic4 flags (epochs 23, 71, 93, 98) against 6.555 expected by chance; survival probability 0.8976 against a 0.01 threshold
Bootstrap replay (Step 17)10,000 replicates per mine count rebuilt from the recorded master seed — 138 / 138 early p-values match, 0 mismatches
Independent Python replay240,000 / 240,000 null statistics and 138 / 138 p-values reproduced, late-p max |Δ| 9.0e-13 (npm run bootstrap-replay)
tests/steps/simulation.ts · Step 17 · Verified
const SERVED_NONCES = 10_000;const SERVED_ALPHA = 0.01;const SERVED_FAMILY_ALPHA = 0.001;const SERVED_POOLED_Z = 3.090;// …  //  THE SERVED-CONFIG TEST (MINES-21) — the profile's statistic, computed here and nowhere else  // …  const WINDOWS = Math.floor(SERVED_NONCES / EXPECTED_EPOCH_SIZE);  // …      let mu = 0, v = 0;      for (const c of e.configs) {        const P = winProbability(c.m, c.plan.length), mult = minesMultiplierExact(c.m, c.plan.length);        mu += P * mult; v += P * mult * mult - (P * mult) * (P * mult);      }      const early = x.windowReturns[0];      const le = x.windowReturns.slice(1).filter(w => w <= early).length;      const pS = (1 + le) / WINDOWS;      if (pS <= SERVED_ALPHA) { servedFlags++; servedFlaggedEpochs.push(e.epoch); }      muAll += mu; vAll += v; earlyAll += early;  // …  const servedGate = binomialTailThreshold(nServed, SERVED_ALPHA, SERVED_FAMILY_ALPHA);  const pooledZ = vAll > 0 ? (earlyAll - muAll) / Math.sqrt(vAll) : NaN;  const servedPower = SERVED_POWER_SHORTFALLS.map(d => ({ shortfall: d, power: normalCdf(d * muAll / Math.sqrt(vAll) - SERVED_POOLED_Z) }));  // …  const servedOver = servedFlags > servedGate || !(pooledZ >= -SERVED_POOLED_Z);
Result: PASS. 0 / 138 epochs flagged, and the pooled early-window return sits above its expectation (z = +0.723 against the −3.09 gate) — no sign of seed selection. The first-draw diagnostic raises 4 flags against 6.555 expected (P = 0.8976), every early p-value rebuilt from the recorded master seed (Step 17).
Why is this included? Commit-reveal constrains what happens after a commitment, not which seed gets committed. LIQD lets players set or rotate the client seed after the server-seed commitment is published, so cherry-picking is structurally contained for a player who sets an unpredictable seed. Pass 2 checks the seeds LIQD actually served for a statistical trace of selection.
4.6Bet-Size Invariance (Step 18)

Phase C placed 200 bets at $10.00 on mine count 3 — the same setting tested at $0.10 in Phase A. All 200 mine layouts recompute in draw order from the revealed seeds with the same HMAC-SHA256 sequential Fisher-Yates derivation, and every one of the 176 $10.00 wins credited exactly $11.25 (stake × 1.125). Bet amount is not an input to the RNG.

3 columns
MetricPhase A ($0.10)Phase C ($10.00)
Rounds at mineCount 3, 1 tile200200
Layout recomputation parity200 / 200200 / 200
Exact multiplier on a win1.125000001.12500000
Credited per win$0.11250000$11.25000000
Wager argument in revealMinesnone — the function has no stake parameternone — same function, same code path
Result: 200 / 200 Phase C rounds at $10.00 recompute against the same wager-free derivation as the $0.10 rounds — revealMines takes no stake argument (Step 18).
4.7Informational Items (Not Scored)

These items are reported for transparency but are not scored audit steps. They provide context on the empirical RTP observed during data collection and on fields outside the scored surface. At the per-phase sample sizes in this audit, empirical RTP is not a meaningful measure of fairness — the anti-circularity proof in 4.1 is the authoritative evidence.

3 columns
ItemValueContext
Live capture RTP99.5361%$2,657.61 returned on $2,670.00 wagered over 6,900 bets at mixed settings — a variance illustration only: 220–1,220 bets per mine count is far below convergence
Live win rate3,104 / 6,900 = 44.99%Mixed mine counts and depths — informational
Live-sample fit (Step 9)p = 0.8889 · p = 0.9709First-drawn mine position over all 6,900 layouts, χ² 15.978 on 24 df; win counts in 25 (mine count, depth) groups against the binomial, Σz² 13.424 on 25 df. Reported, not scored
Live win/loss lag-1 autocorrelationr₁ = 0.3548The capture walks mine counts in blocks, so adjacent bets share a win rate and the series reflects that grouping; Pass 1’s serial screen tests the generator itself
maxOdds in the audited build’s settings10,000×Echoed on 3,844 of the 6,900 settles. 44 of the 300 settings have an exact multiplier above it, from 21 mines with 4 reveals (12,523.5×); no captured round came near it — the largest multiplier won is 24.75×, and 40.86× in the supplement. The supplement’s settings carry no maxOdds, and LIQD states that Mines pays the full multiplier at every depth, with a limit on how much one round can pay out. The 99.0000% at every setting is the uncapped formula.
4.8Worked Example — Payout Verification

Real bet from Phase A — bet GbqOuPvhQekqcARNtHZ3_ (epoch 8), mineCount 3, nonce 25, single reveal. Verified from mines-master-6900bets.json — the same round shape as LIQD’s settle screen in evidence E14 (S6.10):

serverSeed = 8b2fc7e909d2fff439692f648a9df0ba
clientSeed = audit59c2b0bf86aa
nonce      = 25
mineCount  = 3
betAmount  = $0.10
openTiles  = [2]
k          = 1
recorded   = mineTiles [6, 20, 7] · multiplier 1.12 · winningAmount 0.1125

Step 1 — Mine computation: revealMines(seed, client, 25, 3) → [6, 20, 7] — matches the record in draw order.

Step 2 — Win check: Opened tile 2 is not in {6, 20, 7} → the round is legitimately won.

Step 3 — Multiplier derivation: minesMultiplierExact(3, 1) = 0.99 × 25/22 = 1.125 exact; the panel displays floor2 = 1.12×.

Step 4 — Payout: 0.10 × 1.125 = $0.1125 — the credited winningAmount, verbatim from the record.

3 columns
StepProcessOutput
1Recompute the layout from seeds[6, 20, 7] — ordered match
2Check the opened tile against the mine setSafe — won confirmed
3Exact multiplier 0.99 × 25/221.125 (display: 1.12×)
4Stake × exact multiplier$0.1125 credited
5RTP identity at this config0.88 × 1.125 = 0.9900 — the flat edge, exactly
RTP proof for mines=3, k=1 · Verified
winProbability(3, 1)        = C(22, 1) / C(25, 1) = 22 / 25 = 0.88minesMultiplierExact(3, 1)  = 0.99 × 25 / 22      = 1.125RTP = winProbability × minesMultiplierExact    = 0.88 × 1.125    = 0.9900   →  99.0000%   (the fair terms cancel; only the edge remains)
Parity verified: Bet GbqOuPvhQe… — mine layout, win condition, exact multiplier, and payout all match. RTP for mineCount 3 = 99.0000% proven from algebraic cancellation, independent of any casino data. This is the exact shape of LIQD’s settle screen in evidence E14 (S6.10) — a 1.12× display beside a full-precision 1.125 credit.
Live Game$0.1125
Verifier$0.1125
Technical Evidence & Verification5 sections
4.9Evidence Coverage Summary8 checks
3 columns
Verification AreaCoverageResult
Anti-circularity (Step 13)300 (m, k) pairs from combinatorics alone — cross-check deviation 2.2e-16; counting anchor 110 cases at integer equalityPass
House edge audit (Step 10)28 / 28 scored cases and 300 / 300 settings = 99.0000%; 552 / 552 adjacent pairs strictly increasingPass
RTP claim rule (Step 10)LIQD’s published 99% against all 300 settings — worst gap 0.000000 pp, 0 beyond the bandPass
Payout reconciliation (Step 8)3,104 wins at floor8(stake × exact), zero tolerance + 3,796 losses at 0Pass
Simulation Pass 1 (Step 16)24M rounds replayed — 0 / 24 first-draw, 0 / 23 pairwise, 0 / 24 serial at Bonferroni; 1.66 SE below 99.0000%Pass
Simulation Pass 2 (Step 17)Served-config test 0 / 138 epochs flagged, pooled z = +0.723; first-draw diagnostic 4 flags vs 6.555 expectedPass
Bet-size invariance (Step 18)200 / 200 Phase C at $10.00Pass
Multi-reveal payouts (Steps 15, 22)216 / 216 Phase E winning multipliers; 393 / 393 supplement cash-outs at the rulePass
4.10Code References11 source files
2 columns
FilePurpose
tests/steps/anti-circularity.tsStep 13: anti-circularity proof (combinatorial identity, cross-checks and counting anchor)
tests/steps/payouts.tsSteps 7–10: display field, payout replay, config completeness, house-edge audit and the RTP claim rule
src/rtp-claim.ts · src/rtp-claim-switch.tsThe RTP claim rule (RTP-CLAIM 1.2.0) and its enforcement switch (on)
src/rtp-settings.tsThe 300-setting exact RTP table and LIQD’s published claim
tests/steps/simulation.tsSteps 16–17: Pass 1 replay and scoring, the served-config test and the Pass 2 bootstrap replay
tests/steps/standardization.tsStep 18: bet-size invariance across Phases A and C
src/simulate.tsMonte Carlo simulation (24M rounds, two passes — Pass 1 FWER families, Pass 2 bootstrap)
src/stats.tschiSquaredTest, max-|z| joint test, lag1Autocorrelation, runsTest
src/config.tswinProbability, pairCoOccurProb, minesMultiplierExact, minesMultiplier, theoreticalRTP — closed-form formulas
src/false-alarm.tsNominal false-alarm accounting of the eight scored screens in Steps 16–17 (6.97% total nominal level — an accounting, not a calibrated rate)
src/figures.tsDerived-figure artifact (outputs/audit-figures.json) — the producing artifact for every quoted figure, re-derived by Step 19
4.11Datasets UsedSource artifacts
Simulation output: outputs/simulation-results.json — Pass 1 (24M rounds, 1M per config × 24 configs, each row’s seed pair) + Pass 2 (138 seeds × 10K nonces, master seed 7461a9c2… recorded for replay) · SHA-256 8854fc57e7f22f19…
RTP claim table: outputs/rtp-claim-table.json — LIQD’s published 99% against all 300 settings, with each setting’s exact RTP, gap and outcome · SHA-256 b8293e56cb2f77d5…
Derived figures: outputs/audit-figures.json — every quoted figure (300-setting reference grid, settlement floor, client-seed controls, rejection census, live aggregate, false-alarm accounting, simulation summary) re-derived field-for-field by Step 19 on every run
Game config: Multiplier formula 0.99 × C(25,k)/C(25−m,k) in src/config.ts — algebraic, not a lookup table
Primary dataset: data/mines-master-6900bets.json — payout reconciliation, Phase C bet-size invariance, the served configurations of Pass 2, and empirical-RTP context
Convergence chart: outputs/rtp-convergence.html — pooled single-reveal RTP at seven sample sizes; the ±2 SE band narrows from ±2.22 pp at 1K to ±0.069 pp at 1M, and the final interval [98.8736%, 99.0118%] contains 99.0000%
4.12Verified Invariants15 invariants
2 columns
InvariantResult
winProbability(m,k) × minesMultiplierExact(m,k) = 0.9900 at all 300 settings (non-circular)Pass
Combinatorial form equals the telescoping product (max deviation 2.220e-16)Pass
k = 1 outcome space sums to 1.0 exactly at every mine countPass
Counting anchor: an exhaustive k-subset tally equals C(25, k) and C(25−m, k) as integers on all 110 enumerated (m, k) casesPass
LIQD’s published 99% holds at all 300 settings under the RTP claim rule (worst gap 0.000000 pp)Pass
Exact multiplier strictly increasing in depth and mine count (552 / 552 adjacent pairs)Pass
Flat 1.00% house edge — all 3,104 wins credit floor8(stake × exact), zero tolerance; losses pay exactly 0 (3,796 / 3,796)Pass
Display field = floor2(exact), never above the credit — 0 exceptionsPass
Simulated RTP 98.9427% across 24M rounds — 1.66 standard errors below 99.0000%Pass
0 / 24 configs reject first-draw uniformity at Bonferroni α/24Pass
0 / 23 configs reject the pairwise-joint fit at Bonferroni (m ≥ 2)Pass
0 serial independence failures at Bonferroni (lag-1 + runs test)Pass
Served-config test: 0 / 138 epochs flagged; pooled early-window z = +0.723 (gate −3.09)Pass
First-draw diagnostic: 4 flags vs 6.555 expected (P = 0.8976); null replayed from the recorded master seedPass
Phase C ($10.00) layouts match the $0.10 derivation; bet amount absent from the RNG inputPass
4.13Reproduction InstructionsRun locally

Clone the release, install, and run the verification and the two replays:

reproduce-s4.sh · 6 lines · Verified
git clone https://github.com/ProvablyFair-org/liqd-mines.gitcd liqd-mines && git checkout v1.0.1npm cinpm run verify            # Steps 8, 10, 13, 15–18 and the RTP claim rulenpm run deep-replay       # the 24M Pass 1 rounds, stand-alonenpm run bootstrap-replay  # independent Python rebuild of the Pass 2 null (Python 3.9+)
S4-related steps:

  [PASS] Step 8 — Payout Math + Win-Condition (wins: betAmount × EXACT multiplier == winningAmount; losses: 0; label ⇔ mine opened)
  [PASS] Step 10 — House Edge / RTP Audit (closed-form identity, per-config, anti-circular)
  [PASS] Step 13 — Anti-Circularity (Combinatorial Win Probability)
  [PASS] Step 15 — Phase E — Multi-Reveal Verification
  [PASS] Step 16 — Simulation — Pass 1 (First-Draw · Joint · Serial, FWER)
  [PASS] Step 17 — Simulation — Pass 2 Cherry-Pick Test
  [PASS] Step 18 — Bet-Size Invariance

  rtp-claim: ENFORCED — PASS
  rtp-claim:   claim flat 99.000000% — source: …
  rtp-claim:   band ±0.10 pp · 300 settings · true min 99.000000% (mines=1 k=1) · true max 99.000000% (mines=1 k=1)
  rtp-claim:   house side beyond band: 0 · player-favourable beyond band: 0 · worst mines=1 k=1 at 0.000000 pp
  rtp-claim:   outcome PASS · minimal fix: none — the claim holds for every setting it states a figure for
  rtp-claim:   committed table outputs/rtp-claim-table.json: equals this run; sha256 b8293e56cb2f77d5… matches the pin

Mean simulated RTP in the committed artifact: 0.9894271044 (theoretical 0.99 exactly). npm run simulate runs a new experiment into outputs/run/ under a fresh recorded master seed (SIM_MASTER_SEED repeats one; SIM_REPLAY=1 regenerates the committed experiment); a single uncorrected trip on a new run is within the screens’ combined false-alarm rate, their nominal levels summing to 6.97%. The verdict is scored against the committed artifacts by npm run verify, which replays all 24M Pass 1 rounds from their saved seeds. The chart on this page plots the committed artifact’s pooled single-reveal convergence.
05

Fairness Integrity Testing

Does the implementation maintain fairness under non-standard conditions?

Sections 1–4 establish that the game is mathematically fair; Section 5 tests whether the live implementation keeps its integrity under non-standard conditions. It covers 31 integrity tests across 11 categories — 30 run against LIQD’s running service and the payout-precision check raised by the RTP derivation — including race conditions, input validation, idempotency, authorization, outcome and payout fields, nonce and seed handling, and the provably fair surface itself. 27 passed. Four were remediated. The original build credited wins with the multiplier floored to two decimals, paying slightly under the formula (Medium); LIQD moved to full-precision crediting. Wrong-type mine-count and stake values were converted instead of refused, and a few aggressive client disconnects drew server errors from the recovery path (both Low); LIQD fixed the input handling, and recovery was clean on a broader re-test. All four are re-verified. No test altered, replayed or injected an outcome.

Fairness Integrity Testing

27pass4remediated

What we verified

  • Race conditions — can parallel bets, a cash-out racing a tile reveal, or a seed rotation racing a live game change an outcome?
  • Nonce tampering — can the sequence be forced, replayed, or skipped?
  • Seed handling — can unusual client seeds or server seed fields be overridden?
  • Outcome replay — can a completed bet be replayed, or a tile revealed with no game in play?
  • Cross-player isolation — can one player act on another’s account or session?
  • Payout tampering — can multiplier or payout values be injected, and is every win credited at the exact multiplier?
  • Parameter limits — can invalid mine counts, stakes or currencies be submitted?

What this means for you

  • Across the 31 tests, no request path let anyone alter, replay or inject an outcome
  • Once a bet is placed, its mine layout cannot be changed or replayed
  • Each bet gets its own server-assigned nonce under a committed seed
  • Your games are bound to your session — no other account can act on them
  • The gaps found — wins credited slightly under the advertised return on the original build, wrong-type values converted instead of refused, and an untidy recovery path under aggressive disconnects — are remediated and re-verified; every win in the audited capture is credited at the exact multiplier
27pass4remediated
  • Race conditions — can parallel bets, a cash-out racing a tile reveal, or a seed rotation racing a live game change an outcome?
  • Nonce tampering — can the sequence be forced, replayed, or skipped?
  • Seed handling — can unusual client seeds or server seed fields be overridden?
  • Outcome replay — can a completed bet be replayed, or a tile revealed with no game in play?
  • Cross-player isolation — can one player act on another’s account or session?
  • Payout tampering — can multiplier or payout values be injected, and is every win credited at the exact multiplier?
  • Parameter limits — can invalid mine counts, stakes or currencies be submitted?
4 / 4Race Conditions
4 / 4Input Validation
3 / 3Atomic Balance / Idempotency
2 / 2Broken Authorization
3 / 3Max-win Bypass
4 / 4Nonce Manipulation
3 / 3RNG / PF Verification
1 / 1Entropy / PF Verification
2 / 2Precision / Payout
2 / 2Event Replay
3 / 3Adversarial Seeds

Category Coverage

Race Conditions4/4
Input Validation4/4
Atomic Balance / Idempotency3/3
Broken Authorization2/2
Max-win Bypass3/3
Nonce Manipulation4/4
RNG / PF Verification3/3
Entropy / PF Verification1/1
Precision / Payout2/2
Event Replay2/2
Adversarial Seeds3/3
CheckResultEvidence
Nonce integrityPassServer-assigned and sequential: injected reused, skipped, zero or negative and overflow-sized nonces are all ignored (NONCE-001 … NONCE-004); the nonce advances by one per bet (PF-002) and restarts at 0 on rotation (ENTROPY-004)
Seed integrityPassAcross 70 attempts, a rotation during a live game was blocked or revealed only the previous seed, and no seed ID was reused (EXTRA-001); unusual and Unicode client seeds handled (SEED-001, SEED-002), and each bet uses the seed set just before it (SEED-003)
Outcome determinismPassMine positions from the revealed seed match the tiles played, in order, through LIQD’s verifier (EXTRA-002, PARAM-003)
Round & player isolationPassBets carrying another account’s identifier stay on the caller’s own account; post-logout reuse rejected; one game at a time, with duplicate bets and reveals without a game refused
Payout integrityPassInjected payout and outcome fields ignored; a cash-out racing a reveal never collected while revealing; 200 consecutive games settled with no mismatch
Payout precisionRemediatedThe original build credited wins with the multiplier floored to two decimals (Medium); LIQD moved to full-precision crediting, and all 3,104 winning bets in the audited capture are credited at the exact multiplier (PREC-002; Section 4.3)
Parameter limits & recoveryRemediatedRanges held on every test; wrong-type mine-count and stake values were converted instead of refused, and some aggressive disconnects drew server errors with no orphaned debit (Low) — the input handling fixed by LIQD, recovery clean on a broader re-test; all re-verified (PARAM-001, PARAM-002, IDEMP-003)
Nonce integrityPass
Server-assigned and sequential: injected reused, skipped, zero or negative and overflow-sized nonces are all ignored (NONCE-001 … NONCE-004); the nonce advances by one per bet (PF-002) and restarts at 0 on rotation (ENTROPY-004)
Seed integrityPass
Across 70 attempts, a rotation during a live game was blocked or revealed only the previous seed, and no seed ID was reused (EXTRA-001); unusual and Unicode client seeds handled (SEED-001, SEED-002), and each bet uses the seed set just before it (SEED-003)
Outcome determinismPass
Mine positions from the revealed seed match the tiles played, in order, through LIQD’s verifier (EXTRA-002, PARAM-003)
Round & player isolationPass
Bets carrying another account’s identifier stay on the caller’s own account; post-logout reuse rejected; one game at a time, with duplicate bets and reveals without a game refused
Payout integrityPass
Injected payout and outcome fields ignored; a cash-out racing a reveal never collected while revealing; 200 consecutive games settled with no mismatch
Payout precisionRemediated
The original build credited wins with the multiplier floored to two decimals (Medium); LIQD moved to full-precision crediting, and all 3,104 winning bets in the audited capture are credited at the exact multiplier (PREC-002; Section 4.3)
Parameter limits & recoveryRemediated
Ranges held on every test; wrong-type mine-count and stake values were converted instead of refused, and some aggressive disconnects drew server errors with no orphaned debit (Low) — the input handling fixed by LIQD, recovery clean on a broader re-test; all re-verified (PARAM-001, PARAM-002, IDEMP-003)
✓  31 of 31 Pass or Remediated

31 integrity tests: 27 pass and 4 remediated (payout precision, Medium — LIQD moved to full-precision crediting, re-verified across the audited capture; input hygiene, fixed by LIQD; recovery under aggressive disconnects, clean on a broader re-test; all re-verified). No test altered, replayed or injected an outcome. Test procedures and payloads are withheld by policy — see 5.5; the payout-precision working is published in Section 4.3.

How It Works — Fairness Integrity Testing2 sections
5.1Framework Overview

Testing follows the ProvablyFair.org Fairness Integrity Framework — a structured methodology derived from real, historically observed failures in provably fair systems. Cryptographic verification (S1–S3) shows the game computed every captured game from its disclosed inputs; fairness-integrity testing asks whether the surface around that computation holds up under non-standard use — seeds swapped, rounds replayed, parameters injected, settlements raced. This section covers 31 tests across 11 categories: 30 run against LIQD’s running service, and the payout-precision check that the RTP derivation raised (Section 4.3). Each card below gives the control assessed, the broad testing approach, the observed result, its impact and any limitation.

3 columns
CategoryTestsWhat It Catches
Race Conditions4Parallel bets, reveals and cash-outs racing each other or a seed rotation
Input Validation4Malformed, wrong-type or out-of-range mine counts, stakes and currencies
Atomic Balance / Idempotency3Double debits, orphaned debits and unsafe retry behaviour after a timeout or disconnect
Broken Authorization2Acting on another account, or after the session has ended
Max-win Bypass3Client-supplied outcome, payout or mine-count fields, and stake limits
Nonce Manipulation4Reused, skipped, zero, negative or oversized nonces supplied by the client
RNG / PF Verification3Provably fair fields, the nonce sequence, and mine positions against LIQD’s verifier
Entropy / PF Verification1Server-seed rotation behaviour: fresh seeds, a new commitment and the nonce reset
Precision / Payout2Settlement arithmetic at scale, and every credit against the exact closed-form multiplier
Event Replay2Duplicate bets, and tile reveals sent with no game in play
Adversarial Seeds3Unusual client seeds: length, character sets, Unicode forms and rapid rotation
5.2Severity Framework & Hard Fail Criteria

Two scales apply in this section. The audit framework decides whether a result can pass at all — any test that lets money settle against a tampered or unverifiable outcome is an automatic hard fail. The integrity matrix records each test’s status — Pass, Remediated, Warning or Fail — and each finding is graded Critical, High, Medium or Low. On this page: four Remediated tests — payout precision, graded Medium (raised by the RTP derivation: wins credited with the multiplier floored to two decimals, now credited at full precision), and three graded Low: wrong-type mine-count and stake values converted inside the valid ranges, fixed by LIQD, and server errors from the recovery path under aggressive disconnects, cleared on a broader re-test. All four are re-verified.

3 columns
StatusMeaningAction
PassThe service handled the scenario correctly: the control heldNone — recorded with its result
RemediatedA failed control that LIQD has since fixed, re-tested by ProvablyFair.org on the current buildRecorded with the fix and its re-test
WarningA real defect, or a property not yet proven, without a direct effect on funds or fairnessRecorded with its impact; open until a re-test resolves it
FailA defect affecting funds, fairness or securityRemediation and a re-test required
2 columns
ConditionConsequence
A substituted server seed is accepted against a published commitmentHard fail
A revealed seed fails its commitment and play continuesHard fail
Any settled round's layout cannot be reproduced from disclosed inputsHard fail
A credited payout deviates from the closed-form multiplierHard fail
Nonce reuse or gaps that could conceal discarded outcomesHard fail
Hard fail criteria: These conditions are absolute — a single occurrence fails the game regardless of every other result. None of them occurred on the audited build: the payout-precision deviation belonged to the original build, and every win in the audited capture is credited at the exact multiplier. No test altered, replayed or injected an outcome, and no finding touched the provably fair layer.

31 tests · 27 pass · 4 remediated

Race Conditions4/4
LIQD-MINES-EXTRA-001Pass

Seed rotation racing a live game

Control assessed
No seed is revealed while a game is in play, and no seed ID is reused.
Testing approach
Seventy attempts to rotate the seed during an active game.
Observed result
Across all 70 attempts, a rotation during an active game was either blocked or revealed only the previous seed — never the one in play — and no seed ID was reused.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-RACE-001Pass

Parallel bets over-committing funds

Control assessed
Only one bet is accepted when the balance covers one, so funds cannot be over-committed.
Testing approach
Five attempts, each sending two bets at once against a balance that covers one.
Observed result
In every attempt exactly one of the two bets was accepted: the single-active-game guard held.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-RACE-002Pass

Parallel bets and parallel reveals

Control assessed
One bet is accepted at a time, and one reveal is processed at a time.
Testing approach
Ten bets sent at once, then parallel reveals of the same tile.
Observed result
Only 1 of the 10 bets was accepted, and only one reveal was processed: both guards held.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-RACE-003Pass

Cash-out racing a tile reveal

Control assessed
A cash-out cannot collect a payout while another tile is revealed at the same moment.
Testing approach
Twenty attempts firing a cash-out together with a further reveal; 13 reached a state where the race could occur.
Observed result
In none of the 13 raceable attempts did a cash-out collect a payout while also revealing another gem.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Input Validation4/4
LIQD-MINES-PARAM-001LowRemediated

Mine count: input types

Control assessed
Out-of-range values and wrong-type values for the mine count are both refused.
Testing approach
Mine counts in range, out of range and of the wrong type.
Observed result
Every out-of-range mine count was rejected, and no converted value could fall outside the 1–24 range, but a few wrong-type values were converted into valid in-range counts instead of being refused. After LIQD’s fix, wrong-type values are rejected and the 1–24 range remains enforced.
Impact
Low · input hygiene: no invalid mine count was ever accepted. Fixed by LIQD and re-verified.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-PARAM-002LowRemediated

Stake types and non-finite values

Control assessed
Wrong-type and non-finite stakes are refused, and no negative or over-balance amount is accepted.
Testing approach
Stakes of the wrong type, below the currency’s precision and non-finite, alongside the explicit zero-stake free-play value.
Observed result
No negative or over-balance amount was ever accepted, but wrong-type stakes were converted to their numeric value and sub-precision or non-finite values were recorded as a zero stake. After LIQD’s fix, zero is accepted only as explicit free play, and wrong-type, sub-precision and non-finite stakes are rejected.
Impact
Low · input hygiene, with no effect on funds. Fixed by LIQD and re-verified.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-PARAM-003Pass

Maximum mines, verified

Control assessed
Twenty-five mines are refused and twenty-four accepted, and the game verifies at the maximum.
Testing approach
A 25-mine and a 24-mine bet; the 24-mine game verified after the reveal.
Observed result
Twenty-five mines (every tile) were rejected and twenty-four accepted; verification succeeded at the maximum mine count.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-TOKEN-001Pass

Currency identifier manipulation

Control assessed
Invalid currency identifiers are refused, and any default currency is the account’s own.
Testing approach
Bets carrying malformed and foreign currency identifiers, and one with none.
Observed result
A missing identifier and every invalid value were rejected; empty and null values resolve to the account’s own currency, echoed in the reply. No cross-currency path exists.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Atomic Balance / Idempotency3/3
LIQD-MINES-IDEMP-003LowRemediated

Server errors under aggressive disconnects

Control assessed
Aggressive client disconnects recover cleanly, with no server error and no orphaned debit.
Testing approach
Bets abandoned by the client at a range of very early moments, then a recovery bet.
Observed result
Every bet still succeeded and no orphaned debit was found, but a few disconnect timings drew a server error from the recovery path. On a broader re-test with revised timing variants, no server error appeared, no orphaned debit was found, and every variant recovered cleanly.
Impact
Low · robustness — no orphaned debit at any point. Remediated: recovery was clean on the re-test.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-IDEMP-001Pass

Retry after a client timeout

Control assessed
Retrying after a client timeout does not debit twice.
Testing approach
A bet sent with a very short client timeout, the game recovered, then a retry.
Observed result
The timed-out bet had been processed server-side; the game was recovered and the retry succeeded with no double debit.
Impact
None — no double debit.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-IDEMP-002Pass

Orphaned-debit check

Control assessed
Every debit has a matching game result.
Testing approach
Twenty consecutive games reconciled against their charges.
Observed result
All 20 were charged correctly, and every debit had a matching game result.
Impact
None — no orphaned debit.
Limitations
None specific to this test; the general scope is in 5.5.
Broken Authorization2/2
LIQD-MINES-AUTH-001Pass

Another player’s identifier in the request

Control assessed
Every bet is attributed to the caller’s own session; another player’s balance is never touched.
Testing approach
Bets sent with fake identifiers and with a second account’s real identifier; the second account’s balance compared before and after.
Observed result
All 6 bets were attributed to the caller’s own session, and the second account’s balance was identical before and after: the game session is bound to the login, not to a field in the request.
Impact
None — account isolation held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-AUTH-003Pass

Session reuse after logout

Control assessed
A session ends at logout: its cookie cannot place another bet.
Testing approach
A bet attempted with a session cookie after logout.
Observed result
The logged-out session was rejected; no bet was placed and no balance changed.
Impact
None — the session was invalidated server-side.
Limitations
None specific to this test; the general scope is in 5.5.
Max-win Bypass3/3
LIQD-MINES-INJECT-001Pass

Extra fields in the bet request

Control assessed
Unknown fields in a bet request cannot change the game.
Testing approach
Bets carrying injected mine-count, payout and result fields.
Observed result
The server ignored the unknown fields and used its own game state.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-MAXWIN-001Pass

Stake boundary enforcement

Control assessed
Strictly invalid stakes are refused.
Testing approach
Negative, non-numeric and over-balance stakes.
Observed result
Every strictly invalid stake was rejected. Sub-precision handling is covered by PARAM-002.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-MAXWIN-002Pass

Outcome fields injected

Control assessed
The server ignores client-supplied outcome values and computes its own.
Testing approach
Bets carrying an injected nonce, payout amount and multiplier.
Observed result
All three were ignored; the server computed its own values.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Nonce Manipulation4/4
LIQD-MINES-NONCE-001Pass

Reused nonce

Control assessed
The client cannot choose the nonce: an injected value is ignored, and the server assigns its own.
Testing approach
A bet carrying an already-used nonce.
Observed result
The injected nonce was ignored; the server assigned the next one in sequence.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-NONCE-002Pass

Skipped-ahead nonce

Control assessed
An injected nonce cannot move the server’s sequence.
Testing approach
A bet carrying a nonce ten ahead of the current one.
Observed result
The injected nonce was ignored; the server assigned the next one, keeping the sequence contiguous.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-NONCE-003Pass

Zero and negative nonces

Control assessed
Implausible injected nonces are ignored.
Testing approach
Bets carrying zero and negative nonces.
Observed result
All three values were ignored; none was honoured.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-NONCE-004Pass

Overflow-sized nonces

Control assessed
Oversized injected nonces are ignored, with no crash, overflow or wrap-around.
Testing approach
Bets carrying nonces at integer-overflow scale.
Observed result
All three were ignored, with no crash, overflow or wrap-around.
Impact
None — the server kept its own sequence.
Limitations
None specific to this test; the general scope is in 5.5.
RNG / PF Verification3/3
LIQD-MINES-EXTRA-002Pass

Mine positions against LIQD’s verifier

Control assessed
Mine positions derived from the revealed seed match the tiles actually played, in order.
Testing approach
Five games at 20 mines, each a loss so that all 20 positions show; positions derived through LIQD’s verify endpoint and compared tile by tile.
Observed result
All five games matched exactly, in the same order, with no seed-hash mismatch.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-PF-001Pass

Provably-fair data and the reveal flow

Control assessed
Each bet carries every provably-fair field, and a revealed seed matches its earlier commitment.
Testing approach
Bets checked field by field, then the seed rotated and revealed.
Observed result
All fields were present and the nonce incremented by exactly one per bet; after rotation, the previous seed was revealed and hashed to its commitment.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-PF-002Pass

Nonce sequence within a seed period

Control assessed
The nonce advances by exactly one per bet under a single commitment.
Testing approach
Ten consecutive bets on one server seed.
Observed result
The nonce advanced from 0 to 10, one step per bet, under one commitment, with no unintended rotation.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Entropy / PF Verification1/1
LIQD-MINES-ENTROPY-004Pass

Nonce reset after seed rotation

Control assessed
Rotating the seed commits a fresh server seed and restarts the nonce at 0.
Testing approach
Bets, a rotation, then further bets.
Observed result
The nonce reset to 0, and a distinct new seed hash was committed.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Precision / Payout2/2
LIQD-MINES-PREC-002MediumRemediated

Payout precision against the exact multiplier

Control assessed
Every win is credited at the exact closed-form multiplier, with no rounding against the player.
Testing approach
Raised by the RTP derivation (Section 4.3): each win’s credit compared with the exact closed-form multiplier, then re-checked on the fixed build across the audited capture.
Observed result
The original build credited the multiplier floored to two decimals, so wins paid slightly under the formula wherever the exact multiplier has more than two decimals: 98.78% average return over the 24 single-reveal settings (98.28%–99.00% per setting) against the advertised 99%. LIQD moved to full-precision crediting; on the fixed build, all 3,104 winning bets in the audited capture are credited at the exact multiplier.
Impact
Medium · payout accuracy — before the fix, wins paid slightly less than the advertised return. Fixed by LIQD and re-verified.
Limitations
Raised by the RTP derivation rather than a test on LIQD’s running service; its full working is published in Section 4.3.
LIQD-MINES-PREC-001Pass

Settlement precision at scale

Control assessed
Every payout matches the balance change, with no settlement mismatch.
Testing approach
Two hundred consecutive one-mine games; each payout compared with the balance change.
Observed result
Every payout matched the balance change across all 200 games; the 94.5% win rate fits one mine (a first-tile gem 24 times in 25).
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Event Replay2/2
LIQD-MINES-REPLAY-001Pass

Duplicate bet sent twice at once

Control assessed
Of two identical bets sent at once, one is accepted and one refused, with no ID or nonce collision.
Testing approach
A pair of identical bet requests sent in parallel.
Observed result
Exactly one was accepted and one rejected, with no bet-ID or nonce collision.
Impact
None — the single-active-game guard held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-REPLAY-002Pass

Tile reveal with no active game

Control assessed
A tile cannot be revealed without an active game.
Testing approach
Reveal requests sent with no game in play.
Observed result
All three were rejected.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Adversarial Seeds3/3
LIQD-MINES-SEED-001Pass

Client-seed length and character sets

Control assessed
A consistent length limit is enforced, and every character set is stored as literal text.
Testing approach
Client seeds of increasing length and across Unicode classes, each followed by a bet.
Observed result
A consistent 255-character limit applies, and every Unicode class was accepted and stored literally. One length at the limit drew a generic server error rather than a clean rejection.
Impact
None on the game; the generic error is an input-hygiene note.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-SEED-002Pass

Unicode forms of one seed

Control assessed
Composed and decomposed Unicode forms of the same seed are handled consistently.
Testing approach
The same visible seed submitted in both forms.
Observed result
Both forms were accepted and stored byte for byte, without normalisation, so they hash differently — a valid, consistent design.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
LIQD-MINES-SEED-003Pass

Rapid seed rotation

Control assessed
Each bet uses the seed set by the rotation immediately before it.
Testing approach
Three back-to-back rotations, each followed by a bet.
Observed result
Every bet’s echoed seed matched the one set just before it.
Impact
None — the control held.
Limitations
None specific to this test; the general scope is in 5.5.
Technical Evidence & Verification4 sections
5.3Coverage Summary11 categories

Coverage by category — 31 tests: 27 pass, 4 remediated.

3 columns
CategoryTestsResult
Race Conditions4All pass
Input Validation42 pass · 2 remediated (PARAM-001, PARAM-002)
Atomic Balance / Idempotency32 pass · 1 remediated (IDEMP-003)
Broken Authorization2All pass
Max-win Bypass3All pass
Nonce Manipulation4All pass
RNG / PF Verification3All pass
Entropy / PF Verification1All pass
Precision / Payout21 pass · 1 remediated (PREC-002)
Event Replay2All pass
Adversarial Seeds3All pass
Method: Every test except the payout-precision check ran against LIQD’s running service on its pre-launch environment; that check came from the RTP derivation and was re-verified across the audited capture; remediations were re-tested on LIQD’s current build. Sections 1–4 verify the captured rounds offline — independent derivation, commitment and chain checks, nonce continuity and credit arithmetic — while Section 5 tests the live service around them. Per-test procedures, payloads and captures are retained under the engagement record and are not published — see 5.5.
5.4Findings & Remediation3 records

3 findings from 4 tests, all three remediated and re-verified. Each test’s card in the matrix above gives its control, approach, result, impact and limitations.

3 columns
TestsFindingStatus
PREC-002The original build credited wins with the multiplier floored to two decimals, returning 98.78% against the advertised 99%. LIQD moved to full precision; fixed and re-verified.Remediated · Medium
PARAM-001 · PARAM-002Wrong-type mine-count and stake values were converted instead of refused, inside the valid ranges. Fixed by LIQD and re-verified.Remediated · Low
IDEMP-003Some aggressive client disconnects drew a server error from the recovery path, with no orphaned debit. Remediated: recovery was clean on a broader re-test.Remediated · Low
5.5Scope & LimitationsScope note
What is published here is coverage, not method. Each test in this section appears with its identifier and category, the control it assessed, the broad testing approach, the observed result, its impact and any limitation; a finding also carries its severity, where graded, and its remediation state. Request payloads, preconditions, step sequences, endpoints, timings and response captures are withheld: they show how to repeat a test, and add nothing a reader needs to judge its result. The full record is retained and is available to LIQD and to regulators on request.
Coverage published, method withheld: The classes of issue tested are standard and publicly documented. What would help someone repeat a test — volumes, timings, settings and request sequences — stays in the engagement record.
Point-in-time, per build: Each result is scoped to the build tested; the input and recovery remediations were re-tested on LIQD’s current build, and the payout-precision fix across the audited capture.
Scope: The tests target the provably fair surface and the money path around it — seeds, rounds, placement and settlement. Wallet custody, account management and infrastructure hardening are outside their scope.
Test environment: Every live test ran against LIQD’s pre-launch environment (qa.liqd.com), not the public site.
5.6Evidence & Disclosure

Live-surface record: Section 5 tests LIQD’s running service, so unlike Sections 1–4 it does not reproduce from the audit repository. The published record is the test cards above. The payout-precision check is the exception: its working is in Section 4.3 and reproduces from the repository.

Retained under the engagement record: Per-test procedures, request and response captures, re-test captures, run timestamps and build identifiers — available to LIQD and to regulators on request.

Section 5 coverage · published on this page
Integrity tests ....................... 31Categories ............................ 11Pass .................................. 27Remediated ............................ 4Warning (open) ........................ 0Findings .............................. 3 · 3 remediated, 0 openProcedures and payloads ............... withheld (5.5)
Why the runner is not published: The tests run against LIQD’s real-money service, here its pre-launch environment. A published runner, with its payloads, would let anyone repeat them against LIQD, or against another operator built the same way; coverage and results are published instead.
06

Player Verification

Can a player verify their own bets without trusting anyone?

Every Mines outcome can be independently reproduced using publicly disclosed inputs. No hidden variables, no private backend data. If the revealed seed matches the hash you saw before betting and your computed mine layout matches the game result, the bet was provably fair. This section walks you through the process — with LIQD’s own panel, with the independent ProvablyFair.org verifier, or with a few lines of code you run yourself.

Independent Result Verification

4 Stepsto verify any bet

Key principles

  • Every Mines outcome can be independently reproduced
  • No hidden variables — no private backend data
  • If the revealed seed matches the hash you saw before betting and your computed mine layout matches the game, the bet was provably fair
  • Most players can verify directly through LIQD’s Provably Fair panel

What you need

  • Server Seed — revealed after seed rotation (casino entropy)
  • Client Seed — your player-controlled seed
  • Nonce — the bet number in sequence (ensures uniqueness)
  • Mine Count — the configuration you played (1–24)
4 Stepsto verify any bet
  • Every Mines outcome can be independently reproduced
  • No hidden variables — no private backend data
  • If the revealed seed matches the hash you saw before betting and your computed mine layout matches the game, the bet was provably fair
  • Most players can verify directly through LIQD’s Provably Fair panel
From Bet to Independent Verification — 4-step flow — illustrated flow
Server SeedRevealed after rotation
Client SeedPlayer-controlled input
NonceRecorded bet sequence
Mine Count1–24, as played
  1. 1

    Place a Mines Bet

    Choose your mine count (1–24) on the 25-tile grid and place a bet. The full mine layout is computed from the seeds before any tile flips — which tiles you click and when you cash out never change where the mines are.

  2. 2

    Open the Provably Fair Panel and Rotate the Seed

    Open the Provably Fair panel on the game page. The Seeds tab shows the active client seed, the Active Server Seed (Hashed) — the commitment, shown before you bet — and the bet count under the pair. Copy the hashed server seed before you rotate: the round view lists only the client seed, server seed, nonce and mine count. Rotating ends the epoch and reveals the outgoing server seed in plaintext — open the round under My Bets to see it in its Provably Fair section. First check the commitment: the SHA-256 of the revealed seed’s hex text must equal the hash you copied — across this audit’s 138 epochs it always did.

  3. 3

    Enter the Seeds in the Verify Tab

    In the panel’s Verify tab, choose Mines and enter the four values the round’s Provably Fair section lists: the revealed server seed, your client seed, the bet’s nonce and the mine count you played.

  4. 4

    Recompute & Confirm

    Press Verify — or enter the same inputs in the ProvablyFair.org verifier. If the commitment check in step 2 passed and the recomputed mines sit exactly where your board showed them — tiles are numbered 1–25 across the rows from the top-left — the bet was provably fair. The scripts under Manual Verification also check the credit.

Visual Walkthroughstep-by-step
6.1Step 1: Place a Mines Bet

Pick a mine count on the 5×5 grid and place a bet. The platform computes the entire layout from (serverSeed, clientSeed, nonce) and the mine count before any tile is revealed — the round below runs at 16 mines, with two tiles opened before the cash-out.

LIQD Mines on liqd.com — a $1.00 bet on 16 mines (the slider shows 9 gems and 16 mines): two gems opened, at row 2 column 1 and row 4 column 3, and cashed out at 8.25× for $8.25, with the rest of the board revealed.

LIQD Mines on liqd.com — a $1.00 bet on 16 mines (the slider shows 9 gems and 16 mines): two gems opened, at row 2 column 1 and row 4 column 3, and cashed out at 8.25× for $8.25, with the rest of the board revealed.

6.2Step 2: Open the Provably Fair Panel and Rotate the Seed

The panel has two tabs, Seeds and Verify. The Seeds tab holds your active pair and its commitment, with the pre-committed next pair below it. Copy the Active Server Seed (Hashed) before you rotate: after the rotation, the round view lists only the client seed, server seed, nonce and mines. Rotation ends the epoch: the outgoing server seed is revealed, and the pre-committed next pair becomes the active one — the Seeds tab then shows the next client seed and next server-seed hash from before the rotation as the active pair, with zero bets made (E12). That pre-commitment is the chain the audit’s Step 2 checks across all 137 links, plus the pre-capture link into epoch 0. Open the round under My Bets: its Provably Fair section shows the revealed server seed beside the client seed, nonce and mine count. Check that the seed’s SHA-256 equals the hash you copied.

Active Server Seed (Hashed) — the commitment for the current epoch; the plaintext is revealed when you rotate and must hash to this value.

Active Client Seed — your player-controlled entropy input, set through the Next Client Seed field before you rotate.

Total bets made with pair — the bet counter under the active pair, which locates each round’s nonce within the epoch.

Next Server Seed — labelled as a seed, but it holds the next epoch’s hash: the pre-commitment for the pair that follows.

The Seeds tab before rotating — Active Server Seed (Hashed) 95c3d981…0c1fa0 after 6 bets, and the next server seed’s hash already published. The seed revealed by the rotation, a50bbca8…2a8524, hashes to 95c3d981…0c1fa0.

The Seeds tab before rotating — Active Server Seed (Hashed) 95c3d981…0c1fa0 after 6 bets, and the next server seed’s hash already published. The seed revealed by the rotation, a50bbca8…2a8524, hashes to 95c3d981…0c1fa0.

6.3Step 3: Enter the Seeds in the Verify Tab

The Verify tab takes the four values the round’s Provably Fair section lists — client seed, server seed, nonce and mines — and recomputes a layout on demand. Choose Mines and enter the server seed revealed in step 2, your client seed, the round’s nonce and the mine count you played. For the round in step 1: client seed 2167d363a51ccf60d479fc24b95f5bcf, server seed a50bbca804aa27ce5105b6fc972a8524, nonce 5 — the pair’s sixth bet — and 16 mines.

6.4Step 4: Recompute & Confirm

Press Verify. The tab recomputes the full 25-tile layout, so you can check that every tile you opened — and the mine that ended the round, if you hit one — sits exactly where the recomputed layout says. Tiles are numbered 1–25 across the rows from the top-left. For the round in step 1, the seed hashes to 95c3d981…0c1fa0, the commitment the Seeds tab showed before the rotation, and the layout computes to the 16 mines [15, 23, 8, 14, 25, 22, 1, 11, 10, 2, 3, 16, 7, 20, 21, 13], in draw order: every tile except 4, 5, 6, 9, 12, 17, 18, 19 and 24 — the board in step 1, with both opened tiles, 6 and 18, safe. Two safe tiles on 16 mines pay 8.25×: $8.25 on the $1.00 stake. The scripts under Manual Verification reproduce all of it.

Manual Verification (Advanced)6 sections
6.5Why Manual Verification Matters

Hosted verifiers — including ours — are conveniences. The guarantee only becomes unconditional when you can reproduce a round with tools nobody else controls. The algorithm below is small enough to audit by eye and run anywhere: if the revealed seed matches the hash you saw before betting and your recomputation matches the round you played, no one in the chain — casino, auditor, or tool host — changed it after you bet.

6.6How the Algorithm Works (Plain English)

Five pieces, in the order they come into play:

  • Commit — before you play, LIQD publishes SHA-256 of the server seed (hashed as the 32-character hex string). The seed itself stays secret until rotation.
  • Draw — each shuffle position asks HMAC-SHA256(key = decoded seed, message = clientSeed:nonce:cursor) for one uniform number, discarding the biased tail of the 32-bit space first.
  • Shuffle — tiles 1…25 are swapped one position at a time (cursor 0…24); when the shuffle finishes, the first mineCount tiles are the mines, in the order they were drawn.
  • Board — tile t sits at row ⌈t / 5⌉, column ((t − 1) mod 5) + 1: numbered across the rows from the top-left.
  • Pay — cashing out after k safe tiles credits stake × 0.99 × Π (25−i)/(25−m−i), floored at the 8th decimal; the panel displays the multiplier floored to 2 decimals.

Note the two hashing domains: the commitment hashes the hex string as UTF-8 text, while the HMAC key is the 16 decoded bytes. Mixing them up is the most common reason a hand-rolled verifier disagrees with the game — both scripts below get it right, and both print the featured bet's expected output in their comments so you can self-check the tool before trusting it.

6.7Casino Verifier vs ProvablyFair.org Verifier4 records

Both should agree — the point of having an independent one is what happens if they ever don't:

3 columns
LIQD Built-in (in-game panel)ProvablyFair.org
Who controls the codeThe casinoProvablyFair.org — open source
What it proves aloneInternal consistency of LIQD’s own mathIndependent recomputation from raw inputs
Checked against this captureIndirectly — LIQD’s own validation vectors agree with our implementation (3 / 3), and the live integrity tests checked five layouts against it (LIQD-MINES-EXTRA-002, S5)Directly — its Mines module, pinned by hash, reproduces 6,900 / 6,900 captured layouts in draw order (Step 5)
If they disagreeSuspect the casino’s tool or a tampered roundThe repo + scripts are the tiebreaker anyone can run
6.8JavaScript Verification Script

Complete, dependency-free, and pre-loaded with the featured bet as its default inputs — run it as-is to see a full match, then swap in your own round. Run on every captured round, it reproduces all 6,900 layouts and every credit, and all 1,000 supplement rounds:

verify-mines.js · Standalone Node.js
// verify-mines.js — standalone LIQD Mines verifier (Node.js, no dependencies)// Usage: node verify-mines.js <serverSeed> <clientSeed> <nonce> <mineCount> [tilesOpened] [stake]const { createHash, createHmac } = require('node:crypto'); const argv = process.argv.slice(2);const [serverSeed, clientSeed, nonceArg, minesArg, tilesArg, stakeArg] = argv.length  ? argv  : ['f6f3c5efd80a81c02a4033a3a370e6b9', 'audit4d3ce43da272', '0', '3', '5', '0.10'];const nonce = Number(nonceArg), mineCount = Number(minesArg);const k = Number(tilesArg ?? '1'), stake = stakeArg ?? '0.10'; // 1) Commitment: SHA-256 of the hex STRING (UTF-8) — what the panel showed before you playedconsole.log('commitment =', createHash('sha256').update(serverSeed, 'utf8').digest('hex')); // 2) One uniform draw per shuffle position — HMAC keyed on the DECODED bytesfunction draw(cursor, range) {  const digest = createHmac('sha256', Buffer.from(serverSeed, 'hex'))    .update(`${clientSeed}:${nonce}:${cursor}`).digest();  const maxFair = Math.floor(0x1_0000_0000 / range) * range;  for (let o = 0; o + 4 <= digest.length; o += 4) {    const chunk = digest.readUInt32BE(o);    if (chunk < maxFair) return chunk % range;  }  throw new Error('all eight chunks rejected (not specified; below 1e-66 per draw)');} // 3) Sequential Fisher-Yates over tiles 1..25 — the first mineCount tiles are the mines, in draw order.//    Tile t sits at row ceil(t / 5), column ((t − 1) % 5) + 1, counting across the rows from the top-left.const tiles = Array.from({ length: 25 }, (_, i) => i + 1);for (let c = 0; c < 25; c++) {  const j = c + draw(c, 25 - c);  [tiles[c], tiles[j]] = [tiles[j], tiles[c]];}const mines = tiles.slice(0, mineCount);console.log('mines      =', JSON.stringify(mines)); // 4) Cash-out after k safe tiles: exact = 0.99 × Π (25−i)/(25−m−i), kept as an exact fraction (BigInt)let num = 99n, den = 100n;for (let i = 0; i < k; i++) { num *= BigInt(25 - i); den *= BigInt(25 - mineCount - i); }const [whole, frac = ''] = stake.split('.');const stakeUnits = BigInt(whole + frac.padEnd(8, '0').slice(0, 8));   // stake in 1e-8 unitsconst credit = (stakeUnits * num) / den;                               // floor8(stake × exact)const display = (100n * num) / den;                                    // floor2(exact), in centsconst fmt = (u, d) => `${u / 10n ** BigInt(d)}.${String(u % 10n ** BigInt(d)).padStart(d, '0')}`;console.log('display    =', `${fmt(display, 2)}×`, '| credit =', fmt(credit, 8));// Expected for the defaults (bet 9pcR2FW8GQmNQ4VusJYDd, epoch 131 nonce 0, 3 mines, 5 tiles, $0.10)://   commitment = 140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27//   mines      = [14,24,1]//   display    = 1.99× | credit = 0.19973684
6.9Python Verification Script

The same verifier in Python 3, standard library only — the payout is computed in exact rational arithmetic, so it doubles as an independent check on the JavaScript version:

verify-mines.py · Standalone Python
# verify-mines.py — standalone LIQD Mines verifier (Python 3, stdlib only)# Usage: python3 verify-mines.py <serverSeed> <clientSeed> <nonce> <mineCount> [tilesOpened] [stake]import hashlib, hmac, struct, sysfrom fractions import Fraction args = sys.argv[1:] or ['f6f3c5efd80a81c02a4033a3a370e6b9', 'audit4d3ce43da272', '0', '3', '5', '0.10']server_seed, client_seed, nonce, mine_count = args[0], args[1], int(args[2]), int(args[3])k = int(args[4]) if len(args) > 4 else 1stake = Fraction(args[5] if len(args) > 5 else '0.10') # 1) Commitment — SHA-256 of the hex string as UTF-8 textprint('commitment =', hashlib.sha256(server_seed.encode('utf-8')).hexdigest()) # 2) Bias-free uniform draw — HMAC-SHA256 keyed on the 16 decoded bytesdef draw(cursor, rng):    digest = hmac.new(bytes.fromhex(server_seed),                      f'{client_seed}:{nonce}:{cursor}'.encode(), hashlib.sha256).digest()    max_fair = (2**32 // rng) * rng    for chunk in struct.unpack('>8I', digest):        if chunk < max_fair:            return chunk % rng    raise ValueError('all eight chunks rejected (not specified; below 1e-66 per draw)') # 3) Sequential Fisher-Yates over tiles 1..25 — draw order preservedtiles = list(range(1, 26))for c in range(25):    j = c + draw(c, 25 - c)    tiles[c], tiles[j] = tiles[j], tiles[c]mines = tiles[:mine_count]print('mines      =', mines) # 4) Cash-out after k safe tiles — exact rational multiplier; credit floored at 8 dp, display at 2 dpexact = Fraction(99, 100)for i in range(k):    exact *= Fraction(25 - i, 25 - mine_count - i)credit = Fraction(int(stake * exact * 10**8), 10**8)display = Fraction(int(exact * 100), 100)print('display    = %.2fx | credit = %.8f' % (display, credit))# Expected for the defaults (bet 9pcR2FW8GQmNQ4VusJYDd, epoch 131 nonce 0, 3 mines, 5 tiles, $0.10):#   commitment = 140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27#   mines      = [14, 24, 1]#   display    = 1.99x | credit = 0.19973684
6.10Evidence Screenshots & Records8 records

The screenshots are live-session captures that illustrate the player path; they are not rounds from the audited dataset (E14 shows a 1.00 USDC demonstration round). The walkthrough’s screenshots are page captures from liqd.com, outside the package’s evidence set.

2 columns
FileShows
E10-mines-game-ui.pngThe Mines interface — 5×5 grid, mine-count selector (shown at 3), Manual / Auto and bet controls
E11-provably-fair-verifier.pngThe Verify tab — client seed / server seed / nonce / mines inputs and the VERIFY action
E12-client-seed-rotation-response.pngThe Seeds tab after a rotation — E13’s next client seed and next server-seed hash as the active pair: the pre-commitment chain in LIQD’s own UI
E13-seed-commitment-rotate-panel.pngThe Seeds tab before play — Active Server Seed (Hashed) 6116a05b…8692bb, the dataset’s epoch-137 next-seed hash, which ties the panel to the audited account and session
E14-floored-multiplier-payout.pngSettle screen for a one-tile, 3-mine demonstration round at 1.00 USDC — the floored display 1.12× beside the credited 1.12500000
E21-revealed-server-seed.pngA round’s Provably Fair section after rotation — client seed, revealed server seed, nonce 0 and 3 mines; the seed hashes to the commitment shown before the round and computes mines at tiles [12, 1, 25]
E22-e21-round-finished-board.pngThe finished board of the E21 round — mines at row 1 column 1, row 3 column 2 and row 5 column 5: the tile numbering, settled on a real round
rtp-page-labels-2026-10-01.jsonThe RTP and house-edge figures on LIQD’s Mines game page, read logged out on 1 October 2026 — the source of the RTP claim (S4.2)
From Bet to Independent Verification — 4-step flow — illustrated flow
How it works
  1. 1

    Place a Mines Bet

    Choose your mine count (1–24) on the 25-tile grid and place a bet. The full mine layout is computed from the seeds before any tile flips — which tiles you click and when you cash out never change where the mines are.

  2. 2

    Open the Provably Fair Panel and Rotate the Seed

    Open the Provably Fair panel on the game page. The Seeds tab shows the active client seed, the Active Server Seed (Hashed) — the commitment, shown before you bet — and the bet count under the pair. Copy the hashed server seed before you rotate: the round view lists only the client seed, server seed, nonce and mine count. Rotating ends the epoch and reveals the outgoing server seed in plaintext — open the round under My Bets to see it in its Provably Fair section. First check the commitment: the SHA-256 of the revealed seed’s hex text must equal the hash you copied — across this audit’s 138 epochs it always did.

  3. 3

    Enter the Seeds in the Verify Tab

    In the panel’s Verify tab, choose Mines and enter the four values the round’s Provably Fair section lists: the revealed server seed, your client seed, the bet’s nonce and the mine count you played.

  4. 4

    Recompute & Confirm

    Press Verify — or enter the same inputs in the ProvablyFair.org verifier. If the commitment check in step 2 passed and the recomputed mines sit exactly where your board showed them — tiles are numbered 1–25 across the rows from the top-left — the bet was provably fair. The scripts under Manual Verification also check the credit.

Visual Walkthroughstep-by-step
6.1Step 1: Place a Mines Bet

Pick a mine count on the 5×5 grid and place a bet. The platform computes the entire layout from (serverSeed, clientSeed, nonce) and the mine count before any tile is revealed — the round below runs at 16 mines, with two tiles opened before the cash-out.

LIQD Mines on liqd.com — a $1.00 bet on 16 mines (the slider shows 9 gems and 16 mines): two gems opened, at row 2 column 1 and row 4 column 3, and cashed out at 8.25× for $8.25, with the rest of the board revealed.

LIQD Mines on liqd.com — a $1.00 bet on 16 mines (the slider shows 9 gems and 16 mines): two gems opened, at row 2 column 1 and row 4 column 3, and cashed out at 8.25× for $8.25, with the rest of the board revealed.

6.2Step 2: Open the Provably Fair Panel and Rotate the Seed

The panel has two tabs, Seeds and Verify. The Seeds tab holds your active pair and its commitment, with the pre-committed next pair below it. Copy the Active Server Seed (Hashed) before you rotate: after the rotation, the round view lists only the client seed, server seed, nonce and mines. Rotation ends the epoch: the outgoing server seed is revealed, and the pre-committed next pair becomes the active one — the Seeds tab then shows the next client seed and next server-seed hash from before the rotation as the active pair, with zero bets made (E12). That pre-commitment is the chain the audit’s Step 2 checks across all 137 links, plus the pre-capture link into epoch 0. Open the round under My Bets: its Provably Fair section shows the revealed server seed beside the client seed, nonce and mine count. Check that the seed’s SHA-256 equals the hash you copied.

Active Server Seed (Hashed) — the commitment for the current epoch; the plaintext is revealed when you rotate and must hash to this value.

Active Client Seed — your player-controlled entropy input, set through the Next Client Seed field before you rotate.

Total bets made with pair — the bet counter under the active pair, which locates each round’s nonce within the epoch.

Next Server Seed — labelled as a seed, but it holds the next epoch’s hash: the pre-commitment for the pair that follows.

The Seeds tab before rotating — Active Server Seed (Hashed) 95c3d981…0c1fa0 after 6 bets, and the next server seed’s hash already published. The seed revealed by the rotation, a50bbca8…2a8524, hashes to 95c3d981…0c1fa0.

The Seeds tab before rotating — Active Server Seed (Hashed) 95c3d981…0c1fa0 after 6 bets, and the next server seed’s hash already published. The seed revealed by the rotation, a50bbca8…2a8524, hashes to 95c3d981…0c1fa0.

6.3Step 3: Enter the Seeds in the Verify Tab

The Verify tab takes the four values the round’s Provably Fair section lists — client seed, server seed, nonce and mines — and recomputes a layout on demand. Choose Mines and enter the server seed revealed in step 2, your client seed, the round’s nonce and the mine count you played. For the round in step 1: client seed 2167d363a51ccf60d479fc24b95f5bcf, server seed a50bbca804aa27ce5105b6fc972a8524, nonce 5 — the pair’s sixth bet — and 16 mines.

6.4Step 4: Recompute & Confirm

Press Verify. The tab recomputes the full 25-tile layout, so you can check that every tile you opened — and the mine that ended the round, if you hit one — sits exactly where the recomputed layout says. Tiles are numbered 1–25 across the rows from the top-left. For the round in step 1, the seed hashes to 95c3d981…0c1fa0, the commitment the Seeds tab showed before the rotation, and the layout computes to the 16 mines [15, 23, 8, 14, 25, 22, 1, 11, 10, 2, 3, 16, 7, 20, 21, 13], in draw order: every tile except 4, 5, 6, 9, 12, 17, 18, 19 and 24 — the board in step 1, with both opened tiles, 6 and 18, safe. Two safe tiles on 16 mines pay 8.25×: $8.25 on the $1.00 stake. The scripts under Manual Verification reproduce all of it.

Technical evidence
6.5Why Manual Verification Matters

Hosted verifiers — including ours — are conveniences. The guarantee only becomes unconditional when you can reproduce a round with tools nobody else controls. The algorithm below is small enough to audit by eye and run anywhere: if the revealed seed matches the hash you saw before betting and your recomputation matches the round you played, no one in the chain — casino, auditor, or tool host — changed it after you bet.

6.6How the Algorithm Works (Plain English)

Five pieces, in the order they come into play:

  • Commit — before you play, LIQD publishes SHA-256 of the server seed (hashed as the 32-character hex string). The seed itself stays secret until rotation.
  • Draw — each shuffle position asks HMAC-SHA256(key = decoded seed, message = clientSeed:nonce:cursor) for one uniform number, discarding the biased tail of the 32-bit space first.
  • Shuffle — tiles 1…25 are swapped one position at a time (cursor 0…24); when the shuffle finishes, the first mineCount tiles are the mines, in the order they were drawn.
  • Board — tile t sits at row ⌈t / 5⌉, column ((t − 1) mod 5) + 1: numbered across the rows from the top-left.
  • Pay — cashing out after k safe tiles credits stake × 0.99 × Π (25−i)/(25−m−i), floored at the 8th decimal; the panel displays the multiplier floored to 2 decimals.

Note the two hashing domains: the commitment hashes the hex string as UTF-8 text, while the HMAC key is the 16 decoded bytes. Mixing them up is the most common reason a hand-rolled verifier disagrees with the game — both scripts below get it right, and both print the featured bet's expected output in their comments so you can self-check the tool before trusting it.

6.7Casino Verifier vs ProvablyFair.org Verifier4 records

Both should agree — the point of having an independent one is what happens if they ever don't:

3 columns
LIQD Built-in (in-game panel)ProvablyFair.org
Who controls the codeThe casinoProvablyFair.org — open source
What it proves aloneInternal consistency of LIQD’s own mathIndependent recomputation from raw inputs
Checked against this captureIndirectly — LIQD’s own validation vectors agree with our implementation (3 / 3), and the live integrity tests checked five layouts against it (LIQD-MINES-EXTRA-002, S5)Directly — its Mines module, pinned by hash, reproduces 6,900 / 6,900 captured layouts in draw order (Step 5)
If they disagreeSuspect the casino’s tool or a tampered roundThe repo + scripts are the tiebreaker anyone can run
6.8JavaScript Verification Script

Complete, dependency-free, and pre-loaded with the featured bet as its default inputs — run it as-is to see a full match, then swap in your own round. Run on every captured round, it reproduces all 6,900 layouts and every credit, and all 1,000 supplement rounds:

verify-mines.js · Standalone Node.js
// verify-mines.js — standalone LIQD Mines verifier (Node.js, no dependencies)// Usage: node verify-mines.js <serverSeed> <clientSeed> <nonce> <mineCount> [tilesOpened] [stake]const { createHash, createHmac } = require('node:crypto'); const argv = process.argv.slice(2);const [serverSeed, clientSeed, nonceArg, minesArg, tilesArg, stakeArg] = argv.length  ? argv  : ['f6f3c5efd80a81c02a4033a3a370e6b9', 'audit4d3ce43da272', '0', '3', '5', '0.10'];const nonce = Number(nonceArg), mineCount = Number(minesArg);const k = Number(tilesArg ?? '1'), stake = stakeArg ?? '0.10'; // 1) Commitment: SHA-256 of the hex STRING (UTF-8) — what the panel showed before you playedconsole.log('commitment =', createHash('sha256').update(serverSeed, 'utf8').digest('hex')); // 2) One uniform draw per shuffle position — HMAC keyed on the DECODED bytesfunction draw(cursor, range) {  const digest = createHmac('sha256', Buffer.from(serverSeed, 'hex'))    .update(`${clientSeed}:${nonce}:${cursor}`).digest();  const maxFair = Math.floor(0x1_0000_0000 / range) * range;  for (let o = 0; o + 4 <= digest.length; o += 4) {    const chunk = digest.readUInt32BE(o);    if (chunk < maxFair) return chunk % range;  }  throw new Error('all eight chunks rejected (not specified; below 1e-66 per draw)');} // 3) Sequential Fisher-Yates over tiles 1..25 — the first mineCount tiles are the mines, in draw order.//    Tile t sits at row ceil(t / 5), column ((t − 1) % 5) + 1, counting across the rows from the top-left.const tiles = Array.from({ length: 25 }, (_, i) => i + 1);for (let c = 0; c < 25; c++) {  const j = c + draw(c, 25 - c);  [tiles[c], tiles[j]] = [tiles[j], tiles[c]];}const mines = tiles.slice(0, mineCount);console.log('mines      =', JSON.stringify(mines)); // 4) Cash-out after k safe tiles: exact = 0.99 × Π (25−i)/(25−m−i), kept as an exact fraction (BigInt)let num = 99n, den = 100n;for (let i = 0; i < k; i++) { num *= BigInt(25 - i); den *= BigInt(25 - mineCount - i); }const [whole, frac = ''] = stake.split('.');const stakeUnits = BigInt(whole + frac.padEnd(8, '0').slice(0, 8));   // stake in 1e-8 unitsconst credit = (stakeUnits * num) / den;                               // floor8(stake × exact)const display = (100n * num) / den;                                    // floor2(exact), in centsconst fmt = (u, d) => `${u / 10n ** BigInt(d)}.${String(u % 10n ** BigInt(d)).padStart(d, '0')}`;console.log('display    =', `${fmt(display, 2)}×`, '| credit =', fmt(credit, 8));// Expected for the defaults (bet 9pcR2FW8GQmNQ4VusJYDd, epoch 131 nonce 0, 3 mines, 5 tiles, $0.10)://   commitment = 140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27//   mines      = [14,24,1]//   display    = 1.99× | credit = 0.19973684
6.9Python Verification Script

The same verifier in Python 3, standard library only — the payout is computed in exact rational arithmetic, so it doubles as an independent check on the JavaScript version:

verify-mines.py · Standalone Python
# verify-mines.py — standalone LIQD Mines verifier (Python 3, stdlib only)# Usage: python3 verify-mines.py <serverSeed> <clientSeed> <nonce> <mineCount> [tilesOpened] [stake]import hashlib, hmac, struct, sysfrom fractions import Fraction args = sys.argv[1:] or ['f6f3c5efd80a81c02a4033a3a370e6b9', 'audit4d3ce43da272', '0', '3', '5', '0.10']server_seed, client_seed, nonce, mine_count = args[0], args[1], int(args[2]), int(args[3])k = int(args[4]) if len(args) > 4 else 1stake = Fraction(args[5] if len(args) > 5 else '0.10') # 1) Commitment — SHA-256 of the hex string as UTF-8 textprint('commitment =', hashlib.sha256(server_seed.encode('utf-8')).hexdigest()) # 2) Bias-free uniform draw — HMAC-SHA256 keyed on the 16 decoded bytesdef draw(cursor, rng):    digest = hmac.new(bytes.fromhex(server_seed),                      f'{client_seed}:{nonce}:{cursor}'.encode(), hashlib.sha256).digest()    max_fair = (2**32 // rng) * rng    for chunk in struct.unpack('>8I', digest):        if chunk < max_fair:            return chunk % rng    raise ValueError('all eight chunks rejected (not specified; below 1e-66 per draw)') # 3) Sequential Fisher-Yates over tiles 1..25 — draw order preservedtiles = list(range(1, 26))for c in range(25):    j = c + draw(c, 25 - c)    tiles[c], tiles[j] = tiles[j], tiles[c]mines = tiles[:mine_count]print('mines      =', mines) # 4) Cash-out after k safe tiles — exact rational multiplier; credit floored at 8 dp, display at 2 dpexact = Fraction(99, 100)for i in range(k):    exact *= Fraction(25 - i, 25 - mine_count - i)credit = Fraction(int(stake * exact * 10**8), 10**8)display = Fraction(int(exact * 100), 100)print('display    = %.2fx | credit = %.8f' % (display, credit))# Expected for the defaults (bet 9pcR2FW8GQmNQ4VusJYDd, epoch 131 nonce 0, 3 mines, 5 tiles, $0.10):#   commitment = 140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27#   mines      = [14, 24, 1]#   display    = 1.99x | credit = 0.19973684
6.10Evidence Screenshots & Records8 records

The screenshots are live-session captures that illustrate the player path; they are not rounds from the audited dataset (E14 shows a 1.00 USDC demonstration round). The walkthrough’s screenshots are page captures from liqd.com, outside the package’s evidence set.

2 columns
FileShows
E10-mines-game-ui.pngThe Mines interface — 5×5 grid, mine-count selector (shown at 3), Manual / Auto and bet controls
E11-provably-fair-verifier.pngThe Verify tab — client seed / server seed / nonce / mines inputs and the VERIFY action
E12-client-seed-rotation-response.pngThe Seeds tab after a rotation — E13’s next client seed and next server-seed hash as the active pair: the pre-commitment chain in LIQD’s own UI
E13-seed-commitment-rotate-panel.pngThe Seeds tab before play — Active Server Seed (Hashed) 6116a05b…8692bb, the dataset’s epoch-137 next-seed hash, which ties the panel to the audited account and session
E14-floored-multiplier-payout.pngSettle screen for a one-tile, 3-mine demonstration round at 1.00 USDC — the floored display 1.12× beside the credited 1.12500000
E21-revealed-server-seed.pngA round’s Provably Fair section after rotation — client seed, revealed server seed, nonce 0 and 3 mines; the seed hashes to the commitment shown before the round and computes mines at tiles [12, 1, 25]
E22-e21-round-finished-board.pngThe finished board of the E21 round — mines at row 1 column 1, row 3 column 2 and row 5 column 5: the tile numbering, settled on a real round
rtp-page-labels-2026-10-01.jsonThe RTP and house-edge figures on LIQD’s Mines game page, read logged out on 1 October 2026 — the source of the RTP claim (S4.2)
✓  Any Player Can Reproduce Mines Results

Only disclosed inputs are used. Identical inputs always produce identical output.

07

Reproducibility & Artifacts

Can anyone independently reproduce every scored check in this audit?

This section consolidates the open-source repository, datasets, output artifacts, and reproducibility posture of the audit. Every scored finding, statistic and pass/fail result can be independently reproduced by anyone with a computer and an internet connection — only the procedures of the live integrity tests in S5 are withheld, by policy. The repository is the credential — not this report.

Open-Source Audit Repository

v1.0.1release audited

Repository Details

Prerequisites

  • Node.js 22.x (package.json engines and .nvmrc) — npm test needs no dependency install
  • Git
  • TypeScript toolchain via npm ci (TypeScript 5.9.3 · ts-node 10.9.2 · Mocha 11.7.6) — for the source commands
  • Python 3.9+ — only for npm run bootstrap-replay and the optional standalone checker in S6
v1.0.1release audited
github.com/ProvablyFair-org/liqd-mines

Commands to Reproduce

The 6,900-bet dataset, the multi-reveal supplement and the committed results ship inside the repo — no separate download, and every dataset’s SHA-256 is checked before anything runs. npm test needs only Node.js 22: it checks that the bundles match the sources, runs the 49 unit tests and the 22-step verification — which replays all 24M simulated rounds — and checks the release manifest. Output as run on Node 22.22.0 with two CPU cores (about 11½ minutes); the 24M-round replay runs in parallel worker processes, about 70 seconds on 12 cores.

sh
git clone https://github.com/ProvablyFair-org/liqd-mines.gitcd liqd-minesgit checkout v1.0.1npm ci # Run the entire audit end-to-end (49 unit tests + 22-step verification + release-manifest check)npm test
Expected output
▶ [test] stale-build and report-count gate: node scripts/check-standalone.mjs[standalone] Source inventory, unit-test inventory and both compiled entry points match the build manifest.…▶ [test] bundled unit specs: node test.js# tests 49# pass 49# fail 0[standalone] 49/49 unit specs passed (src/pins.ts EXPECTED_UNIT_TESTS). ▶ [test] bundled verifier (verdict): node verify.js…  Passed:     22/22  Hard fails: 0  Flags:      0 (0 verdict-affecting, 0 capture-origin)   VERDICT: PROVABLY FAIR — Full Pass…  Reproduction vs the committed artifacts on Node 22.22.0: REPRODUCED ▶ [test] release manifest and release bar: node scripts/release-manifest.mjs --check  [release-manifest] OK: release bar met; 10 artifact hashes match disk, verdict "PROVABLY FAIR — Full Pass", scored 22/22 ── run-stages: test ── every stage ran  stale-build and report-count gate            ok  bundled unit specs                           ok  bundled verifier (verdict)                   exit 0 (verdict: Full Pass)  release manifest and release bar             ok  RESULT: exit 0 — every other stage passed; the exit code is the verifier's verdict code

Audit Reproducibility Pinning

Framework
ProvablyFair.org Audit Framework 1.1 · Mines profile 1.0.2 · RTP-CLAIM 1.2.0
Release Signature
Release commit and tag signed with the ProvablyFair.org release key (SSH, SHA256:gEujiJm2pRyq7lQsfYQfQ1r97H/J7GdaKHbOW8GtikY), recorded in RELEASE-MANIFEST.json
Node Version
22.x (package.json engines and .nvmrc); run here on 22.22.0
Primary Dataset
data/mines-master-6900bets.json (6,900 bets, 138 seed epochs)
Primary Dataset Hash (SHA-256)
c7c2e8a930549e11e3e564a962aea0ca2875b6eb94bc1f4347cc703bc410b222
Supplement Dataset
data/liqd-mines-capture-v3-2026-10-03T11-58-54-479Z.json (1,000 multi-reveal rounds, 20 seed epochs) and its journal
Supplement Hashes (SHA-256)
a7737572394215b0262eb467eec4fe5ff083e87c0d132746aa6e66c271277b45 · journal db16421c70d55331049ce186d03fa1c60367c84b208252f920e8e91f2074e9c9
Simulation Artifact Hash (SHA-256)
8854fc57e7f22f1960b9c89f6c8e003a5e92e19d899a3ecf20e0c0170e44fc2c
Bootstrap Null Hash (SHA-256)
477c58267cf890751abadaf48a723b569207838be7838d7a20da9b2e2d09cf18
Pass 1 Replay Report Hash (SHA-256)
6de3935092af3db155c9e608f7f5993b68b31ac25364ad24ac0c0ce021c8d6bc
Convergence Chart Hash (SHA-256)
ad969c106e979f3f0eabcea69b5d2d881eb4a44be248e670a215e6a561989d2d
RTP Claim Table Hash (SHA-256)
b8293e56cb2f77d5e58e176b1e7cb3b7cbb732bcd5c6a6131ae292fc23839a4a
Verification Results Hash (SHA-256)
25db7665027a4c0df089d1b711fb15c68449214be153ee0cd468d93eaf1ba745
Derived Figures
outputs/audit-figures.json — pinned by SHA-256 and re-derived field-for-field from the pinned dataset on every run (Step 19)
Reproduction Gate
Every run writes only to outputs/run/ and is compared field by field with the committed artifacts (outputs/run/diff.json) — exact content byte for byte, a derived floating-point statistic within 1e-14 + 1e-12 relative; anything beyond tolerance exits 1
Exit Codes
0 Full Pass · 2 operator-origin flag (Conditional Pass) · 5 RTP FIGURE OVERSTATED · 1 hard fail or reproduction beyond tolerance
Mutation Battery
50 must-fail source mutations, each caught by the step it names, plus 11 declared survivors; 63 forged datasets and artifacts, each rejected — as recorded in the package (several hours per battery)
Runtime
npm test ≈ 11½ min on 2 CPU cores, most of it the 24M-round replay (≈ 70 s on 12 cores) · Node 22.22.0
Capture Environment
QA (pre-production) host qa.liqd.com · one account (auditor-attested)
Audit Date
July–October 2026 (dataset captured 30 July 2026); multi-reveal supplement captured 3 October 2026; RTP page read 1 October 2026; player-path screenshots are live-session captures, not dataset rounds
Audit ID
PF-2026-LQ02
Output Artifacts9 files committed
3 columns
FileContentsSize
verification-results.jsonSteps 1–22 results — per-step pass states with evidence detail strings, profile coverage (MINES-01 … MINES-30), artifact hashes, info items, verdict~86 KB
rtp-claim-table.jsonThe RTP claim rule’s table — LIQD’s published 99% against all 300 settings, each with exact RTP, gap and outcome~41 KB
simulation-results.jsonPass 1 per-config results (saved seed pairs, first-draw χ², joint max |z|, serial, convergence traces) + Pass 2 bootstrap scan with the recorded master seed~82 KB
simulation-provenance.jsonReplay inputs for both passes — master seed, derivation version, parameters, artifact and null hashes, replay commands~2 KB
audit-figures.jsonDerived figures (300-setting reference grid, settlement floor, client-seed controls, rejection census, live aggregate, false-alarm accounting, simulation summary) — re-derived by Step 19~196 KB
bootstrap-nulls.float64leThe retained sorted bootstrap null — 10,000 float64 statistics per mine count, mine counts 1–241.92 MB
bootstrap-replay-report.jsonIndependent Python replay of Pass 2 — 240,000 statistics and 138 p-values, 0 mismatches~20 KB
deep-replay-report.jsonFull Pass 1 replay — 24 rows, 24,000,000 rounds, 0 differences~6 KB
rtp-convergence.htmlConvergence chart, self-contained (Chart.js inlined) — the same data as the chart on this page~210 KB
Repository Structure
liqd-mines/
├── src/
│   ├── rng.ts                  → commitHash · HMAC draw (generateProvablyFairNumber) · revealMines (sequential Fisher-Yates, draw order) — written from docs/ALGORITHM.md
│   ├── bias.ts                 → the draw-bias bound for every range, from the same maxFairFor the draw applies
│   ├── config.ts               → closed-form multiplier (exact + BigInt display cents), anti-circular win probability, game constants
│   ├── pins.ts                 → pinned population (6,900 · 138 · 50 · phase plan), nonce window, dataset, artifact and supplement hashes
│   ├── rtp-claim.ts · rtp-claim-switch.ts → the RTP claim rule (RTP-CLAIM 1.2.0) and its enforcement switch (on)
│   ├── rtp-settings.ts         → the 300-setting exact RTP table and LIQD’s published claim
│   ├── profile-map.ts          → which Mines profile check (MINES-01 … MINES-30) each step implements
│   ├── site-verifier.ts        → loads the vendored ProvablyFair.org verifier for the Step 5 parity arm
│   ├── loader.ts               → dataset loader + SHA-256 guard (aborts on mismatch)
│   ├── simulate.ts · seed-derivation.ts → a new Pass 1 + Pass 2 experiment under a recorded master seed → outputs/run/
│   ├── replay.ts · replay-pool.ts · deep-replay.ts → the 24M-round Pass 1 replay (inside every verify run, or stand-alone)
│   ├── stats.ts                → chi-squared (exact incomplete gamma), max-|z| joint test, autocorrelation, runs test
│   ├── false-alarm.ts          → nominal false-alarm accounting of the eight scored screens
│   ├── figures.ts · write-figures.ts → outputs/audit-figures.json, the producing artifact for every quoted figure
│   ├── convergence-chart.ts · write-chart.ts → the self-contained convergence chart
│   ├── domains.ts              → value domains for every numeric field of the committed artifacts (Step 19)
│   ├── diff.ts                 → field-level diff of a run vs the committed artifacts (outputs/run/diff.json)
│   └── types.ts                → dataset record types
├── docs/
│   ├── ALGORITHM.md            → the specification the RNG is written from
│   └── INTERFACE.md            → the module interface the RNG implements
├── verifier-parity/            → the ProvablyFair.org site verifier’s Mines module, vendored byte for byte and pinned (Step 5)
├── tests/
│   ├── verify.ts               → 22-step verification runner (results under outputs/run/; PF_EMIT=1 to regenerate the committed record)
│   ├── mines/                  → 49 unit tests (rngTests 10 · rngSpecTests 1 [98 checks] · qaRegressionTests 38)
│   ├── mutations.ts · mutations.json · survivors.json → 50 must-fail source mutations + 11 declared survivors
│   ├── forgeries.ts            → 63 forged datasets and artifacts that must be rejected
│   ├── fixtures/               → a frozen regression fixture (digest-pinned)
│   └── steps/
│       ├── commitment.ts       → Steps 1–4 (seed hashes, chain + pre-capture link + recorded order, epoch consistency, nonce windows)
│       ├── determinism.ts      → Steps 5–6 (layout recompute + draw-bias census + site-verifier parity, client-seed influence)
│       ├── payouts.ts          → Steps 7–10 (display field, floor8 payout identity + win condition, configs, house edge + RTP claim)
│       ├── dataset.ts          → Steps 11–12 (phase labels, epoch size — population guard)
│       ├── anti-circularity.ts → Step 13 (combinatorial win probability + counting anchor)
│       ├── phase-d.ts          → Step 14 (custom client seeds)
│       ├── phase-e.ts          → Step 15 (multi-reveal verification)
│       ├── simulation.ts       → Steps 16–17 (Pass 1 replay, served-config test + Pass 2 bootstrap replay)
│       ├── standardization.ts  → Steps 18–21 (bet size, artifact integrity, reveal sequence, reveal position)
│       ├── supplement.ts       → Step 22 (the 1,000-round multi-reveal supplement)
│       ├── statistical.ts      → informational items (live RTP, lag-1, win rate)
│       └── context.ts          → shared context + step helper
├── scripts/                    → run-stages (npm test), standalone build + check, release manifest + release bar, site-verifier check, Python bootstrap replay
├── capture/                    → capture-mines.reference.mjs (the audited dataset’s capture flow, sanitized) · capture-mines-v3.mjs (the supplement’s client) + shared helpers
├── data/
│   ├── mines-master-6900bets.json   → 6,900 bets · 138 epochs · SHA-256 pinned
│   └── liqd-mines-capture-v3-2026-10-03….json · .journal.ndjson → the multi-reveal supplement and its journal · SHA-256 pinned
├── outputs/
│   ├── verification-results.json    → 22 steps + profile coverage + info items + artifact hashes + verdict
│   ├── rtp-claim-table.json         → the RTP claim rule over all 300 settings
│   ├── simulation-results.json      → Pass 1 rows (saved seed pairs, convergence) + Pass 2 (master seed recorded)
│   ├── simulation-provenance.json   → replay inputs, parameters and hashes for both passes
│   ├── audit-figures.json           → derived figures, re-derived field-for-field by Step 19
│   ├── bootstrap-nulls.float64le    → the retained sorted bootstrap null (10,000 × 24 statistics)
│   ├── bootstrap-replay-report.json → Python replay: 240,000 statistics + 138 p-values, 0 mismatches
│   ├── deep-replay-report.json      → Pass 1 replay: 24 rows, 24,000,000 rounds, 0 differences
│   └── rtp-convergence.html         → self-contained convergence chart (same data as the chart on this page)
├── evidence/                   → E10–E14, E21, E22 UI screenshots · RTP page labels
├── .github/workflows/reproduce.yml → second-machine reproduction on a clean runner
├── test.js · verify.js         → dependency-free bundles run by `npm test`
├── README.md · MANIFEST.md     → the report (findings, evidence register, worked examples, limitations register)
├── RELEASE-MANIFEST.json       → release identity: tag, framework, artifact hashes, verdict, signing key
├── package.json                → npm test = staleness gate → node test.js → node verify.js → release-manifest --check
└── package-lock.json · tsconfig.json · .mocharc.yml · .nvmrc · .gitattributes · .gitignore · LICENSE
Step-to-Section Cross-Reference22 verification steps mapped
3 columns
StepTest NameReport Section
1Seed Hash IntegrityS1
2Next-Seed Pre-Commitment ChainS1
3Hash Consistency Within EpochS1
4Nonce AuditS1
5Mine-Layout RecomputationS1, S2, S3
6Client Seed InfluenceS1, S2
7Displayed Multiplier Field (floored, cosmetic)S3, S4
8Payout Math + Win-Condition (wins: betAmount × EXACT multiplier == winningAmount; losses: 0; label ⇔ mine opened)S3, S4
9Config Completeness (Phase A: 24 mineCounts × 200)S3, S4
10House Edge / RTP Audit (closed-form identity, per-config, anti-circular)S4 (also scores the RTP claim rule)
11Phase LabelsS3
12Epoch SizeS1, S3
13Anti-Circularity (Combinatorial Win Probability)S4
14Phase D — Client Seed VariationS1, S2, S3
15Phase E — Multi-Reveal VerificationS3, S4
16Simulation — Pass 1 (First-Draw · Joint · Serial, FWER)S2, S4
17Simulation — Pass 2 Cherry-Pick TestS4
18Bet-Size InvarianceS3, S4
19Artifact Integrity (dataset pin + derived figures)S7
20Reveal-Sequence ConsistencyS3
21Reveal-Position IndependenceS3
22Multi-Reveal Supplement (1,000 rounds)S3, S4
Game Profile CoverageMines profile 1.0.2 · 30 checks

Every check in the ProvablyFair.org Mines profile, version 1.0.2, is covered by a scored step, by a package check, or marked n/a with a reason — 23 by scored steps, 6 by package checks, 1 n/a. For MINES-09 and MINES-17 the row states the exact scope it covers. The map ships in src/profile-map.ts and is written into each run’s verification results.

4 columns
CheckWhat it requiresCovered byCoverage
MINES-01Every revealed seed hashes to its commitmentStep 1Full
MINES-02Next-seed pre-commitment chain, including the pre-capture linkStep 2Full
MINES-03One commitment and one client seed per epochSteps 3, 4Full
MINES-04Nonce contiguity, no reuse, no withholding; capture-retry gaps flagged, operator gaps failedStep 4Full
MINES-05Audited population bound to code constants, not the dataset headerSteps 11, 12Full
MINES-06Client-seed control and sensitivitySteps 6, 14Full
MINES-07Every served mine layout recomputes, in draw orderStep 5Full
MINES-08Draw-bias bound computed from the draw’s own rejection ceilingStep 5Full
MINES-09Reference RNG provenance and known-input vectorsPackage check: docs/ALGORITHM.md, src/rng.ts, 49 unit testsLIQD’s three validation vectors and the package’s known-input tests; the RNG is our own implementation, written from the specification, and LIQD’s verify endpoint is checked live (five layouts, LIQD-MINES-EXTRA-002 in S5)
MINES-10Win/loss label against the recomputed board; reveal-sequence rulesSteps 8, 20Full
MINES-11Settlement — winning credits at floor8(stake × exact), losses at 0Steps 7, 8Full
MINES-12Bet-size invarianceStep 18Full
MINES-13Multiplier formula at every setting; display rounding with the exact value creditedSteps 7, 10, 13, 15Full
MINES-14Multiplier monotonicity; the payout limit is reported in S4.7Step 10Full
MINES-15Coverage — all 24 mine counts, several depths, all 25 first picks, the full reference gridSteps 9, 19, 21, 22Full
MINES-16Wallet balance ledger reconciliation—N/A — the capture carries no balance field, so there is no balance delta to reconcile; the settle-response credits are checked in Step 8
MINES-17Multi-reveal — layouts, per-reveal status, cash-out amount, order independenceSteps 15, 20, 21, 22Layouts, per-reveal status, cash-out credits and order independence; LIQD’s reveal responses carry no running multiplier, so the cash-out credit is the compared value
MINES-18Anti-circularity — the identity at every setting, counting anchor and negative controlSteps 10, 13Full
MINES-19Effective edge against the claimed edge; the RTP claim ruleStep 10Full
MINES-20Simulation Pass 1 — replayed, not trustedStep 16Full
MINES-21Cherry-pick test — served-config returnStep 17Full
MINES-22Live-sample fit (informational)Step 9Full
MINES-23Framework fairness-integrity rows S5-1 … S5-8, kept as a package checklist (Section 5 reports the live integrity tests)Package check: MANIFEST.md matrixFull
MINES-24Player-verifier parity — the ProvablyFair.org verifierStep 5Full
MINES-25Player verification path, in LIQD’s field namesPackage check: MANIFEST.md Verify a bet + E12, E13, E21, E22Full
MINES-26Dataset pinned by SHA-256 before any step runsStep 19Full
MINES-27Artifacts of record pinned and re-hashed; every numeric value inside a declared domainStep 19Full
MINES-28Release gate — fresh clone, pinned runtime, zero-install npm testPackage check: scripts/run-stages.mjs, package.json, .nvmrcFull
MINES-29Mutation and forgery batteries with declared survivorsPackage check: tests/mutations.json, tests/survivors.json, tests/forgeries.tsFull
MINES-30Limitations register and numbered evidencePackage check: MANIFEST.mdFull
reproduce-audit.sh
reproduce-audit.sh
# Clone and setupgit clone https://github.com/ProvablyFair-org/liqd-mines.gitcd liqd-minesgit checkout v1.0.1npm ci # Run the full audit end-to-end (49 unit tests + the 22 verification steps + release-manifest check)npm test # Or run individual componentsnpm run verify           # 22-step verification from source — writes outputs/run/, never the committed resultsnpm run deep-replay      # the 24M-round Pass 1 replay, stand-alone (read-only)npm run bootstrap-replay # independent Python rebuild of all 240,000 Pass 2 null statistics (Python 3.9+)npm run simulate         # a new 24M-round experiment into outputs/run/ under a fresh master seed # View the committed resultscat outputs/verification-results.jsoncat outputs/rtp-claim-table.json | head -40
provablyfair.org/verify-bets/liqd
This is an open-source audit. Every scored finding, dataset, and line of verification code is publicly accessible. You don't need to trust us or the casino. Clone the repo, run the tests, and verify every claim yourself.
✓  Fully Reproducible

All audit-package results reproduce from release v1.0.1, the pinned datasets and the commands above. The dataset hashes are checked before any step runs, so you are provably testing the same 6,900 bets and 1,000 supplement rounds; the simulation, bootstrap null, replay reports, chart, derived figures and RTP claim table are pinned by SHA-256 and re-hashed on every run. Every verify run replays all 24,000,000 Pass 1 rounds from their saved seeds and rebuilds the Pass 2 null from its recorded master seed.