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

Mines
Independent verification, backed by open-source code.
- Live Bets Verified
- 6,900
- Parity Rate
- 100%
- Simulated Rounds
- 24M
- Simulated RTP 98.9427%
- Proven RTP
- 99.0000%
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.
The casino result, reproduced.
- Casino outcome
- 5 safe reveals · Cashed out
- Our reproduction
- 5 safe reveals · Cashed out
- Recorded payout
- $0.1997
- Seed Hash IntegrityPass
- Next-Seed ChainPass
- Hash ConsistencyPass
- Nonce AuditPass
- Mine-Layout RecomputationPass
- Client Seed InfluencePass
- Displayed MultiplierPass
- Payout + Win-ConditionPass
- Config CompletenessPass
- House Edge AuditPass
- Phase LabelsPass
- Epoch SizePass
- Anti-CircularityPass
- Client Seed VariationPass
- Multi-Reveal (Phase E)Pass
- Simulation Pass 1Pass
- Cherry-Pick TestPass
- Bet-Size InvariancePass
- Artifact IntegrityPass
- Reveal-Sequence ConsistencyPass
- Reveal-Position Indep.Pass
- Multi-Reveal SupplementPass
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.
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.
Audit Verdict
| Check | Result | Evidence |
|---|---|---|
| Overall Status | Pass | 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 Verified | Pass | 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 Parity | Pass | 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 System | Pass | 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 Seed | Pass | 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 Analysis | Pass | 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 Logic | Pass | 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-Circularity | Pass | 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 Integrity | Pass | 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 |
| Determinism | Pass | Full reproducibility confirmed — 6,900 / 6,900 rounds recompute from the recorded inputs (Step 5) |
Overall StatusPass
RTP VerifiedPass
Live ↔︎ Verifier ParityPass
Commit-Reveal SystemPass
Client SeedPass
RNG AnalysisPass
Payout LogicPass
Anti-CircularityPass
Fairness IntegrityPass
DeterminismPass
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
| Area | Description |
|---|---|
| Commit-Reveal System | SHA-256 server seed hashing, pre-bet commitment, next-seed pre-commitment, reveal on rotation |
| Client Seed Origin | Player-controlled seed set after the server commitment — our own random seeds in 128 epochs and 10 custom seeds in Phase D |
| Seed Handling | Client seed control, nonce lifecycle, seed epoch rotation |
| RNG Analysis | HMAC-SHA256 Fisher-Yates verification, rejection sampling, bias analysis |
| Payout Logic | Closed-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 Parity | Independent mine-layout recomputation vs live results across all capture phases, plus the ProvablyFair.org verifier on the same 6,900 rounds |
| RTP Validation | Anti-circularity proof, the RTP claim rule over all 300 settings, simulated RTP (24M rounds), cherry-pick test on the served-config return |
| Fairness Integrity | Integrity 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).
Win Conditions
A round settles one of two ways, and this audit re-derived the label for all 6,900 captured rounds:
| Outcome | Condition | Example (mines=3, k=5) |
|---|---|---|
| Win (cash out) | You cash out with no mine revealed | Five safe tiles → 1.99736842× exact, 1.99× displayed |
| Loss (bust) | You reveal a mine — always the final tile you opened | Mine on the fifth pick → payout 0 |
| Impossible | A loss with no mine opened, or a win with one | Never observed — 0 violations in 6,900 rounds |
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×to1.99736842×
Parameters
| Parameter | Value | Notes |
|---|---|---|
| Grid | 25 tiles (5×5) | Fixed |
| Mine count | 1 – 24 | Player-selected per round; all 24 captured live |
| House edge | 1.00% flat | Applied once inside the exact multiplier — no depth scaling |
| Reveal depth | 1 – (25 − mines) | Cash out after any safe reveal; depths up to 15 exercised live |
| Seed epoch | Until you rotate | The audit rotated the pair every 50 bets (nonces 0–49) |
| Settlement | 8-decimal unit, rounds down | Capture-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:
| Seed Type | Format | Example | Purpose |
|---|---|---|---|
| Server seed | 32-char hex (16 bytes) | f6f3c5efd80a81c02a4033a3a370e6b9 | HMAC key (decoded); committed via SHA-256 of the string before play |
| Client seed | free-form string | audit4d3ce43da272 | Player-editable entropy — mixed into every draw |
| Nonce | integer from 0 | 0 | The bet counter under the active pair (0–49 in this capture) |
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| Reveals (k) | Mines (m) | Multiplier |
|---|---|---|
| 1 | 1 | 1.03125000 |
| 1 | 3 | 1.12500000 |
| 3 | 3 | 1.47857143 |
| 5 | 3 | 1.99736842 |
| 5 | 5 | 3.39258901 |
| 1 | 24 | 24.75000000 |
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
| Check | What it proves |
|---|---|
| Server seed hash shown before play | With the hash shown first, the casino cannot change the layout after you bet |
| SHA-256 of the revealed seed matches the commitment | The revealed seed is the one that was committed |
| Next epoch's hash pre-committed at rotation | The casino cannot mint a fresh seed in response to your play |
| One commitment per epoch, no mid-epoch switching | Every bet is pinned to a single verified commitment |
Seed System
| Check | What it proves |
|---|---|
| Client seed is player-editable after the commitment exists | An unpredictable seed you set after the commitment cannot be pre-evaluated |
| Client seed changes the layout when substituted | Your seed genuinely enters every derivation |
| Nonces run contiguously 0–49 in every epoch | No skipped bet where a discarded outcome could hide |
RNG & Parity
| Check | What it proves |
|---|---|
Layout recomputes from (serverSeed, clientSeed, nonce, mineCount) | No hidden inputs influence the board |
Rejection sampling below maxFair | The specified draw makes every tile exactly equally likely |
| 24M-round bias screens clean at Bonferroni | The screens find no sign of placement bias at scale |
Payout & RTP
| Check | What it proves |
|---|---|
Wins credit floor8(stake × exact multiplier) exactly | The advertised 1.00% edge is the real edge |
Display field = floor2(exact), never above the credit | The on-screen number can only understate your payout |
winChance × exact multiplier = 0.9900 at all 300 settings | The RTP is an identity, not a sampled estimate |
Integrity
| Check | What it proves |
|---|---|
| Losses pay exactly 0 and bust on the final opened tile | No round settles against a board that didn't bust |
| Same wager-free derivation at $0.10 and $10.00 | Both 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.
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 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’snonceStart/nonceEndis checked against it (Step 4)
"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.
revealMines(serverSeed, clientSeed, nonce, mineCount) → [tile, tile, …] // the mines, in draw order — nothing else in, nothing else outPhase Map
The capture is engineered — five phases, each isolating one claim, plus a multi-reveal supplement:
| Phase | Rounds | What it isolates |
|---|---|---|
| A | 4,800 | The full grid — all 24 mine counts at 200 bets each, $0.10, 1 tile |
| B | 1,000 | The highest-variance tail — mineCount 24, thickened for the statistics |
| C | 200 | Stake independence — the identical game at $10.00 |
| D | 500 | Client-seed influence — 10 auditor-chosen seeds, live |
| E | 400 | The multi-reveal payout path — 5 planned tiles at mineCount 3 |
| Supplement | 1,000 | Interactive 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 |
Technical Glossary7 categories▶
Every term used in this report, defined the way the audit uses it.
Core Concepts
| Term | Definition |
|---|---|
| Provably Fair | A 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-Reveal | The 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 Edge | The 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-Circularity | The 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 Rule | The 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
| Term | Definition |
|---|---|
| Server Seed | A 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 Hash | SHA-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 Seed | A 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. |
| Nonce | An 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. |
| Epoch | The 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
| Term | Definition |
|---|---|
| HMAC-SHA256 | Hash-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 Shuffle | The 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. |
| Cursor | In 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 Rejection | Discarding 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
| Term | Definition |
|---|---|
| Win Probability | C(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 Multiplier | The 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 Multiplier | The 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
| Term | Definition |
|---|---|
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 Tile | A 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
| Term | Definition |
|---|---|
| First-Draw Uniformity | A 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) Test | For 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 Multinomial | The 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 Test | Compares 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 Correction | Controls 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 Autocorrelation | Pearson 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 Test | A 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 Bootstrap | The 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 Orphaning | A 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 Fail | Any 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 Test | The 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 Flag | A 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
| Term | Definition |
|---|---|
winningAmount | The 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 / E | The 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 Supplement | A 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. |
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.
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
- 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
- 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
Seed hash integrityPass
Next-seed chainPass
Recorded orderPass
One commitment per epochPass
serverSeedId is one value per epoch, 138 distinct (Step 3)Nonce sequencingPass
Client seed originPass
audit… seeds in 128 epochs and 10 custom pfaudit- seeds in Phase DClient seed controlPass
Client seed influencePass
Deterministic outputPass
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.
| Check | Result | Evidence |
|---|---|---|
| Seed hash integrity | Pass | 138 / 138 revealed seeds hash to their commitments — SHA-256 of the seed text, 0 mismatches (Step 1) |
| Next-seed chain | Pass | Consistent on 137 / 137 rotations, plus the pre-capture commitment that links into epoch 0 (Step 2) |
| Recorded order | Pass | On the capture’s own clock, each epoch’s commitment was recorded before its first bet — 138 / 138 (Step 2) |
| One commitment per epoch | Pass | 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 sequencing | Pass | Nonces 0–49 in every epoch: 0 gaps, 0 duplicates, 0 capture flags (Step 4) |
| Client seed origin | Pass | Set after the server commitment — our own random audit… seeds in 128 epochs and 10 custom pfaudit- seeds in Phase D |
| Client seed control | Pass | 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 influence | Pass | A wrong client seed changes 6,891 / 6,900 layouts; an empty one reproduces only 7 / 6,900 (Step 6) |
| Deterministic output | Pass | Same (serverSeed, clientSeed, nonce, mineCount) always produces the same layout — 6,900 / 6,900 confirmed, in draw order (Step 5) |
Seed hash integrityPass
Next-seed chainPass
Recorded orderPass
One commitment per epochPass
serverSeedId is one value per epoch, 138 distinct (Step 3)Nonce sequencingPass
Client seed originPass
audit… seeds in 128 epochs and 10 custom pfaudit- seeds in Phase DClient seed controlPass
Client seed influencePass
Deterministic outputPass
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.
/** 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'));}Real Example from Live Data:
{ "epoch": 131, "phase": "E", "clientSeed": "audit4d3ce43da272", "hashedServerSeed": "140cbc408754457f6767662eb1a02b5ac12eda07f7f4e916f02800633ce24f27", "nextHashedServerSeed": "fc05c3fc452c5c760597cde92169ff7b583b4430f234431948f72854d55d1ea9", "serverSeed": "f6f3c5efd80a81c02a4033a3a370e6b9", "nonceStart": 0, "nonceEnd": 49}Verification:
// 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.
// ── 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',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).
// ── 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;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)
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.json1.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.
// ── 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`); }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.
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);}mineTiles in draw order (Step 5). Zero mismatches, zero skipped. No external entropy is mixed into the Mines RNG.Real Bet 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 — 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).
// ── 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++; // … }Technical Evidence & Verification5 sections
1.8Evidence Coverage Summary6 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Seed hash integrity (Step 1) | 138 / 138 revealed seeds hash-verified | Pass |
| Next-seed pre-commitment chain and recorded order (Step 2) | 137 / 137 transitions; 138 / 138 commitments recorded before their first bet | Pass |
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 epochs | Pass |
| Mine-layout recomputation (Step 5) | 6,900 / 6,900 — full layout verified, in draw order | Pass |
| Client seed influence (Steps 6, 14) | 6,891 / 6,900 (99.87%) changed by a wrong seed; 500 / 500 Phase D | Pass |
1.9Code References7 source files
| File | Purpose |
|---|---|
| tests/verify.ts | 22-step verification runner (Steps 1–6 cover S1) |
| tests/steps/commitment.ts | Steps 1–4: commit-reveal integrity, recorded order and nonce audit |
| tests/steps/determinism.ts | Steps 5–6: mine-layout recomputation and client seed influence |
| src/rng.ts | HMAC-SHA256 sequential Fisher-Yates (revealMines, generateProvablyFairNumber, commitHash) |
| docs/ALGORITHM.md | The written specification src/rng.ts is built from |
| src/loader.ts | Dataset loading + SHA-256 hash guard |
| capture/capture-mines.reference.mjs | Sanitized capture methodology record (Phases A–E) |
1.10Datasets UsedSource artifacts
| Property | Value |
|---|---|
| Source | Live Mines game data from LIQD’s QA (pre-production) environment, qa.liqd.com — 6,900 real bets across 5 phases, USDC |
| Capture environment | QA (pre-production) host qa.liqd.com · one account (auditor-attested) |
| Total Records | 6,900 bets across 138 epochs (50 bets each) |
| Captured | 30 July 2026 (16:23–18:05 UTC) |
| SHA-256 | c7c2e8a930549e11e3e564a962aea0ca2875b6eb94bc1f4347cc703bc410b222 |
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
| Invariant | Result |
|---|---|
| SHA-256(utf8(serverSeed)) = hashedServerSeed for all 138 revealed seeds | Pass |
| Next-seed pre-commitment chain consistent for all 137 transitions, plus the pre-capture link into epoch 0 | Pass |
| 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 switching | Pass |
| LIQD’s serverSeedId: one value per epoch, 138 distinct | Pass |
| Zero nonce gaps within any epoch (contiguous 0–49), 0 capture flags | Pass |
| Zero nonce duplicates within any epoch | Pass |
| Every epoch contains exactly 50 bets (138 / 138) | Pass |
| Same inputs produce same mine layout, in draw order, for all 6,900 bets | Pass |
| 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:
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 PASSSteps 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 InfluenceRNG & 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.
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
- 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
- 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
RNG derived only from disclosed inputsPass
Entropy purityPass
Our own implementationPass
LIQD’s validation vectorsPass
Modulo biasPass
Key encoding verifiedPass
Serial independencePass
Client seed influencePass
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.
| Check | Result | Evidence |
|---|---|---|
| RNG derived only from disclosed inputs | Pass | HMAC-SHA256(serverSeed, clientSeed:nonce:cursor) — no hidden entropy |
| Entropy purity | Pass | No timestamps, external APIs, Math.random, or server-side state |
| Our own implementation | Pass | Written from the specification without LIQD’s code; reproduces all 6,900 layouts in draw order (Step 5) |
| LIQD’s validation vectors | Pass | LIQD’s own validation vectors pass 3 / 3 against our implementation — a cross-check, not the source |
| Modulo bias | Pass | 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 verified | Pass | Server seed hex-decoded to bytes for the HMAC key; the commitment hashes the seed text — both confirmed via 6,900-bet recomputation |
| Serial independence | Pass | First-drawn tile: lag-1 and runs screens clear at Bonferroni for all 24 configs at 1M rounds each (Step 16) |
| Client seed influence | Pass | 99.87% of layouts change with an alternate client seed — confirmed across the full dataset |
RNG derived only from disclosed inputsPass
Entropy purityPass
Our own implementationPass
LIQD’s validation vectorsPass
Modulo biasPass
Key encoding verifiedPass
Serial independencePass
Client seed influencePass
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.
| Component | Detail |
|---|---|
| Hash function | HMAC-SHA256 (32-byte digest) |
| Key | Buffer.from(serverSeed, 'hex') — 16 raw bytes (distinct from the commitment domain, which hashes the seed text) |
| Message | clientSeed:nonce:cursor — UTF-8, literal colons, no padding |
| Extraction | 4-byte big-endian chunks → readUInt32BE → uint32, up to 8 per digest |
| Rejection | chunk < maxFair where maxFair = floor(2³² / range) × range — bias-free ceiling per range |
| Reduction | uint32 % range → offset in [0, range); swap position j = cursor + offset |
| Exhaustion path | All 8 chunks rejected — no further draw defined; our implementation stops with an error. Below 1e-66 per draw, never reached |
| Shuffle | Sequential Fisher-Yates: position 0 to 24, cursor = position |
| Output | First mineCount tiles in draw order → mine layout |
/** * §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}`, );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:
| Source | Controlled By | Purpose |
|---|---|---|
| Server Seed | Casino | Base randomness — the HMAC key, committed via SHA-256 hash of the seed text before betting |
| Client Seed | Player | Player-contributed entropy — mixed into every HMAC message |
| Nonce | System | Uniqueness per bet (0–49 within each epoch, audited gap-free) |
| Cursor | System | Per-draw isolation (0 to 24, one per Fisher-Yates position) — an index, not an entropy source |
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)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.
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).
| Metric | Value |
|---|---|
| Rounds | 24,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 RTP | 98.9427% vs the proven 99.0000% — 1.66 standard errors below |
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);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.
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;}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)| i | cursor | range | HMAC[:8] | uint32 | j | swap |
|---|---|---|---|---|---|---|
| 0 | 0 | 25 | 3dba941c | 1,035,637,788 | 13 | tile 14 → slot 0 |
| 1 | 1 | 24 | 4f491826 | 1,330,190,374 | 23 | tile 24 → slot 1 |
| 2 | 2 | 23 | 551c6b57 | 1,427,925,847 | 13 | tile 1 → slot 2 |
| 3 | 3 | 22 | dfeac082 | 3,756,703,874 | 3 | tile 4 → slot 3 |
| … | 4 … 23 | 21 … 2 | … | … | … | 20 more swaps |
| 24 | 24 | 1 | c4985873 | 3,298,318,451 | 24 | tile 22 → slot 24 |
cursor = i); the first three draws decide the mines — [14, 24, 1], in draw order, exactly as the live game recorded.Technical Evidence & Verification5 sections
2.8Evidence Coverage Summary8 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Our implementation (Step 5) | 6,900 / 6,900 rounds — written from the specification; its 98 specification checks pass | Pass |
| 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 analysis | maxFair 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 vector | Pass |
| External entropy non-participation (Step 5) | 6,900 / 6,900 reproduced from disclosed inputs alone | Pass |
| First-draw uniformity (Step 16) | 0/24 configs fail at Bonferroni α/24 | Pass |
| Pairwise co-occurrence (Step 16) | 0/23 configs fail (m ≥ 2) — 300 tile pairs vs the hypergeometric | Pass |
| 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 D | Pass |
2.9Code References9 source files
| File | Purpose |
|---|---|
| src/rng.ts | HMAC-SHA256 sequential Fisher-Yates (generateProvablyFairNumber, revealMines), written from docs/ALGORITHM.md |
| docs/ALGORITHM.md | The written specification of the RNG, commitment and payout |
| src/bias.ts | Draw-bias bound for every range, built on maxFairFor |
| src/simulate.ts · src/seed-derivation.ts | Monte Carlo simulation (24M rounds, two passes) and the Pass 2 seed derivation |
| src/replay.ts · src/deep-replay.ts | Full replay of the committed Pass 1 from its saved row seeds (inside every verify run, and stand-alone) |
| src/stats.ts | Chi-squared, max-|z| joint test, lag-1 autocorrelation, Wald-Wolfowitz runs test |
| tests/steps/determinism.ts | Steps 5–6: mine-layout recomputation, draw-bias census and client seed influence |
| tests/steps/simulation.ts | Steps 16–17: Pass 1 replay and scoring, Pass 2 re-derivation and bootstrap replay |
| tests/mines/rngTests.ts · rngSpecTests.ts · qaRegressionTests.ts | 49 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
| Invariant | Result |
|---|---|
| HMAC-SHA256 output matches live game for all 6,900 bets | Pass |
| Key is hex-decoded (16 bytes, not UTF-8) — wrong encoding produces wrong layouts | Pass |
| Message format is clientSeed:nonce:cursor — literal colons, no padding | Pass |
| Chunks scanned big-endian, 4 bytes, first accepted wins | Pass |
| Rejection sampling eliminates modulo bias for all ranges (1–25) — every residue below maxFair owns exactly floor(2³² / range) values | Pass |
| 0 of 172,500 captured draws reach the rejection branch (2.024e-4 expected); the branch is exercised on a fixed vector | Pass |
| No external entropy sources required for layout computation | Pass |
| 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 Bonferroni | Pass |
| 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
2.12Reproduction InstructionsRun locally
Clone the repository, install dependencies, and run the verification and the bootstrap replay:
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)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.
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
- 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
- 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
Mine-layout recomputationPass
ProvablyFair.org verifierPass
Payout correctnessPass
Displayed multiplierPass
Reveal-sequence consistencyPass
Bet-size independencePass
Config completenessPass
Multi-reveal coveragePass
Multi-reveal supplementPass
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.
| Check | Result | Evidence |
|---|---|---|
| Mine-layout recomputation | Pass | 6,900 / 6,900 exact match — mine layout verified in draw order for every bet (Step 5) |
| ProvablyFair.org verifier | Pass | The site verifier’s Mines module, pinned by hash, reproduces 6,900 / 6,900 layouts in draw order (Step 5) |
| Payout correctness | Pass | All 3,104 wins: credit = floor8(bet × exact multiplier) with zero tolerance; all 3,796 losses pay 0 (Step 8) |
| Displayed multiplier | Pass | The on-screen multiplier is the exact value floored to 2 decimals — cosmetic, and it can only understate what was paid |
| Reveal-sequence consistency | Pass | Wins never open a mine; losses bust on exactly the final opened tile |
| Bet-size independence | Pass | Bet amount is absent from the RNG input — same layout at $0.10 and $10. Tested in Phase C (200 / 200) |
| Config completeness | Pass | All 24 mine counts (1–24) covered in live data — 200 bets each in Phase A |
| Multi-reveal coverage | Pass | 400 / 400 multi-reveal rounds recompute — 107 distinct tile sets, all 25 first picks exercised |
| Multi-reveal supplement | Pass | 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) |
Mine-layout recomputationPass
ProvablyFair.org verifierPass
Payout correctnessPass
Displayed multiplierPass
Reveal-sequence consistencyPass
Bet-size independencePass
Config completenessPass
Multi-reveal coveragePass
Multi-reveal supplementPass
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.
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.
| Phase | Bets | Config | Bet Amount | Purpose |
|---|---|---|---|---|
| A — Configuration coverage | 4,800 | 24 configs (mineCount 1–24, 200 per config), 1 tile | $0.10 | Verify formula correctness across every mine count |
| B — High-mine edge case | 1,000 | 1 config (mineCount 24), 1 tile | $0.10 | Maximum variance; only 1 safe tile in 25 |
| C — Stake equivalence | 200 | 1 config (mineCount 3), 1 tile | $10.00 | Confirm bet amount is not an RNG input (100× the Phase A stake) |
| D — Client-seed variation | 500 | All 24 mineCounts cycled (20–21 each), 10 custom seeds | $0.10 | 10 custom pfaudit-prefixed client seeds; confirms seed control is player-side |
| E — Multi-reveal | 400 | 1 config (mineCount 3), 5 tiles planned | $0.10 | Multi-reveal payout path — five planned reveals (the supplement goes to 15) |
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.
// ── 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',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.
// ── 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++; } }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.
| Metric | Count |
|---|---|
Wins where multiplier == floor2(exact) at 1e-9 | 3,104 / 3,104 |
| Wins where the display understates the credited amount | 2,662 |
| Wins where the display overstates the credited amount | 0 |
Losses carrying a multiplier key | 0 / 3,796 (absent, and winningAmount == 0) |
// ── 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++; } }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.
| Scope | Coverage | Verified |
|---|---|---|
| Wins — no opened tile is a mine | 3,104 rounds | 3,104 / 3,104 |
| Losses — bust on exactly the final opened tile | 3,796 rounds | 3,796 / 3,796 |
| Earlier-tile mine violations (either direction) | All 6,900 rounds | 0 |
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.
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.
| Check | Detail |
|---|---|
| Integrity | Dataset 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 |
| Commitments | 20 / 20 commitments verify, 19 / 19 chain links, pre-capture anchor links into epoch 0; 0 nonce violations |
| Layouts and reveals | 1,000 / 1,000 layouts recompute in draw order; 3,314 / 3,314 served mine/safe statuses equal the recomputed layout |
| Cash-outs | 393 wins, 607 losses; 393 / 393 credits equal floor8(stake × exact) — 0 above, 0 below; 393 / 393 displays equal floor2(exact) |
| Order independence | 20 / 20 settings with more than one reveal order carry one multiplier and one credit; first picks span 25 / 25 positions |
| Coverage | 20 cash-out settings witnessed — together with the audited dataset, 45 of the 300 settings carry a captured winning payout |
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…| Step | Process | Output |
|---|---|---|
| 1 | Recompute the commitment — SHA-256(utf8(serverSeed)) | Matches the pre-published hash |
| 2 | Run revealMines(serverSeed, clientSeed, 0, 3) | [14, 24, 1] — equals the recorded mineTiles, in order |
| 3 | Check the five opened tiles against the mine set | No overlap — the won label is correct |
| 4 | Compute minesMultiplierExact(3, 5) | 1.99736842… exact (panel displays the floored 1.99×) |
| 5 | Multiply by the stake | 0.10 × 1.99736842 = 0.19973684 — the credited winningAmount, verbatim |
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×)Technical Evidence & Verification5 sections
3.10Evidence Coverage Summary12 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Mine-layout recomputation (Step 5) | 6,900 / 6,900 rounds — full layout verified, in draw order; ProvablyFair.org verifier 6,900 / 6,900 | Pass |
| Payout math — wins (Step 8) | 3,104 / 3,104 winning bets credit floor8(stake × exact multiplier), zero tolerance | Pass |
| Payout math — losses (Step 8) | 3,796 / 3,796 losing bets pay exactly zero | Pass |
| Displayed multiplier convention (Step 7) | 3,104 wins at floor2(exact), 1e-9 — never above the credit | Pass |
| Win/loss labels vs recomputed boards (Steps 8, 20) | 0 violations in 6,900 rounds | Pass |
| Config completeness (Step 9) | All 24 mine counts covered — 200 bets each in Phase A | Pass |
| Phase D client-seed variation (Step 14) | 500 bets, 10 custom client seeds — 500 / 500 match | Pass |
| Phase E multi-reveal (Step 15) | 400 / 400 layouts · 216 / 216 winning multipliers | Pass |
| Phase C bet-size invariance (Step 18) | 200 / 200 at $10.00 | Pass |
| Reveal-position independence (Step 21) | 107 distinct open-sets · all 25 first picks | Pass |
| Multi-reveal supplement (Step 22) | 1,000 / 1,000 layouts · 3,314 / 3,314 reveals · 393 / 393 credits and displays at the rule | Pass |
| Multi-phase coverage | 5 phases: A (4,800) + B (1,000) + C (200) + D (500) + E (400) | Pass |
3.11Code References7 source files
| File | Purpose |
|---|---|
| tests/steps/determinism.ts | Step 5: Mine-layout recomputation (ordered comparison) |
| tests/steps/payouts.ts | Steps 7–9: Display field, payout + win-condition replay, config completeness |
| tests/steps/phase-d.ts | Step 14: Phase D custom client-seed variation |
| tests/steps/phase-e.ts | Step 15: Phase E multi-reveal verification |
| tests/steps/standardization.ts | Steps 18, 20, 21: Bet-size invariance, reveal-sequence consistency, reveal-position independence |
| src/config.ts | minesMultiplierExact, minesMultiplier — the closed-form formulas every payout is replayed against |
| tests/steps/supplement.ts | Step 22: the multi-reveal supplement — integrity, commitments, layouts, reveals and cash-outs |
3.12Datasets UsedSource artifacts
3.13Verified Invariants14 invariants
| Invariant | Result |
|---|---|
| Computed mine layout matches live mineTiles, in draw order, for all 6,900 bets | Pass |
| winningAmount = floor8(betAmount × minesMultiplierExact(m, openTiles.length)) in exact rational arithmetic, for all 3,104 wins | Pass |
| winningAmount = 0 for all 3,796 losses | Pass |
| Displayed multiplier = floor2(exact) on all 3,104 wins; absent on losses; never above the credit | Pass |
| Win condition holds: won ⇔ no opened tile is a mine, for all 6,900 bets | Pass |
| Loss condition holds: lost ⇔ the final opened tile is the only opened mine | Pass |
| Planned reveals equals openTiles.length on every win (3,104 / 3,104) | Pass |
| Payout keys off executed depth (openTiles.length), never the plan | Pass |
| 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 each | Pass |
| Phase D: 500/500 layouts verified across 10 custom pfaudit client seeds | Pass |
| Phase E spans 107 distinct opened-tile sets and all 25 first picks | Pass |
| 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:
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 PASSS3-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)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.
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
- 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)
- 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
Anti-circularityPass
House edge auditPass
RTP claim (RTP-CLAIM 1.2.0)Pass
Simulated RTP (Pass 1)Pass
Cherry-pick test (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
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.
| Check | Result | Evidence |
|---|---|---|
| Anti-circularity | Pass | 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 audit | Pass | 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 invariance | Pass | 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 formula | Pass | 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) |
Anti-circularityPass
House edge auditPass
RTP claim (RTP-CLAIM 1.2.0)Pass
Simulated RTP (Pass 1)Pass
Cherry-pick test (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
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:
| Component | Formula | Source |
|---|---|---|
| Win probability | C(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 anchor | Exhaustive 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 |
| RTP | winProbability × minesMultiplierExact | The product — the fair terms cancel |
| Mines (m) | winChance (k=1) | Multiplier (with edge) | winChance × Multiplier | RTP |
|---|---|---|---|---|
| 1 | 24/25 = 0.960000 | 1.03125000 | 0.990000 | 99.0000% |
| 3 | 22/25 = 0.880000 | 1.12500000 | 0.990000 | 99.0000% |
| 5 | 20/25 = 0.800000 | 1.23750000 | 0.990000 | 99.0000% |
| 12 | 13/25 = 0.520000 | 1.90384615 | 0.990000 | 99.0000% |
| 20 | 5/25 = 0.200000 | 4.95000000 | 0.990000 | 99.0000% |
| 24 | 1/25 = 0.040000 | 24.75000000 | 0.990000 | 99.0000% |
Anti-Circularity Verification:
/** * 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.
| Item | Detail |
|---|---|
| Claim | 99% RTP at every setting — a flat 1.00% house edge |
| Where LIQD publishes it | The 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 tested | 300 — every mine count 1–24, each with cash-out after every legal depth k = 1 … 25 − m |
| Method | Exact 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 · maximum | 99.000000% · 99.000000% |
| Worst gap | 0.000000 pp |
| Settings beyond the band | 0 on the house side · 0 on the player side |
| Scored on | Step 10 (profile check MINES-19), rule enforced |
| Result | Pass — the claim holds at every setting; no fix needed |
| Mine count | Depths | Settings | Exact RTP | RTP | Gap | Result |
|---|---|---|---|---|---|---|
| 1 mine | k = 1 … 24 | 24 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 2 mines | k = 1 … 23 | 23 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 3 mines | k = 1 … 22 | 22 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 4 mines | k = 1 … 21 | 21 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 5 mines | k = 1 … 20 | 20 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 6 mines | k = 1 … 19 | 19 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 7 mines | k = 1 … 18 | 18 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 8 mines | k = 1 … 17 | 17 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 9 mines | k = 1 … 16 | 16 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 10 mines | k = 1 … 15 | 15 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 11 mines | k = 1 … 14 | 14 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 12 mines | k = 1 … 13 | 13 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 13 mines | k = 1 … 12 | 12 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 14 mines | k = 1 … 11 | 11 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 15 mines | k = 1 … 10 | 10 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 16 mines | k = 1 … 9 | 9 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 17 mines | k = 1 … 8 | 8 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 18 mines | k = 1 … 7 | 7 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 19 mines | k = 1 … 6 | 6 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 20 mines | k = 1 … 5 | 5 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 21 mines | k = 1 … 4 | 4 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 22 mines | k = 1 … 3 | 3 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 23 mines | k = 1 … 2 | 2 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 24 mines | k = 1 | 1 | 99/100 | 99.000000% | 0.000000 pp | Pass |
| All 300 settings | every legal depth | 300 | 99/100 | 99.000000% | 0.000000 pp | Pass — 300 / 300 |
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.
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:
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.
| Mine count | Theoretical RTP | Simulated RTP (1M) | Deviation | z |
|---|---|---|---|---|
| 1 | 99.0000% | 98.9871% | −0.0129 pp | −0.64 |
| 2 | 99.0000% | 99.0612% | +0.0612 pp | +2.10 |
| 3 | 99.0000% | 98.9917% | −0.0083 pp | −0.23 |
| 4 | 99.0000% | 98.9437% | −0.0563 pp | −1.30 |
| 5 | 99.0000% | 99.0724% | +0.0724 pp | +1.46 |
| 6 | 99.0000% | 98.9767% | −0.0233 pp | −0.42 |
| 7 | 99.0000% | 99.1128% | +0.1128 pp | +1.83 |
| 8 | 99.0000% | 98.9538% | −0.0462 pp | −0.68 |
| 9 | 99.0000% | 98.9759% | −0.0241 pp | −0.33 |
| 10 | 99.0000% | 99.0688% | +0.0688 pp | +0.85 |
| 11 | 99.0000% | 98.8998% | −0.1002 pp | −1.14 |
| 12 | 99.0000% | 99.0196% | +0.0196 pp | +0.21 |
| 13 | 99.0000% | 99.0347% | +0.0347 pp | +0.34 |
| 14 | 99.0000% | 98.9003% | −0.0997 pp | −0.89 |
| 15 | 99.0000% | 99.1285% | +0.1285 pp | +1.06 |
| 16 | 99.0000% | 99.0242% | +0.0242 pp | +0.18 |
| 17 | 99.0000% | 98.8865% | −0.1135 pp | −0.79 |
| 18 | 99.0000% | 98.9042% | −0.0958 pp | −0.60 |
| 19 | 99.0000% | 99.1737% | +0.1737 pp | +0.99 |
| 20 | 99.0000% | 98.9535% | −0.0465 pp | −0.24 |
| 21 | 99.0000% | 99.0210% | +0.0210 pp | +0.09 |
| 22 | 99.0000% | 98.8507% | −0.1493 pp | −0.56 |
| 23 | 99.0000% | 98.1993% | −0.8007 pp | −2.38 |
| 24 | 99.0000% | 98.4852% | −0.5148 pp | −1.06 |
| All 24 (mean) | 99.0000% | 98.9427% | −0.0573 pp | −1.66 |
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++; // … }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.
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.
| Test | Detail |
|---|---|
| Seeds tested | 138 — every revealed server seed in the capture, each replayed for 10,000 nonces |
| Served-config test | 0 / 138 epochs flagged at p ≤ 0.01 (family gate ≤ 6 at family α 0.001) |
| Pooled early-window return | 6,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 diagnostic | 4 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 replay | 240,000 / 240,000 null statistics and 138 / 138 p-values reproduced, late-p max |Δ| 9.0e-13 (npm run bootstrap-replay) |
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);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.
| Metric | Phase A ($0.10) | Phase C ($10.00) |
|---|---|---|
| Rounds at mineCount 3, 1 tile | 200 | 200 |
| Layout recomputation parity | 200 / 200 | 200 / 200 |
| Exact multiplier on a win | 1.12500000 | 1.12500000 |
| Credited per win | $0.11250000 | $11.25000000 |
Wager argument in revealMines | none — the function has no stake parameter | none — same function, same code path |
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.
| Item | Value | Context |
|---|---|---|
| Live capture RTP | 99.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 rate | 3,104 / 6,900 = 44.99% | Mixed mine counts and depths — informational |
| Live-sample fit (Step 9) | p = 0.8889 · p = 0.9709 | First-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 autocorrelation | r₁ = 0.3548 | The 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 settings | 10,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.1125Step 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.
| Step | Process | Output |
|---|---|---|
| 1 | Recompute the layout from seeds | [6, 20, 7] — ordered match |
| 2 | Check the opened tile against the mine set | Safe — won confirmed |
| 3 | Exact multiplier 0.99 × 25/22 | 1.125 (display: 1.12×) |
| 4 | Stake × exact multiplier | $0.1125 credited |
| 5 | RTP identity at this config | 0.88 × 1.125 = 0.9900 — the flat edge, exactly |
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)1.12× display beside a full-precision 1.125 credit.Technical Evidence & Verification5 sections
4.9Evidence Coverage Summary8 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Anti-circularity (Step 13) | 300 (m, k) pairs from combinatorics alone — cross-check deviation 2.2e-16; counting anchor 110 cases at integer equality | Pass |
| House edge audit (Step 10) | 28 / 28 scored cases and 300 / 300 settings = 99.0000%; 552 / 552 adjacent pairs strictly increasing | Pass |
| RTP claim rule (Step 10) | LIQD’s published 99% against all 300 settings — worst gap 0.000000 pp, 0 beyond the band | Pass |
| Payout reconciliation (Step 8) | 3,104 wins at floor8(stake × exact), zero tolerance + 3,796 losses at 0 | Pass |
| 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 expected | Pass |
| Bet-size invariance (Step 18) | 200 / 200 Phase C at $10.00 | Pass |
| Multi-reveal payouts (Steps 15, 22) | 216 / 216 Phase E winning multipliers; 393 / 393 supplement cash-outs at the rule | Pass |
4.10Code References11 source files
| File | Purpose |
|---|---|
| tests/steps/anti-circularity.ts | Step 13: anti-circularity proof (combinatorial identity, cross-checks and counting anchor) |
| tests/steps/payouts.ts | Steps 7–10: display field, payout replay, config completeness, house-edge audit and the RTP claim rule |
| src/rtp-claim.ts · src/rtp-claim-switch.ts | The RTP claim rule (RTP-CLAIM 1.2.0) and its enforcement switch (on) |
| src/rtp-settings.ts | The 300-setting exact RTP table and LIQD’s published claim |
| tests/steps/simulation.ts | Steps 16–17: Pass 1 replay and scoring, the served-config test and the Pass 2 bootstrap replay |
| tests/steps/standardization.ts | Step 18: bet-size invariance across Phases A and C |
| src/simulate.ts | Monte Carlo simulation (24M rounds, two passes — Pass 1 FWER families, Pass 2 bootstrap) |
| src/stats.ts | chiSquaredTest, max-|z| joint test, lag1Autocorrelation, runsTest |
| src/config.ts | winProbability, pairCoOccurProb, minesMultiplierExact, minesMultiplier, theoreticalRTP — closed-form formulas |
| src/false-alarm.ts | Nominal 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.ts | Derived-figure artifact (outputs/audit-figures.json) — the producing artifact for every quoted figure, re-derived by Step 19 |
4.11Datasets UsedSource artifacts
4.12Verified Invariants15 invariants
| Invariant | Result |
|---|---|
| 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 count | Pass |
| Counting anchor: an exhaustive k-subset tally equals C(25, k) and C(25−m, k) as integers on all 110 enumerated (m, k) cases | Pass |
| 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 exceptions | Pass |
| Simulated RTP 98.9427% across 24M rounds — 1.66 standard errors below 99.0000% | Pass |
| 0 / 24 configs reject first-draw uniformity at Bonferroni α/24 | Pass |
| 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 seed | Pass |
| Phase C ($10.00) layouts match the $0.10 derivation; bet amount absent from the RNG input | Pass |
4.13Reproduction InstructionsRun locally
Clone the release, install, and run the verification and the two replays:
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.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.
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
- 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?
- 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
Nonce integrityPass
Seed integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
Payout precisionRemediated
Parameter limits & recoveryRemediated
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.
Category Coverage
| Check | Result | Evidence |
|---|---|---|
| Nonce integrity | Pass | 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 integrity | Pass | 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 determinism | Pass | Mine positions from the revealed seed match the tiles played, in order, through LIQD’s verifier (EXTRA-002, PARAM-003) |
| Round & player isolation | Pass | 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 integrity | Pass | Injected payout and outcome fields ignored; a cash-out racing a reveal never collected while revealing; 200 consecutive games settled with no mismatch |
| Payout precision | Remediated | 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 & recovery | Remediated | 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) |
Nonce integrityPass
Seed integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
Payout precisionRemediated
Parameter limits & recoveryRemediated
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.
| Category | Tests | What It Catches |
|---|---|---|
| Race Conditions | 4 | Parallel bets, reveals and cash-outs racing each other or a seed rotation |
| Input Validation | 4 | Malformed, wrong-type or out-of-range mine counts, stakes and currencies |
| Atomic Balance / Idempotency | 3 | Double debits, orphaned debits and unsafe retry behaviour after a timeout or disconnect |
| Broken Authorization | 2 | Acting on another account, or after the session has ended |
| Max-win Bypass | 3 | Client-supplied outcome, payout or mine-count fields, and stake limits |
| Nonce Manipulation | 4 | Reused, skipped, zero, negative or oversized nonces supplied by the client |
| RNG / PF Verification | 3 | Provably fair fields, the nonce sequence, and mine positions against LIQD’s verifier |
| Entropy / PF Verification | 1 | Server-seed rotation behaviour: fresh seeds, a new commitment and the nonce reset |
| Precision / Payout | 2 | Settlement arithmetic at scale, and every credit against the exact closed-form multiplier |
| Event Replay | 2 | Duplicate bets, and tile reveals sent with no game in play |
| Adversarial Seeds | 3 | Unusual 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.
| Status | Meaning | Action |
|---|---|---|
| Pass | The service handled the scenario correctly: the control held | None — recorded with its result |
| Remediated | A failed control that LIQD has since fixed, re-tested by ProvablyFair.org on the current build | Recorded with the fix and its re-test |
| Warning | A real defect, or a property not yet proven, without a direct effect on funds or fairness | Recorded with its impact; open until a re-test resolves it |
| Fail | A defect affecting funds, fairness or security | Remediation and a re-test required |
| Condition | Consequence |
|---|---|
| A substituted server seed is accepted against a published commitment | Hard fail |
| A revealed seed fails its commitment and play continues | Hard fail |
| Any settled round's layout cannot be reproduced from disclosed inputs | Hard fail |
| A credited payout deviates from the closed-form multiplier | Hard fail |
| Nonce reuse or gaps that could conceal discarded outcomes | Hard fail |
31 tests · 27 pass · 4 remediated
Race Conditions4/4
LIQD-MINES-EXTRA-001PassSeed 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-001PassParallel 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-002PassParallel 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-003PassCash-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-001LowRemediatedMine 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-002LowRemediatedStake 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-003PassMaximum 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-001PassCurrency 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-003LowRemediatedServer 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-001PassRetry 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-002PassOrphaned-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-001PassAnother 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-003PassSession 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-001PassExtra 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-001PassStake 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-002PassOutcome 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-001PassReused 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-002PassSkipped-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-003PassZero 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-004PassOverflow-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-002PassMine 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-001PassProvably-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-002PassNonce 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-004PassNonce 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-002MediumRemediatedPayout 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-001PassSettlement 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-001PassDuplicate 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-002PassTile 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-001PassClient-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-002PassUnicode 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-003PassRapid 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.
| Category | Tests | Result |
|---|---|---|
| Race Conditions | 4 | All pass |
| Input Validation | 4 | 2 pass · 2 remediated (PARAM-001, PARAM-002) |
| Atomic Balance / Idempotency | 3 | 2 pass · 1 remediated (IDEMP-003) |
| Broken Authorization | 2 | All pass |
| Max-win Bypass | 3 | All pass |
| Nonce Manipulation | 4 | All pass |
| RNG / PF Verification | 3 | All pass |
| Entropy / PF Verification | 1 | All pass |
| Precision / Payout | 2 | 1 pass · 1 remediated (PREC-002) |
| Event Replay | 2 | All pass |
| Adversarial Seeds | 3 | All pass |
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.
| Tests | Finding | Status |
|---|---|---|
| PREC-002 | The 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-002 | Wrong-type mine-count and stake values were converted instead of refused, inside the valid ranges. Fixed by LIQD and re-verified. | Remediated · Low |
| IDEMP-003 | Some 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
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.
Integrity tests ....................... 31Categories ............................ 11Pass .................................. 27Remediated ............................ 4Warning (open) ........................ 0Findings .............................. 3 · 3 remediated, 0 openProcedures and payloads ............... withheld (5.5)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.
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)
- 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
- 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)
Player can access all required inputsPass
Client seed origin transparentPass
Verification process documentedPass
MANIFEST.md, Verify a bet) and a demonstration round in its evidence (E21, E22)Casino UI exposes verification panelPass
Independent verifier availablePass
Manual recomputation possiblePass
No hidden or proprietary stepsPass
Public algorithm documentationInfo
docs/ALGORITHM.md), and publishing LIQD’s own — with the tile numbering and the multiplier formula — is our first recommendation to the operatorOnly disclosed inputs are used. Identical inputs always produce identical output.
- 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
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
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
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.
Verify a real bet, step by stepVerify a real bet · Swipe for all 4 steps →
Verify with the audit code
Independent verification using the implementation behind this report.
Open independent verifierBuilt-in verification
The operator’s own verification page. This tool is controlled by LIQD.com.
Open LIQD verifierVisual Walkthroughstep-by-step
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.
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 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.
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-256of 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…25are swapped one position at a time (cursor 0…24); when the shuffle finishes, the firstmineCounttiles are the mines, in the order they were drawn. - Board — tile
tsits at row ⌈t / 5⌉, column ((t − 1) mod 5) + 1: numbered across the rows from the top-left. - Pay — cashing out after
ksafe tiles creditsstake × 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:
| LIQD Built-in (in-game panel) | ProvablyFair.org | |
|---|---|---|
| Who controls the code | The casino | ProvablyFair.org — open source |
| What it proves alone | Internal consistency of LIQD’s own math | Independent recomputation from raw inputs |
| Checked against this capture | Indirectly — 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 disagree | Suspect the casino’s tool or a tampered round | The 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 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.199736846.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 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.199736846.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.
| File | Shows |
|---|---|
| E10-mines-game-ui.png | The Mines interface — 5×5 grid, mine-count selector (shown at 3), Manual / Auto and bet controls |
| E11-provably-fair-verifier.png | The Verify tab — client seed / server seed / nonce / mines inputs and the VERIFY action |
| E12-client-seed-rotation-response.png | The 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.png | The 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.png | Settle 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.png | A 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.png | The 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.json | The 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) |
Verify a real bet, step by stepVerify a real bet · Swipe for all 4 steps →
How it works
- 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
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
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
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
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.
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 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.
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-256of 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…25are swapped one position at a time (cursor 0…24); when the shuffle finishes, the firstmineCounttiles are the mines, in the order they were drawn. - Board — tile
tsits at row ⌈t / 5⌉, column ((t − 1) mod 5) + 1: numbered across the rows from the top-left. - Pay — cashing out after
ksafe tiles creditsstake × 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:
| LIQD Built-in (in-game panel) | ProvablyFair.org | |
|---|---|---|
| Who controls the code | The casino | ProvablyFair.org — open source |
| What it proves alone | Internal consistency of LIQD’s own math | Independent recomputation from raw inputs |
| Checked against this capture | Indirectly — 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 disagree | Suspect the casino’s tool or a tampered round | The 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 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.199736846.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 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.199736846.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.
| File | Shows |
|---|---|
| E10-mines-game-ui.png | The Mines interface — 5×5 grid, mine-count selector (shown at 3), Manual / Auto and bet controls |
| E11-provably-fair-verifier.png | The Verify tab — client seed / server seed / nonce / mines inputs and the VERIFY action |
| E12-client-seed-rotation-response.png | The 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.png | The 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.png | Settle 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.png | A 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.png | The 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.json | The 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) |
Only disclosed inputs are used. Identical inputs always produce identical output.
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.
Repository Details
- GitHub: ProvablyFair-org/liqd-mines
- Release: v1.0.1 · Framework 1.1
- Game: Mines (liqd-mines)
- Public Verifier: provablyfair.org/verify-bets/liqd
Prerequisites
- Node.js 22.x (
package.jsonengines and.nvmrc) —npm testneeds 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-replayand the optional standalone checker in S6
- GitHub: ProvablyFair-org/liqd-mines
- Release: v1.0.1 · Framework 1.1
- Game: Mines (liqd-mines)
- Public Verifier: provablyfair.org/verify-bets/liqd
- Node.js 22.x (
package.jsonengines and.nvmrc) —npm testneeds 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-replayand the optional standalone checker in S6
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.
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.
Every run writes only to outputs/run/ and compares its results field by field with the committed results (outputs/run/diff.json) — that comparison is the reproduction gate. Exit codes: 0 Full Pass · 2 an operator-origin flag (Conditional Pass) · 5 RTP FIGURE OVERSTATED (the RTP claim rule finds a published figure overstated) · 1 any hard fail, or a reproduction beyond tolerance. Output of node verify.js as run on Node 22.22.0; npm run verify runs the same steps from source. The informational context the suite prints before the summary and the RTP claim’s source note are left out here (…).
Every verify run already replays all 24,000,000 Pass 1 rounds from their saved seed pairs (Step 16) and rebuilds the Pass 2 bootstrap null from its recorded master seed (Step 17). npm run deep-replay runs the Pass 1 replay on its own; npm run bootstrap-replay rebuilds the Pass 2 null in an independent Python implementation. npm run simulate starts a new experiment instead: it records a fresh master seed and writes into outputs/run/ (SIM_MASTER_SEED repeats a run; SIM_REPLAY=1 regenerates the committed experiment). The eight scored screens have a total nominal level of 6.97%, so a single uncorrected trip on a new experiment is within that expected false-alarm rate; the verdict is scored against the committed artifacts. Output of the two replays as run on Node 22.22.0 with two CPU cores (repeated lines left out, …); the bootstrap replay takes about 20 seconds.
Audit Reproducibility Pinning
- Release Tag
- v1.0.1 — github.com/ProvablyFair-org/liqd-mines/tree/v1.0.1
- 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 inRELEASE-MANIFEST.json - Node Version
- 22.x (
package.jsonengines 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
| File | Contents | Size |
|---|---|---|
| verification-results.json | Steps 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.json | The RTP claim rule’s table — LIQD’s published 99% against all 300 settings, each with exact RTP, gap and outcome | ~41 KB |
| simulation-results.json | Pass 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.json | Replay inputs for both passes — master seed, derivation version, parameters, artifact and null hashes, replay commands | ~2 KB |
| audit-figures.json | Derived 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.float64le | The retained sorted bootstrap null — 10,000 float64 statistics per mine count, mine counts 1–24 | 1.92 MB |
| bootstrap-replay-report.json | Independent Python replay of Pass 2 — 240,000 statistics and 138 p-values, 0 mismatches | ~20 KB |
| deep-replay-report.json | Full Pass 1 replay — 24 rows, 24,000,000 rounds, 0 differences | ~6 KB |
| rtp-convergence.html | Convergence 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
| Step | Test Name | Report Section |
|---|---|---|
| 1 | Seed Hash Integrity | S1 |
| 2 | Next-Seed Pre-Commitment Chain | S1 |
| 3 | Hash Consistency Within Epoch | S1 |
| 4 | Nonce Audit | S1 |
| 5 | Mine-Layout Recomputation | S1, S2, S3 |
| 6 | Client Seed Influence | S1, S2 |
| 7 | Displayed Multiplier Field (floored, cosmetic) | S3, S4 |
| 8 | Payout Math + Win-Condition (wins: betAmount × EXACT multiplier == winningAmount; losses: 0; label ⇔ mine opened) | S3, S4 |
| 9 | Config Completeness (Phase A: 24 mineCounts × 200) | S3, S4 |
| 10 | House Edge / RTP Audit (closed-form identity, per-config, anti-circular) | S4 (also scores the RTP claim rule) |
| 11 | Phase Labels | S3 |
| 12 | Epoch Size | S1, S3 |
| 13 | Anti-Circularity (Combinatorial Win Probability) | S4 |
| 14 | Phase D — Client Seed Variation | S1, S2, S3 |
| 15 | Phase E — Multi-Reveal Verification | S3, S4 |
| 16 | Simulation — Pass 1 (First-Draw · Joint · Serial, FWER) | S2, S4 |
| 17 | Simulation — Pass 2 Cherry-Pick Test | S4 |
| 18 | Bet-Size Invariance | S3, S4 |
| 19 | Artifact Integrity (dataset pin + derived figures) | S7 |
| 20 | Reveal-Sequence Consistency | S3 |
| 21 | Reveal-Position Independence | S3 |
| 22 | Multi-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.
| Check | What it requires | Covered by | Coverage |
|---|---|---|---|
| MINES-01 | Every revealed seed hashes to its commitment | Step 1 | Full |
| MINES-02 | Next-seed pre-commitment chain, including the pre-capture link | Step 2 | Full |
| MINES-03 | One commitment and one client seed per epoch | Steps 3, 4 | Full |
| MINES-04 | Nonce contiguity, no reuse, no withholding; capture-retry gaps flagged, operator gaps failed | Step 4 | Full |
| MINES-05 | Audited population bound to code constants, not the dataset header | Steps 11, 12 | Full |
| MINES-06 | Client-seed control and sensitivity | Steps 6, 14 | Full |
| MINES-07 | Every served mine layout recomputes, in draw order | Step 5 | Full |
| MINES-08 | Draw-bias bound computed from the draw’s own rejection ceiling | Step 5 | Full |
| MINES-09 | Reference RNG provenance and known-input vectors | Package check: docs/ALGORITHM.md, src/rng.ts, 49 unit tests | LIQD’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-10 | Win/loss label against the recomputed board; reveal-sequence rules | Steps 8, 20 | Full |
| MINES-11 | Settlement — winning credits at floor8(stake × exact), losses at 0 | Steps 7, 8 | Full |
| MINES-12 | Bet-size invariance | Step 18 | Full |
| MINES-13 | Multiplier formula at every setting; display rounding with the exact value credited | Steps 7, 10, 13, 15 | Full |
| MINES-14 | Multiplier monotonicity; the payout limit is reported in S4.7 | Step 10 | Full |
| MINES-15 | Coverage — all 24 mine counts, several depths, all 25 first picks, the full reference grid | Steps 9, 19, 21, 22 | Full |
| MINES-16 | Wallet 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-17 | Multi-reveal — layouts, per-reveal status, cash-out amount, order independence | Steps 15, 20, 21, 22 | Layouts, 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-18 | Anti-circularity — the identity at every setting, counting anchor and negative control | Steps 10, 13 | Full |
| MINES-19 | Effective edge against the claimed edge; the RTP claim rule | Step 10 | Full |
| MINES-20 | Simulation Pass 1 — replayed, not trusted | Step 16 | Full |
| MINES-21 | Cherry-pick test — served-config return | Step 17 | Full |
| MINES-22 | Live-sample fit (informational) | Step 9 | Full |
| MINES-23 | Framework fairness-integrity rows S5-1 … S5-8, kept as a package checklist (Section 5 reports the live integrity tests) | Package check: MANIFEST.md matrix | Full |
| MINES-24 | Player-verifier parity — the ProvablyFair.org verifier | Step 5 | Full |
| MINES-25 | Player verification path, in LIQD’s field names | Package check: MANIFEST.md Verify a bet + E12, E13, E21, E22 | Full |
| MINES-26 | Dataset pinned by SHA-256 before any step runs | Step 19 | Full |
| MINES-27 | Artifacts of record pinned and re-hashed; every numeric value inside a declared domain | Step 19 | Full |
| MINES-28 | Release gate — fresh clone, pinned runtime, zero-install npm test | Package check: scripts/run-stages.mjs, package.json, .nvmrc | Full |
| MINES-29 | Mutation and forgery batteries with declared survivors | Package check: tests/mutations.json, tests/survivors.json, tests/forgeries.ts | Full |
| MINES-30 | Limitations register and numbered evidence | Package check: MANIFEST.md | Full |
reproduce-audit.sh
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.