
Mines
Independent verification, backed by open-source code.
audit and dataset.
- Live Bets Verified
- 7,050
- Parity Rate
- 100%
- Simulated Rounds
- 24M
- Proven RTP
- 99.9%
Verification pipeline
Real bets. Independently reproduced.
Placed anonymously at Duel.com. We rebuilt the game logic from scratch and matched all 7,050 recorded outcomes.
The casino result, reproduced.
- Casino outcome
- 5 safe reveals · Cashed out
- Our reproduction
- 5 safe reveals · Cashed out
- Recorded payout
- $0.0202
- Seed Hash IntegrityPass
- Commitment LinkagePass
- Hash ConsistencyPass
- Nonce AuditPass
- Mine Position RecomputationPass
- Client Seed InfluencePass
- Payout MathPass
- Multiplier FormulaPass
- Win ConditionPass
- Bet-Size InvariancePass
- Config CompletenessPass
- House Edge AuditPass
- Phase LabelsPass
- Dataset HashPass
- Epoch SizePass
- Anti-CircularityPass
- Client Seed VariationPass
- Simulation Pass 1Pass
- Cherry-Pick DetectionPass
- Multi-Reveal ChainPass
- Mine-Set InvariancePass
- Reveal-Position Indep.Pass
- Cash-Out PayoutPass
The repo is the credential. You don't have to trust us — every finding ships as code. Run npm test to re-run the full audit: 23 verification steps, 24M simulated rounds, 7,050 live bets re-verified.
This audit independently validates the Mines game operated by Duel.com across five domains: deterministic outcome generation, entropy integrity, live-to-verifier parity, RTP mathematical accuracy, and fairness integrity testing. We placed 7,050 real bets across 151 seed entries and independently verified every single outcome using our own implementation of the algorithm.
Audit Verdict
| Check | Result | Evidence |
|---|---|---|
| Overall Status | Pass | |
| RTP Verified | Pass | 99.9% proven · 99.904% simulated (24M) · flat 0.1% house edge across all 24 mine counts |
| Live ↔︎ Verifier Parity | Pass | 100% — 7,050 / 7,050 bets matched |
| Commit-Reveal System | Pass | SHA-256 verified, 151 seed entries — commitment intact across all rotations |
| Client Seed | Pass | Browser-generated + player customizable — server commits before client seed is known; 646/650 (99.4%) influence-test positives |
| RNG Analysis | Pass | HMAC-SHA256 backward Fisher-Yates on a 25-tile grid — bias-free rejection sampling, no hidden inputs |
| Payout Logic | Pass | All 7,050 payouts verified — single-reveal and multi-reveal chains, exact to 8 decimal places |
| Anti-Circularity | Pass | Algebraic identity: winChance × payoutMult = 0.999 for all 300 (mine count, reveals) combinations — proven from first principles |
| Fairness Integrity | Pass | 16 fairness integrity tests — all 16 pass (includes one Mines-specific multi-step state test) |
| Determinism | Pass | Full reproducibility confirmed |
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 algorithm is deterministic and verifiable
- Server seeds are cryptographically committed via SHA-256 before play
- Client seed is browser-generated and players can customize it
- Nonces increment correctly and are never reused within a seed pair
- Mine positions are computed via HMAC-SHA256 backward Fisher-Yates with bias-free rejection sampling
- Outcomes are reproducible from server seed, client seed, and nonce
- Single-reveal and multi-reveal payouts match the combinatorial formula C(25,k) / C(25−m,k) × 0.999 for all 300 (mine count, reveals) combinations
- The mine set is fixed at round start and unchanged by reveal sequence or cash-out timing
- Theoretical RTP is 99.9% across all 24 configurations (flat 0.1% house edge)
- Bet amount does not influence mine placement or outcome
- Players can independently verify every bet
What Audit Excludes
- Infrastructure or server security
- Wallet, payments, or operational systems outside game logic
- Rakeback layer — 99.9% is the certified RTP; rakeback is operator-side
- Cross-account sampling
- Max win cap enforcement — not embedded in game logic
What Audit Covers
| Area | Description |
|---|---|
| Commit-Reveal System | SHA-256 server seed hashing, pre-bet commitment, reveal on rotation |
| Client Seed Origin | Player-controlled seed, browser-generated — server commits before your seed is known |
| Seed Handling | Client seed control, nonce lifecycle, seed pair rotation |
| RNG Analysis | HMAC-SHA256 backward Fisher-Yates verification, rejection sampling, bias analysis |
| Payout Logic | Combinatorial multiplier formula, multi-reveal chains, cash-out correctness, bet-size invariance (Phase C) |
| Live Parity | Independent mine-position recomputation vs live game results across all capture phases |
| RTP Validation | Anti-circularity proof, simulated RTP (24M rounds), cherry-pick detection (Pass 2) |
| Fairness Integrity | Standard integrity matrix — 15 tests across commit-reveal, determinism, payout, isolation, and parameter enforcement |
What Audit Guarantees
- Outcomes are deterministic and reproducible from the recorded inputs
- Live game results match independent recomputation for the verified sample (7,050 / 7,050)
- Mine positions follow the uniform distribution expected from the backward Fisher-Yates shuffle across all 24 mine counts
- RTP is proven analytically: winChance × payoutMult = 0.999 for all 300 (mine count, reveals) combinations by combinatorial cancellation
- Simulated RTP of 99.904% across 24 million rounds (1M per mine count) — matches the theoretical 99.9% within expected variance
- The mine set is fixed at round start and independent of which tile the player reveals
- Client seed is a genuine, browser-generated input that materially influences results
- The house edge is a flat 0.1% across all 24 mine counts — no scaling edge
- All 16 fairness integrity tests passed at audit time, including the Mines-specific multi-step state integrity test
References
Mines — Game Rules7 sections▶
Mines is a grid-based reveal game: choose how many mines hide on a 5×5 board, then flip tiles one at a time. Every safe reveal grows the payout; hit a mine and the round ends.
Game Rules
Mines is a grid-based game on a 5×5 board of 25 tiles. Before each round the player chooses a mine count between 1 and 24. The platform places the mines on the grid server-side using a backward Fisher-Yates shuffle seeded with HMAC-SHA256. The player then reveals tiles one at a time: each safe reveal grows the multiplier; hitting a mine ends the round and forfeits the bet. The player may cash out after any safe reveal. More mines means a higher reward per safe reveal but a higher chance of hitting one.
How to Play
1. Choose mine count — Select between 1 and 24 mines. More mines means higher reward per safe reveal but a higher chance of hitting a mine.
2. Enter bet amount — Choose how much to wager.
3. Reveal tiles — Click tiles one at a time. Each safe reveal grows the running multiplier.
4. Cash out or continue — After any safe reveal, choose to cash out at the current multiplier or push on for more.
5. Outcome — If you hit a mine, the round ends and the bet is lost. If you cash out, payout = bet × multiplier at the current reveal count.
Win Conditions
The win condition in Mines depends on whether the revealed tile is in the mine set.
| Outcome | Condition | Example (mines=3, k=5) |
|---|---|---|
| Safe reveal | Revealed tile is not in mine set | Multiplier grows; player may cash out or continue |
| Cash out | Player stops after k safe reveals | k=5 → payout × 2.017× |
| Loss | Revealed tile is in mine set | Round ends, bet is lost |
Risk vs Reward
The core mechanic of Mines is the tradeoff between mine count and safe-reveal risk.
- Mine count controls risk per reveal — more mines means fewer safe tiles, higher multiplier growth per reveal, and a higher chance the next reveal ends the round
- Reveal count controls total multiplier — each additional safe reveal multiplies your running payout by the ratio
(25−k) / (25−m−k) - RTP is constant — all 24 mine counts have the same theoretical RTP (99.9%) regardless of how many reveals you take
Parameters
| Parameter | Value | Notes |
|---|---|---|
| Grid Size | 25 tiles (5×5) | Fixed; all rounds use this layout |
| Mine Count | 1–24 | Player-selected; determines risk and multiplier curve |
| Configurations | 24 | One per distinct mine count |
| House Edge | 0.1% flat | No scaling — same edge for all mine counts and all bet sizes |
| Theoretical RTP | 99.9% | Verified across all 24 mine counts |
| RNG Algorithm | HMAC-SHA256 | Backward Fisher-Yates with bias-free rejection sampling; key = hex-decoded server seed |
Seed Formats
Every Mines bet uses three cryptographic inputs to generate the mine layout.
| Seed Type | Format | Example | Purpose |
|---|---|---|---|
| Server Seed | 64-char hex (32 bytes) | 01b20487103f970a… | Casino-provided randomness |
| Client Seed | Alphanumeric string | NxcarOV28bkerHJk | Player-contributed entropy |
| Nonce | Integer ≥ 0 | 6 | Per-round counter within the active seed pair |
serverSeedHashed (SHA-256 of the raw hex bytes) before play and revealed only on seed rotation. The HMAC key is the hex-decoded bytes of the server seed, not its UTF-8 text.Multiplier & Payout
The payout multiplier after k safe reveals with m mines is the combinatorial ratio of unrevealed configurations multiplied by the house-edge factor.
multiplier(k, m) = C(25, k) / C(25−m, k) × 0.999| Reveals (k) | Mines (m) | Raw multiplier | With 0.1% edge |
|---|---|---|---|
| 1 | 1 | 1.0417× | 1.0406× |
| 1 | 3 | 1.1364× | 1.1352× |
| 1 | 5 | 1.2500× | 1.2488× |
| 3 | 5 | 2.0175× | 2.0155× |
| 5 | 3 | 1.9409× | 1.9390× |
| 10 | 3 | 4.3636× | 4.3592× |
multiplier as the raw combinatorial ratio C(25,k)/C(25−m,k) without the edge applied, and no_house_edge_multiplier as the actual payout multiplier with the 0.999 edge factor applied. Payout is computed as amount_won = amount × no_house_edge_multiplier. The naming is counter-intuitive — the field labelled no_house_edge_multiplier is the one that includes the house edge. We verify against the correct field throughout.Why Provably Fair Matters▶
Traditional online casinos ask you to trust them. Provably fair cryptography replaces that trust with independently verifiable proof. Every bet you place can be reconstructed from three public inputs — the server seed, your client seed, and the nonce — and the result you see should match the result anyone else can compute from those inputs.
- No outcome manipulation — the server seed is committed (SHA-256 hashed) and published before you play. The casino cannot change the outcome based on your bet size or history without breaking the commitment.
- Your seed matters — the client seed is controlled by your browser, not the casino. Changing it produces a different outcome for the same server seed and nonce, which proves your input is genuinely part of the computation.
- Verifiable by anyone — once the server seed is revealed on rotation, anyone with the seed triple can recompute the result with a few lines of code. The casino has no way to hide a tampered outcome.
- Mathematically bounded — the published multiplier formula determines payouts. We prove from first principles that win-chance × payout-multiplier equals 99.9% for every configuration, so the house edge cannot be inflated silently.
High-Level Overview8 sections▶
This section walks through how a Mines bet moves through the system end-to-end — from the server seed commitment, through the bet and reveal flow, to the post-rotation verification step that closes the loop.
Checklist Reference
Commit-Reveal
| Check | What it proves |
|---|---|
| Server seed committed before play | Casino cannot change outcome post-bet |
| SHA-256 of revealed seed matches committed hash | Revealed seed is the one that was actually committed |
| Hash identical across all bets in an epoch | All bets within a seed pair share the same commitment |
Seed System
| Check | What it proves |
|---|---|
| Nonces are sequential within an epoch | No nonce skipping or reuse |
| Single client seed per epoch | Client seed cannot be silently rotated mid-epoch |
| Next-seed pre-committed before rotation | Future commitment chain is locked in before reveal |
RNG & Parity
| Check | What it proves |
|---|---|
| Mine positions recompute from seeds + nonce | Outcome is deterministic and verifiable |
| Independent verifier matches live API for every bet | No hidden inputs to the RNG |
| Mine set invariant across reveal sequence | Layout fixed at round start, not rewritten per reveal |
Payout & RTP
| Check | What it proves |
|---|---|
| Multiplier matches C(25,k)/C(25−m,k) × 0.999 | Payout formula matches published rules |
| amount_won = amount × no_house_edge_multiplier | Payout arithmetic is exact to 1e-8 |
| winChance × payoutMult = 0.999 for all m | RTP is fixed at 99.9% by construction, not observation |
Integrity
| Check | What it proves |
|---|---|
| Wrong client seed produces different mines | Client seed genuinely influences outcome |
| Bet size does not influence outcome | Phase C $10 bets match Phase A/B $0.01 bets |
| No cherry-picking in seed selection (Pass 2) | Casino is not curating favourable seeds |
High-Level Flow
A Mines round runs through five stages — from the casino committing to a server seed before you play, through the bet and reveals, to the post-rotation verification that anyone can run.
1. Commit — Server generates server seed, publishes SHA-256 hash as `serverSeedHashed`. Player sees only the hash.
2. Seed pair activated — Client seed is browser-generated (or player-set). Nonce starts at 0. Next server seed is pre-committed.
3. Bet — Player selects mine count and bet amount. Platform computes mine positions via HMAC-SHA256 backward Fisher-Yates using the active seed pair + nonce.
4. Reveals — Player reveals tiles one at a time. Multiplier grows per safe reveal. Player may cash out at any point.
5. Rotate — On client-seed change or manual rotation, the active server seed is revealed. The next-committed seed becomes active.
6. Verify — Anyone with serverSeed, clientSeed, nonce, and mine count can recompute the exact mine set.
Provably Fair Model
Mines is a commit-reveal provably fair game. The casino commits to a server seed by publishing its SHA-256 hash before any bet is placed. Once play on that seed is complete and the seed is rotated, the casino reveals the raw server seed. Anyone can then verify two things: (1) that the revealed seed hashes to the previously-published commitment, and (2) that the mine positions for every bet in that epoch recompute correctly from the seed triple. The model does not require trust in the casino — only that SHA-256 and HMAC-SHA256 are cryptographically sound, which is a well-established assumption.
Commit-Reveal Model
Mines uses a four-phase commit-reveal cycle. Each phase has a single, well-defined purpose, and every phase produces artefacts that can be verified after the fact.
Commit phase
Server generates a 32-byte random server seed, computes `SHA-256(hex_bytes(serverSeed))`, and publishes the hash as `serverSeedHashed`. The raw seed is kept secret. A next-seed hash is also pre-committed.
Bet phase
Player places bets against the active seed pair. For each bet, mine positions are computed via `HMAC-SHA256(key=hex_bytes(serverSeed), msg=clientSeed:nonce:cursor)` followed by backward Fisher-Yates on the 25-tile grid. Nonce increments per bet.
Reveal phase
Player (or system) rotates the seed pair — typically by changing the client seed. The previously-active server seed is now revealed in the API. The pre-committed next-seed hash becomes the active `serverSeedHashed`.
Verify phase
Anyone with the seed triple can run HMAC-SHA256 + Fisher-Yates independently and confirm that mine positions match the values returned by the live API. The commitment chain is preserved: revealed hash = prior commitment.
Player-Controlled Client Seed
The client seed is the player's lever into the RNG. It is generated in the browser (not the server) and can be replaced at any time, which forces a seed rotation.
- Browser-generated — a fresh 16-character alphanumeric string is produced client-side on first load
- Player-editable — the player can overwrite it with any value they choose; changing the client seed triggers a seed rotation and reveals the active server seed
- Materially influences outcome — we verified that changing
clientSeedto a random alternative produces different mine positions in 646 / 650 tests (99.4%); the remaining 4 collisions are expected at low mine counts where the sample space is small - Not known to the server at commit time — the server publishes
serverSeedHashedbefore your client seed is known, so it cannot cherry-pick a commitment that favours the casino against your specific seed
Nonce Lifecycle
The nonce is a per-round counter within the active seed pair. It starts at 0, increments by 1 per bet, and resets only on seed rotation. The nonce is reused across all reveals in a single round — the mine set is computed once, at bet time, and is unchanged by subsequent reveals.
- Starts at 0 on every new seed pair
- Increments sequentially — no skipping, no reuse within an epoch
- Resets on rotation — every rotation produces a fresh nonce=0 sequence
- Shared across reveals — all reveals in a round use the same nonce; the mine set is fixed at
noncetime
// Per seed pair, nonce sequence:// bet 1 → nonce=0// bet 2 → nonce=1// bet 3 → nonce=2// ...// On client seed change:// serverSeed revealed, new pair activated, nonce resets to 0Determinism Guarantee
Given serverSeed, clientSeed, nonce, and mineCount, the mine set is fully determined — there is no randomness, no hidden input, and no dependence on bet amount, timestamp, or any other variable. The pipeline is:
// Inputs: serverSeed (hex), clientSeed (string), nonce (int), mineCount (1–24) key = Buffer.from(serverSeed, 'hex') // hex-decoded bytes, NOT utf-8grid = [0, 1, 2, ..., 24]cursor = 0 for i from 24 down to 1: range = i + 1 maxFair = 0xFFFFFFFF - (0xFFFFFFFF % range) loop: hash = HMAC-SHA256(key, `${clientSeed}:${nonce}:${cursor}`) scan 4-byte chunks of hash: if chunk < maxFair: j = chunk % range swap grid[i] ↔ grid[j] break out of loop cursor++ // only if no chunk passed rejection mine_positions = grid[0 .. mineCount-1] // sorted ascendingTechnical Glossary7 categories▶
Terms and definitions used throughout this audit report, grouped by category.
Core Concepts
| Term | Definition |
|---|---|
| Provably Fair | A system where every game outcome can be independently verified from public cryptographic inputs, without trusting the operator. |
| Commit-Reveal | A two-phase protocol where a party publishes a hash of a secret before play (commit), then reveals the secret afterward (reveal). The hash proves the secret was fixed in advance. |
| Determinism | A property where the same inputs always produce the same output. Mines is deterministic: (serverSeed, clientSeed, nonce, mineCount) uniquely determines the mine set. |
| Epoch | The set of bets placed under a single active seed pair, from activation to rotation. Also called a seed pair lifecycle. |
Seed System
| Term | Definition |
|---|---|
| Server Seed | A 32-byte random value generated by the casino. Represented as a 64-character hex string. Used as the HMAC key (hex-decoded). |
| Server Seed Hashed | SHA-256(hex_bytes(serverSeed)). The commitment published before play. |
| Client Seed | A player-controlled alphanumeric string, browser-generated by default. Part of the HMAC message. |
| Nonce | A per-round counter within the active seed pair. Starts at 0, increments per bet, shared across all reveals in a single round, resets on seed rotation. |
| Cursor | A rejection-sampling retry counter appended to the HMAC message. Increments only when every 4-byte chunk of the previous hash exceeded the fairness threshold. |
| Seed Pair | The combination of one server seed and one client seed. A seed pair defines an epoch. |
| Next Server Seed Hash | A pre-commitment to the server seed that will become active after the current one is rotated. Locks the future chain in advance. |
Cryptographic Functions
| Term | Definition |
|---|---|
| SHA-256 | A 256-bit cryptographic hash function. Used here for server seed commitment: SHA-256(hex_bytes(serverSeed)) = serverSeedHashed. |
| HMAC-SHA256 | Hash-based message authentication code built on SHA-256. Takes a key and a message, returns a 256-bit (64-hex-char) digest. Used here for mine position generation. |
| HMAC-SHA256 Backward Fisher-Yates | The full RNG pipeline: HMAC-SHA256 produces the 4-byte random chunks; backward Fisher-Yates uses those chunks to shuffle the 25-tile grid. Mines-specific implementation. |
| Rejection Sampling | A technique for bias-free uniform sampling. A 4-byte chunk is accepted only if it falls below maxFair = 0xFFFFFFFF − (0xFFFFFFFF mod range). Guarantees every position in range is equally likely. |
Verification Terms
| Term | Definition |
|---|---|
| Parity | Agreement between the live game's reported outcome and an independent recomputation from the same inputs. 100% parity means zero mismatches. |
| Recomputation | Running the audit's standalone RNG implementation on captured seed triples and comparing the output to the live API response. |
| Anti-Circularity Proof | A proof that RTP is not an empirical observation but a mathematical identity — winChance × payoutMult = 0.999 by algebraic cancellation, independent of any simulation. |
| Cherry-Pick Detection (Pass 2) | A statistical test over revealed casino seeds that checks whether early-epoch win rates systematically exceed later-epoch win rates — the signature of seed pre-selection. |
Game Mechanics
| Term | Definition |
|---|---|
| Mine Count | The number of mines placed on the 25-tile grid for a given round. Player-selected, range 1–24. |
| Reveal | The act of clicking a tile. A safe reveal grows the multiplier; a mine reveal ends the round. |
| Multiplier Chain | The sequence of running multipliers as a player reveals tiles: m₁, m₂, ..., mₖ. Each step satisfies mₖ = C(25,k) / C(25−m,k) × 0.999. |
| Cash-Out | Stopping after k safe reveals and collecting amount × mₖ. Only available after at least one safe reveal. |
| Mine Set Invariance | The property that the mine set is fixed at round start and does not change based on which tiles the player reveals, or in what order. |
| Combinatorial Identity | The algebraic cancellation that proves Mines' RTP: winChance(k, m) × payoutMult(k, m) = (C(25−m, k) / C(25, k)) × (C(25, k) / C(25−m, k) × 0.999) = 0.999 for all valid k, m. |
Audit Terms
| Term | Definition |
|---|---|
| Fairness Integrity (FI) Matrix | The standard 15-test matrix applied to every audit, covering commit-reveal, determinism, payout, isolation, and parameter enforcement. Documented in S5. |
| Chi-Squared Test | A goodness-of-fit test comparing observed distributions (e.g., mine positions, win counts) to theoretical expectations. Used with Bonferroni correction where multiple configurations are tested. |
| Bonferroni Correction | A multiple-comparison adjustment: when testing N hypotheses at significance α, the per-test threshold is α/N. Prevents false positives from mass testing. |
| Point-in-Time Audit | An audit verdict that applies to the code and configuration in force at the audit date. Subsequent changes are outside scope unless re-certified. |
Data Formats
| Term | Definition |
|---|---|
| mines-master-6500bets.json | Primary capture dataset. 6,500 auto-bet rounds across Phases A–D, all with k=1 safe-reveal outcomes. SHA-256 pinned in S1. |
| mines-phaseE-550bets.json | Multi-reveal and cash-out capture dataset. 550 rounds across 5 sub-phases (E1–E5) covering k up to 5 and varied reveal positions. SHA-256 pinned in S1. |
| verification-results.json | Output of the 23-step verification pipeline. Contains per-step pass/fail status and per-step detail strings. |
| simulation-results.json | Output of the 24-configuration × 1M-round simulation. Contains Pass 1 (fresh seeds, RTP + chi²) and Pass 2 (casino seeds, cherry-pick detection). |
Seed, Nonce & Determinism
Can the casino change your outcome after you bet?
Every Mines round on Duel.com is generated from three inputs: server seed, client seed, and nonce. The casino commits to its server seed by publishing a SHA-256 hash before you place any bets. After you rotate your seed, the server reveals the actual seed — and anyone can verify that the hash matches. This cryptographic commitment makes it impossible for the casino to secretly change your outcome after you bet.
What we verified
- Casino commits to the server seed hash before any bet is placed
- Client seed is browser-generated — server cannot know it at commitment time (Full Pass origin)
- Players can set or change their client seed at any time via the rotation UI
- Nonce increments by 1 per bet with no gaps or duplicates across 142 epochs
- Mine positions are fully determined by the seed inputs before any tile is revealed
- Identical inputs always produce the same mine set — confirmed across all 7,050 bets
- Your client seed is a genuine input — changing it changes the mine layout
What this means for you
- The casino cannot change where the mines are after you bet
- You contribute randomness the server cannot predict or pre-select against
- Every bet is unique — the nonce ensures no two bets share an outcome
- Any result can be independently verified using the public tools and repo
- Outcomes are tamper-proof and verifiable even months later
- Cherry-picking favourable seeds is structurally impossible
- Casino commits to the server seed hash before any bet is placed
- Client seed is browser-generated — server cannot know it at commitment time (Full Pass origin)
- Players can set or change their client seed at any time via the rotation UI
- Nonce increments by 1 per bet with no gaps or duplicates across 142 epochs
- Mine positions are fully determined by the seed inputs before any tile is revealed
- Identical inputs always produce the same mine set — confirmed across all 7,050 bets
- Your client seed is a genuine input — changing it changes the mine layout
- The casino cannot change where the mines are after you bet
- You contribute randomness the server cannot predict or pre-select against
- Every bet is unique — the nonce ensures no two bets share an outcome
- Any result can be independently verified using the public tools and repo
- Outcomes are tamper-proof and verifiable even months later
- Cherry-picking favourable seeds is structurally impossible
Server seed committed before betPass
Client seed originPass
Client seed controlPass
Nonce sequencingPass
Hash consistency within epochPass
Seed hash integrityPass
Deterministic outputPass
Client seed participationPass
All 130 revealed seeds hash-verified. Every seed rotation was verified — the next seed the casino pre-committed always matched what was actually used. Outcomes are fully deterministic — the same server seed, client seed, and nonce always produce the same mine layout. The casino cannot change your result after you bet.
| Check | Result | Evidence |
|---|---|---|
| Server seed committed before bet | Pass | SHA-256 hash of server seed published before play — casino cannot change mine layout after betting |
| Client seed origin | Pass | Browser-generated (Full Pass) — server commits before client seed is known |
| Client seed control | Pass | Player can set/change client seed via rotation UI at any time |
| Nonce sequencing | Pass | Sequential within each epoch, 0 gaps, 0 duplicates across 142 epochs |
| Hash consistency within epoch | Pass | serverSeedHashed constant across all bets within each of 142 epochs |
| Seed hash integrity | Pass | 130 / 130 revealed seeds hash-verified — commitment chain intact |
| Deterministic output | Pass | Same (serverSeed, clientSeed, nonce, mineCount) always produces same mine SET — 7,050 / 7,050 confirmed |
| Client seed participation | Pass | Client seed is a genuine input — changing it changes the mine positions (646 / 650, 99.4%) |
Server seed committed before betPass
Client seed originPass
Client seed controlPass
Nonce sequencingPass
Hash consistency within epochPass
Seed hash integrityPass
Deterministic outputPass
Client seed participationPass
All 130 revealed seeds hash-verified. Every seed rotation was verified — the next seed the casino pre-committed always matched what was actually used. Outcomes are fully deterministic — the same server seed, client seed, and nonce always produce the same mine layout. 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, the casino generates a secret server seed and publicly commits to it by displaying its SHA-256 hash. The server seed is a 64-character hex string (32 bytes), and the commitment hash is computed as SHA-256(hexDecode(serverSeed)). This cryptographic commitment locks the server's randomness before the player acts. After the epoch ends, the actual server seed is revealed — and anyone can verify that the hash matches.
export function verifyHash(serverSeed: string, serverSeedHashed: string): boolean { const seedBytes = Buffer.from(serverSeed, 'hex'); const computed = crypto.createHash('sha256').update(seedBytes).digest('hex'); return computed === serverSeedHashed;}Real Example from Live Data:
// Seed block captured at bet time (round 25075415){ "serverSeedHashed": "2d3a2c76d354a0a859d84b9fe8183e8f48cf6e973e0264947af3f95c1505e72b", "clientSeed": "NxcarOV28bkerHJk", "nonce": 6} // After seed rotation, the plaintext was revealed as:// serverSeed = "01b20487103f970a94c7d615ba53497605020ee1dc3e9e601261d4088275bbe3"//// SHA-256 of the revealed seed equals the committed hash above — verified below.Verification:
const crypto = require('crypto'); const serverSeed = "01b20487103f970a94c7d615ba53497605020ee1dc3e9e601261d4088275bbe3"; const hashedServerSeed = crypto .createHash("sha256") .update(Buffer.from(serverSeed, 'hex')) .digest("hex"); console.log(hashedServerSeed);// Output: 2d3a2c76d354a0a859d84b9fe8183e8f48cf6e973e0264947af3f95c1505e72b ✅1.2Commitment Linkage (Next-Seed Promotion)
At every seed rotation, the nextServerSeedHash pre-committed before rotation must match the serverSeedHashed that becomes active after rotation. This chain ensures the server cannot generate seeds at rotation time after seeing the player's client seed — the next seed was already locked before the previous epoch began.
// Step 2: Next-Seed Promotion (Commitment Linkage)for (const s of seeds) { if (!s.nextSeedPromotion) continue; promoChecked++; // recompute linkage — don't trust a captured flag: const { previousNextHash, newActiveHash } = s.nextSeedPromotion; if (previousNextHash !== newActiveHash) promoFails++;}1.3Hash Consistency Within Epoch
For each of the 142 bet-bearing epochs, the serverSeedHashed field was extracted from every bet response and checked for uniqueness. More than one distinct hash within a single epoch would indicate a mid-epoch seed substitution — a critical commit-reveal violation. Step 3 of the verification pipeline runs against the primary dataset's 130 epochs directly; the additional 12 Phase E epochs are checked implicitly through the mine-set invariance test (Step 21) — any mid-epoch hash change there would surface as a recomputation failure.
// Step 3: Hash Consistency Within Epochfor (const [, epochBets] of byHash) { const distinctHashes = new Set(epochBets.map(b => b.seed.serverSeedHashed)); if (distinctHashes.size !== 1) epochsWithMultipleHashes++;}serverSeedHashed constant within every epoch. Zero mid-epoch changes detected.1.4Client Seed Origin & Control
Players have full control over their client seed through the Duel.com fairness UI, allowing them to view, modify, or randomize it at any time before placing bets. The client seed is an input to every HMAC-SHA256 computation — changing it produces a completely different mine layout. The client seed is browser-generated (Full Pass origin) — the server commits to its seed before the client seed for that epoch is known, making cherry-picking structurally impossible.
EveJlkDtA24dfvBrNxcarOV28bkerHJkdatWCpUusET02jWwQfpCgsRdMX8w1CZbDZINIfo9nv2pxGzu
mines-master-6500bets.json, mines-phaseE-550bets.json1.5Nonce Incrementation
The nonce begins at 0 and increments by 1 for each bet under the same server seed. In Mines, the nonce advances once per round — every bet uses one nonce value, which is 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 for the primary capture (nonces 0–49 per epoch, 130 epochs); Phase E epochs vary in length per sub-phase. Epoch length is not enforced by the casino — players can rotate at any time.
// Step 4: Nonce Auditfor (const [hash, epochBets] of byHash) { const sorted = [...epochBets].sort((a, b) => a.seed.nonce - b.seed.nonce); const nonces = sorted.map(b => b.seed.nonce); const clientSeeds = new Set(sorted.map(b => b.seed.clientSeed)); if (clientSeeds.size !== 1) { hardFailures.push(`Epoch ${shortHash}: ${clientSeeds.size} distinct client seeds`); } if (nonces[0] !== 0) { hardFailures.push(`Epoch ${shortHash}: first nonce is ${nonces[0]} (expected 0)`); }}1.6Deterministic Mapping
The RNG algorithm is fully deterministic: given the same server seed, client seed, nonce, and mine count, it always produces the exact same mine SET. The algorithm is a backward Fisher-Yates shuffle with HMAC-SHA256 on a 25-tile grid. The server seed is hex-decoded to 32 raw bytes before use as the HMAC key. HMAC message format: clientSeed:nonce:cursor where cursor starts at 0 for the first shuffle step (i=24). Each step produces a uint32 from a 4-byte HMAC chunk; rejection sampling via the maxFair ceiling ensures bias-free index selection. After 24 steps, positions[0..mineCount-1] (sorted) gives the mine SET.
export function computeMinePositions( serverSeed: string, clientSeed: string, nonce: number, mineCount: number, gridSize = GRID_SIZE,): number[] { const key = Buffer.from(serverSeed, 'hex'); return computeMinePositionsFromBuffer(key, clientSeed, nonce, mineCount, gridSize);}mines_positions. Zero SET mismatches. No external entropy is mixed into the Mines RNG.Real Bet Verified:
// Source: data/mines-master-6500bets.json// Round ID: 25075415 (Phase A, mines=3, nonce=6)// ✅ VERIFIED — mine set recomputed from revealed server seed { "serverSeed": "01b20487103f970a94c7d615ba53497605020ee1dc3e9e601261d4088275bbe3", "serverSeedHashed": "2d3a2c76d354a0a859d84b9fe8183e8f48cf6e973e0264947af3f95c1505e72b", "clientSeed": "NxcarOV28bkerHJk", "nonce": 6, "mines_count": 3, "revealed_positions": [0], "mines_positions": [5, 16, 21], "multiplier": "1.136363636363636364", "no_house_edge_multiplier": "1.135227272727272728", "amount_won": "0.011352272727272727"}Verification:
// computeMinePositions("01b20487…", "NxcarOV28bkerHJk", 6, 3) // Backward Fisher-Yates: 24 steps (i=24 downto 1)// Each step: HMAC-SHA256(hexDecode(serverSeed), "NxcarOV28bkerHJk:6:{cursor}")// cursor = gridSize - 1 - i (starts at 0)// Rejection sampling: chunk < maxFair → j = chunk % range; swap // Mine SET: {5, 16, 21} ✅// Tile 0 revealed (not in mine set) → win ✅// Multiplier (mines=3, k=1): 25 / (25−3) = 25/22 = 1.1364× ✅// Payout multiplier: 1.1364 × 0.999 = 1.1352× ✅// Payout: 0.01 × 1.1352 = 0.011352 ✅1.7Client Seed Influence
To confirm the client seed is a genuine input to the HMAC-SHA256 computation, a sample of bets across all 130 primary epochs was recomputed using a deliberately incorrect client seed ('wrong-client-seed-test'). 646 of 650 bets (99.4%) produced a different mine set with the wrong client seed — every such outcome proves the client seed is a material input. The 4 collisions occurred in low-mine-count rounds (e.g., mines=1) where the 25-position sample space makes a random collision expected; this is not a defect but a statistical consequence of small-cardinality mine sets.
// Step 6: Client Seed Influenceconst WRONG_CLIENT = 'wrong-client-seed-test';for (const [hash, epochBets] of byEpoch) { const ss = seedMap.get(hash); if (!ss) continue; for (const b of epochBets.slice(0, 5)) { tested++; const correct = computeMinePositions(ss, b.seed.clientSeed, b.seed.nonce, b.request.mines_count); const wrong = computeMinePositions(ss, WRONG_CLIENT, b.seed.nonce, b.request.mines_count); if (correct.join(',') !== wrong.join(',')) changed++; }}Technical Evidence & Verification5 sections
1.8Evidence Coverage Summary6 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Seed hash integrity (Step 1) | 130 / 130 revealed primary seeds hash-verified | Pass |
| Commitment linkage (Step 2) | 134 / 134 next-seed promotions | Pass |
| Hash consistency (Step 3) | 130 / 130 primary epochs | Pass |
| Nonce audit (Step 4) | 0 gaps, 0 duplicates across 130 epochs | Pass |
| Mine position recomputation (Step 5) | 6,500 / 6,500 | Pass |
| Client seed influence (Step 6) | 646 / 650 (99.4%) | Pass |
1.9Code References6 source files
| File | Purpose |
|---|---|
| tests/verify.ts | 23-step verification pipeline (Steps 1–6 cover S1) |
| tests/steps/commitment.ts | Steps 1–4: Commit-reveal integrity checks |
| tests/steps/determinism.ts | Steps 5–6: Mine position recomputation and client seed influence |
| src/rng.ts | HMAC-SHA256 backward Fisher-Yates (computeMinePositions, verifyHash) |
| src/loader.ts | Dataset loading + seed/bet parsing |
| capture/mines-capture.reference.js | Browser-based data collection script (Phases A–D) |
1.10Datasets UsedSource artifacts
| Property | Value |
|---|---|
| Source | Live Mines game data from Duel.com |
| Total Records | 6,500 bets across 130 epochs (134 seed entries) |
| SHA-256 | 331f74ff98b88d06242d57e806186548612f13c4269eb754ebb571d8a6fc9b20 |
Fields used: serverSeed, serverSeedHashed, clientSeed, nonce, mines_count, mines_positions, revealed_positions, multiplier, no_house_edge_multiplier, amount_won
1.11Verified Invariants9 invariants
| Invariant | Result |
|---|---|
| SHA-256(hexDecode(serverSeed)) = serverSeedHashed for all 130 revealed primary seeds | Pass |
| Next-seed promotion chain intact for all 134 transitions | Pass |
| serverSeedHashed constant within epoch for all 130 primary epochs | Pass |
| Zero nonce gaps within any epoch | Pass |
| Zero nonce duplicates within any epoch | Pass |
| Single client seed per epoch across all 130 epochs | Pass |
| Same inputs produce same mine SET for all 6,500 primary bets | Pass |
| Wrong client seed changes mine SET in 99.4% of tests (646 / 650) | Pass |
| Client seed is browser-generated (Full Pass origin) | Pass |
1.12Reproduction InstructionsRun locally
Clone the repository, install dependencies, and run the verification suite:
git clone https://github.com/ProvablyFair-org/duel-mines.gitcd duel-mines && npm installnpm 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 Promotion (Commitment Linkage)
[PASS] Step 3 — Hash Consistency Within Epoch
[PASS] Step 4 — Nonce Audit
[PASS] Step 5 — Mine Position Recomputation
[PASS] Step 6 — Client Seed InfluenceRNG & Entropy Model
Is the randomness genuinely random, or could it be rigged?
This section verifies that Duel.com's Mines random number generation produces cryptographically sound, unbiased outputs using only the disclosed inputs. The RNG uses a backward Fisher-Yates shuffle with HMAC-SHA256 — each shuffle step derives a swap index from a 4-byte HMAC chunk with bias-free rejection sampling via the maxFair ceiling. We independently implemented this algorithm, verified it produces the same results as the live game, and confirmed no hidden inputs can influence outcomes.
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
- Rejection sampling via
maxFairceiling eliminates modulo bias for all ranges (2–25) - Mine placement follows uniform distribution for all 24 configurations (confirmed over 24M simulated rounds)
- Consecutive outcomes are statistically independent — no patterns, no streaks
- 99.4% of outcomes change with a different client seed (646 / 650 tested bets)
What this means for you
- Each mine placement is generated fairly and cannot be skewed
- All 25 grid positions are equally likely to host a mine — no positional bias
- No hidden randomness or server-side tricks influence where mines appear
- Consecutive bets are not correlated — past results don't affect future outcomes
- 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
- Rejection sampling via
maxFairceiling eliminates modulo bias for all ranges (2–25) - Mine placement follows uniform distribution for all 24 configurations (confirmed over 24M simulated rounds)
- Consecutive outcomes are statistically independent — no patterns, no streaks
- 99.4% of outcomes change with a different client seed (646 / 650 tested bets)
- Each mine placement is generated fairly and cannot be skewed
- All 25 grid positions are equally likely to host a mine — no positional bias
- No hidden randomness or server-side tricks influence where mines appear
- Consecutive bets are not correlated — past results don't affect future outcomes
- The algorithm depends only on seeds you can verify
RNG derived only from disclosed inputsPass
Entropy purityPass
Algorithm independently implementedPass
Modulo biasPass
maxFair ceiling eliminates bias for all ranges (2–25)Key encoding verifiedPass
Serial independencePass
Client seed influencePass
The Mines RNG uses only the disclosed inputs, produces uniform mine-position distribution across all 24 configurations, and shows no serial dependence across 24M simulated rounds. The client seed is a genuine input — 99.4% of outcomes change with a different seed.
| Check | Result | Evidence |
|---|---|---|
| RNG derived only from disclosed inputs | Pass | HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce:cursor) — no hidden entropy |
| Entropy purity | Pass | No timestamps, external APIs, Math.random, or server-side state |
| Algorithm independently implemented | Pass | Independent implementation produces identical mine sets for all 7,050 bets |
| Modulo bias | Pass | Rejection sampling via maxFair ceiling eliminates bias for all ranges (2–25) |
| Key encoding verified | Pass | Server seed hex-decoded to bytes (not UTF-8) — confirmed via 7,050-bet recomputation |
| Serial independence | Pass | Lag-1 autocorrelation near zero and runs tests pass across all 24 configs at 1M rounds each |
| Client seed influence | Pass | 99.4% of outcomes change with an alternate client seed — confirmed across 650 tested bets |
RNG derived only from disclosed inputsPass
Entropy purityPass
Algorithm independently implementedPass
Modulo biasPass
maxFair ceiling eliminates bias for all ranges (2–25)Key encoding verifiedPass
Serial independencePass
Client seed influencePass
The Mines RNG uses only the disclosed inputs, produces uniform mine-position distribution across all 24 configurations, and shows no serial dependence across 24M simulated rounds. The client seed is a genuine input — 99.4% of outcomes 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 backward Fisher-Yates shuffle. The algorithm iterates from index 24 down to 1 (24 steps). At each step, an HMAC-SHA256 hash is computed with the hex-decoded server seed as key and clientSeed:nonce:cursor as the message. The first 4-byte chunk below the maxFair ceiling is used: j = chunk % range determines the swap index. After all 24 steps, positions[0..mineCount−1] (sorted ascending) gives the mine SET.
| Component | Detail |
|---|---|
| Hash function | HMAC-SHA256 |
| Key | Buffer.from(serverSeed, 'hex') — 32 bytes |
| Message | clientSeed:nonce:cursor |
| Extraction | 4-byte chunks → parseInt(hex, 16) → uint32 |
| Rejection | value < maxFair — bias-free ceiling per range |
| Reduction | uint32 % range → swap index j ∈ [0, range) |
| Shuffle | Backward Fisher-Yates: i=24 downto 1 |
| Output | positions[0..mineCount−1] (sorted) → mine SET |
export function computeMinePositionsFromBuffer( keyBuffer: Buffer, clientSeed: string, nonce: number, mineCount: number, gridSize = GRID_SIZE,): number[] { const positions: number[] = Array.from({ length: gridSize }, (_, i) => i); for (let i = gridSize - 1; i > 0; i--) { const range = i + 1; const maxFair = MAX_UINT32 - (MAX_UINT32 % range); let cursor = gridSize - 1 - i; while (true) { const message = `${clientSeed}:${nonce}:${cursor}`; const hmac = crypto.createHmac('sha256', keyBuffer).update(message).digest('hex'); let found = false; for (let off = 0; off + 8 <= hmac.length; off += 8) { const value = parseInt(hmac.substring(off, off + 8), 16); if (value < maxFair) { const j = value % range; [positions[i], positions[j]] = [positions[j], positions[i]]; found = true; break; } } if (found) break; cursor++; } } return positions.slice(0, mineCount).sort((a, b) => a - b);}2.2Entropy Sources
All randomness derives exclusively from the deterministic HMAC-SHA256 function combining three cleanly separated inputs:
| Source | Controlled By | Purpose |
|---|---|---|
| Server Seed | Casino | Base randomness (committed via SHA-256 hash before betting) |
| Client Seed | Player | Player-contributed entropy |
| Nonce | System | Uniqueness per bet (increments automatically within each epoch) |
| Cursor | System | Per-step isolation (0 to 23, one per Fisher-Yates step) |
How we know: 7,050/7,050 bets were recomputed using only the three declared inputs. If any hidden entropy source existed, recomputation would fail. It does not.
Verified absent: No timestamps, no Math.random(), no external APIs, no server-side mutable state. Only: HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce:cursor)
2.3Modulo Bias Analysis
Each shuffle step's swap index is determined by chunk % range where range varies from 2 to 25 across the 24 Fisher-Yates steps. Modulo bias occurs when 2^32 is not evenly divisible by the range. The algorithm eliminates this bias entirely via rejection sampling: a ceiling maxFair = 0xFFFFFFFF − (0xFFFFFFFF % range) ensures only unbiased chunks are used. Any chunk ≥ maxFair is discarded.
For range = 25 (first step, i=24):
maxFair = 0xFFFFFFFF − (0xFFFFFFFF % 25)
= 4,294,967,295 − 20
= 4,294,967,275 (= 0xFFFFFFEB)
Rejected values: [4,294,967,275 .. 4,294,967,295] = 21 values
Rejection rate: 21 / 4,294,967,296 ≈ 4.9 × 10⁻⁹
Accepted values: exactly divisible by 25
→ each residue 0–24 equally likely (zero bias)For range = 2 (last step, i=1), maxFair = 0xFFFFFFFF − (0xFFFFFFFF % 2) = 0xFFFFFFFE. Only two values (0xFFFFFFFE, 0xFFFFFFFF) are rejected — negligible. For all 24 ranges in the shuffle, the rejection ceiling makes every swap index uniformly distributed. Retries (all 8 chunks rejected in one hash) are astronomically rare — probability (21/2^32)^8 ≈ 10⁻⁶⁶ for range=25.
2.4RNG Isolation
Each shuffle step within a single bet uses a unique cursor value in the HMAC message (clientSeed:nonce:cursor), ensuring per-step outputs are cryptographically independent. Each bet uses a unique nonce, ensuring per-bet outputs are independent. HMAC-SHA256 is a pseudorandom function — knowing one step's output gives zero information about other steps' outputs. There is no shared state between steps, between bets, or between epochs.
Evidence: The computeMinePositionsFromBuffer implementation in 2.1 confirms this — it is a pure function with no class state, no external calls, and no cross-bet memory. The function takes (keyBuffer, clientSeed, nonce, mineCount) explicitly and returns an array. Same inputs always produce the same output.
2.5Monte Carlo Simulation (24M Rounds)
A 24,000,000-round Monte Carlo simulation (1,000,000 rounds per config, all 24 mine-count configurations) verified that the algorithm produces the expected mine-position distribution at scale.
| Metric | Value |
|---|---|
| Average simulated RTP | 99.904% |
| Average theoretical RTP | 99.900% |
| Chi-squared (α=0.01) | 24/24 configs pass |
| Bonferroni-corrected (α/24) | 24/24 pass |
| Serial independence | 24/24 pass |
// Simulation parametersconst GRID_SIZE = 25;const CONFIGS = Array.from({ length: 24 }, (_, i) => i + 1); // mine counts 1–24const ROUNDS_PER_CONFIG = 1_000_000;// Per-config pinned seeds via crypto.randomBytes// Rounds: 1,000,000 per config × 24 configs = 24,000,000// Mine-position distribution tested against uniform across 25 positionsAll 24 configurations pass individual chi-squared tests. Serial independence: 0 failures across 24M rounds.
Methodology: Per-config pinned seeds for reproducibility — one unique (serverSeed, clientSeed) pair per mine count, generated once via crypto.randomBytes. Chi-squared goodness-of-fit on mine-position frequency across the 25 grid positions. Serial independence tested via lag-1 autocorrelation and Wald-Wolfowitz runs test on the per-round mine-count sequence.
2.6Serial Independence
Serial independence ensures consecutive bet outcomes are not correlated — winning or losing on one bet does not affect the next. Two tests were applied to each configuration in the Pass 1 simulation (1,000,000 rounds per config):
Lag-1 autocorrelation: Measures correlation between consecutive mine-count sequence values. Expected r ≈ 0 for independent sequences. Threshold: |z| > 3 (where z = r × √n).
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.
export function lag1Autocorrelation(series: number[]): number { const n = series.length; const mean = series.reduce((a, b) => a + b, 0) / 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 — Round ID 25075415, mines=3, nonce 6. Verified from mines-master-6500bets.json:
serverSeed = 01b20487103f970a94c7d615ba53497605020ee1dc3e9e601261d4088275bbe3
clientSeed = NxcarOV28bkerHJk
nonce = 6
mines_count = 3
revealed = [0]| i | cursor | range | HMAC[:8] | uint32 | j | swap |
|---|---|---|---|---|---|---|
| 24 | 0 | 25 | ff89d959 | 4,287,224,153 | 3 | pos[24]↔︎pos[3] |
| 23 | 1 | 24 | ee24d5d2 | 3,995,391,442 | 10 | pos[23]↔︎pos[10] |
| 22 | 2 | 23 | 93470e02 | 2,470,907,394 | 6 | pos[22]↔︎pos[6] |
| 21 | 3 | 22 | c883330b | 3,364,041,483 | 11 | pos[21]↔︎pos[11] |
| 20 | 4 | 21 | 9e8e4926 | 2,660,124,966 | 9 | pos[20]↔︎pos[9] |
25 / (25−3) = 25/22 = 1.1364×. Payout: 0.01 × 1.1364 × 0.999 = 0.011352.Technical Evidence & Verification5 sections
2.8Evidence Coverage Summary6 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Algorithm implementation (Step 5) | 6,500/6,500 bets | Pass |
| Key encoding (hex vs UTF-8) | Confirmed via recomputation | Pass |
| Modulo bias analysis | Rejection sampling: maxFair ceiling for all ranges 2–25 | Pass |
| External entropy non-participation (Step 5) | 6,500/6,500 reproduced from disclosed inputs alone | Pass |
| Simulation chi-squared (Step 18) | 0/24 configs fail at α=0.01 | Pass |
| Serial independence (Step 18) | 0/24 configs fail (lag-1 + runs test) | Pass |
2.9Code References5 source files
| File | Purpose |
|---|---|
| src/rng.ts | HMAC-SHA256 backward Fisher-Yates (computeMinePositions, computeMinePositionsFromBuffer) |
| src/simulate.ts | Monte Carlo simulation (24M rounds, two-pass) |
| src/stats.ts | Chi-squared, lag-1 autocorrelation, Wald-Wolfowitz runs test |
| tests/steps/determinism.ts | Steps 5–6: Mine position recomputation and client seed influence |
| tests/steps/simulation.ts | Steps 18–19: Simulation integrity verification |
2.10Verified Invariants9 invariants
| Invariant | Result |
|---|---|
| HMAC-SHA256 output matches live game for all 7,050 bets | Pass |
| Key is hex-decoded (not UTF-8) — wrong encoding produces wrong mine sets | Pass |
| Rejection sampling eliminates modulo bias for all ranges (2–25) | Pass |
| No external entropy sources required for mine-set computation | Pass |
| Mine-position distribution is uniform across the 25-tile grid for all 24 configs (1M rounds each) | Pass |
| Lag-1 autocorrelation near zero for all 24 configs | Pass |
| Runs test p > 0.01 for all 24 configs | Pass |
| Per-config simulated RTP converges to 99.9% theoretical | Pass |
| Client seed change produces different mine SET in 99.4% of bets (646/650) | Pass |
2.11Datasets UsedSource artifacts
2.12Reproduction InstructionsRun locally
Clone the repository, install dependencies, and run the simulation and verification:
git clone https://github.com/ProvablyFair-org/duel-mines.gitcd duel-mines && npm installnpm run simulate # 24M-round simulation (~11 min)npm run verify # Steps 5, 18 cover S2S2-related steps:
[PASS] Step 5 — Mine Position Recomputation
[PASS] Step 18 — Simulation Results — Pass 1 IntegrityVerifier Parity
Does the live game actually follow its own rules?
This section validates that the independent verifier produces the exact same mine set as the live game for every single bet. It also confirms the win condition and that every payout matches Duel's published multiplier formula. Any mismatch would invalidate the fairness guarantee.
What we verified
- Every bet independently recomputed from seeds — full mine SET verified, not just the payout
- Payout correctness: amount_won = amount × no_house_edge_multiplier, exact to 8 decimal places for all 7,050 bets
- Multiplier formula C(25,k) / C(25−m,k) × 0.999 produces the correct value for all 24 mine-count configurations
- Bet amount is not an input to the RNG — mine layout depends only on seeds and nonce
- All five capture phases recomputed identically (config coverage, high-mine edge case, elevated stake, client-seed variation, multi-reveal + cash-out)
What this means for you
- The verifier isn't a simulation — it produces the exact same mine set as the live game
- Every bet you play can be independently recomputed by anyone
- No hidden logic alters outcomes based on how much you bet or how many tiles you reveal
- The game engine in production matches the published algorithm exactly
- Every bet independently recomputed from seeds — full mine SET verified, not just the payout
- Payout correctness: amount_won = amount × no_house_edge_multiplier, exact to 8 decimal places for all 7,050 bets
- Multiplier formula C(25,k) / C(25−m,k) × 0.999 produces the correct value for all 24 mine-count configurations
- Bet amount is not an input to the RNG — mine layout depends only on seeds and nonce
- All five capture phases recomputed identically (config coverage, high-mine edge case, elevated stake, client-seed variation, multi-reveal + cash-out)
- The verifier isn't a simulation — it produces the exact same mine set as the live game
- Every bet you play can be independently recomputed by anyone
- No hidden logic alters outcomes based on how much you bet or how many tiles you reveal
- The game engine in production matches the published algorithm exactly
Mine-set recomputationPass
Payout correctnessPass
Multiplier formula integrityPass
Bet-size independencePass
Config completenessPass
Multi-reveal parityPass
Multi-phase coveragePass
All 7,050 bets matched the independent verifier exactly — mine sets verified across all five capture phases, including 550 multi-reveal bets with up to 5 reveals each. Payout math correct. Multiplier formula confirmed across all 24 mine-count configurations.
| Check | Result | Evidence |
|---|---|---|
| Mine-set recomputation | Pass | 7,050/7,050 exact match — mine SET verified for every bet |
| Payout correctness | Pass | All 7,050 bets: amount_won = amount × no_house_edge_multiplier, exact to 8 decimal places |
| Multiplier formula integrity | Pass | All observed multipliers match C(25,k) / C(25−m,k) × 0.999 for all 24 mine-count configurations |
| Bet-size independence | Pass | Bet amount is absent from the RNG input — mine layout depends only on seeds and nonce |
| Config completeness | Pass | All 24 mine-count configurations (1–24) covered in live data |
| Multi-reveal parity | Pass | Phase E: 795 reveal steps verified across k ∈ {1..5}; 550 multi-reveal bets match recomputed mine set |
| Multi-phase coverage | Pass | 5 structured phases: config coverage (A), high-mine edge case (B), elevated stake (C), client-seed variation (D), multi-reveal + cash-out (E) |
Mine-set recomputationPass
Payout correctnessPass
Multiplier formula integrityPass
Bet-size independencePass
Config completenessPass
Multi-reveal parityPass
Multi-phase coveragePass
All 7,050 bets matched the independent verifier exactly — mine sets verified across all five capture phases, including 550 multi-reveal bets with up to 5 reveals each. Payout math correct. Multiplier formula confirmed across all 24 mine-count configurations.
How It Works — Verifier Parity8 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. 100% parity is required because even a single discrepancy would indicate either a bug in the verification logic, manipulation in the live game, or inconsistent RNG implementation. Players must be able to take the revealed seeds after gameplay, input them into the independent verifier, and receive the exact same mine layout they experienced during live play.
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, high-variance edge cases, bet-size invariance, client seed variation, and multi-reveal cash-out flows.
| Phase | Bets | Config | Bet Amount | Purpose |
|---|---|---|---|---|
| A — Configuration coverage | 4,800 | 24 configs (mines=1–24, 200 per config) | $0.01 | Verify formula correctness across every mine count |
| B — High-mine edge case | 1,000 | 1 config (mines=24) | $0.01 | Maximum variance; only 1 safe tile in 25 |
| C — Stake equivalence | 200 | 1 config (mines=12) | $10.00 | Confirm bet amount is not an RNG input |
| D — Client-seed variation | 500 | 24 configs (mines=1–24) | $0.01 | 10 custom pfaudit-prefixed client seeds; confirms seed control is player-side |
| E — Multi-reveal & cash-out | 550 | mines ∈ {3, 5, 12, 15, mixed}, k up to 5 | $0.01 | Multi-step reveal chains, cash-out correctness, reveal-position independence |
3.3Mine Position Recomputation (Step 5)
For every bet belonging to an epoch with a revealed server seed, the verifier independently computed the mine set using computeMinePositionsFromBuffer(key, clientSeed, nonce, mineCount) and compared it (as a SET) to the server-reported mines_positions. The computation uses HMAC-SHA256 backward Fisher-Yates with the hex-decoded server seed as key.
// Step 5: Mine Position Recomputationfor (const b of bets) { const ss = seedMap.get(b.seed.serverSeedHashed); if (!ss) { skipped++; continue; } const key = Buffer.from(ss, 'hex'); const computed = computeMinePositionsFromBuffer(key, b.seed.clientSeed, b.seed.nonce, b.request.mines_count); const actual = [...b.response.mines_positions].sort((a, c) => a - c); if (computed.length !== actual.length || !computed.every((v, i) => v === actual[i])) { mismatches++; }}mines_positions in the dataset. Phase E's 550 multi-reveal bets additionally verified via Step 21 — see 3.9 Evidence Coverage Summary.3.4Payout Math (Step 7)
For each of the 7,050 bets, the verifier computed amount × no_house_edge_multiplier and compared the result to the server-reported amount_won. The tolerance is 1×10⁻⁸ — any difference larger than this would indicate the server is applying hidden fees, rounding errors, or incorrect multipliers. Mines' API exposes two multiplier fields: multiplier is the raw ratio C(25,k)/C(25−m,k) without the edge applied, and no_house_edge_multiplier is the actual payout multiplier with the 0.999 edge factor applied. The payout formula uses the latter.
// Step 7: Payout Mathfor (const b of bets) { if (b.response.outcome === 'win') { const amt = parseFloat(b.request.amount); const nhe = parseFloat(b.response.no_house_edge_multiplier ?? '0'); const won = parseFloat(b.response.amount_won); if (Math.abs(amt * nhe - won) > 1e-8) payoutErrors++; } else { if (b.response.amount_won !== '0') payoutErrors++; }}amount_won = amount × no_house_edge_multiplier within tolerance 1e-8. Zero mismatches. Payout math is exact. Phase E multi-reveal cash-outs additionally verified by Step 23 (133 cash-outs across k ∈ {2,3,5}).3.5Multiplier Formula Verification (Step 8)
Mines does not use a lookup table — the payout multiplier is derived algebraically from the combinatorial ratio C(25,k) / C(25−m,k) × 0.999. Step 8 verifies that every observed no_house_edge_multiplier matches the formula with k=1 (the primary dataset's reveal count) and every multiplier matches the un-edged ratio 25/(25−m). Step 20 extends this to k ∈ {1..5} for Phase E multi-reveal bets.
| Metric | Count |
|---|---|
| Total winning bets checked | 2,795 |
| Matching formula at k=1 | 2,795 |
| Mismatches | 0 |
| Multi-reveal steps verified (Step 20) | 795 across k ∈ {1,2,3,4,5} |
// Step 8: Multiplier Formula (C(25,1)/C(25-m,1) × edge)for (const b of bets) { if (b.response.outcome !== 'win') continue; const m = b.request.mines_count; const expNoEdge = theoreticalMultiplier(m, 1, 0); // raw ratio const actNoEdge = parseFloat(b.response.multiplier); if (Math.abs(expNoEdge - actNoEdge) > 1e-6) multErrors++; const expEdge = theoreticalMultiplier(m, 1, 0.001); // with 0.1% edge const actEdge = parseFloat(b.response.no_house_edge_multiplier ?? '0'); if (Math.abs(expEdge - actEdge) > 1e-6) multErrors++;}C(25,k) / C(25−m,k) × 0.999. 24/24 mine-count configurations verified at k=1; 795 multi-reveal steps verified at k > 1 (Step 20). Flat 0.1% house edge across all configs — no scaling edge.3.6Win Condition Verification (Step 9)
In Mines, the win condition is purely structural: the bet wins if and only if the revealed tile is not in the mine set. Step 9 verifies this for every bet — for each of the 6,500 primary bets we checked that outcome === 'win' matches revealed_positions[0] ∉ mines_positions. For Phase E's multi-reveal bets, Step 22 extends the check to reveal positions in {3, 7, 11, 17, 23} — not just tile 0 — confirming the win condition is position-independent.
| Scope | Coverage | Verified |
|---|---|---|
| Primary dataset (Phase A–D) | 6,500 bets, tile 0 reveals | 6,500 / 6,500 |
| Phase E5 reveal-position independence | 50 bets, tiles {3, 7, 11, 17, 23} | 50 / 50 |
| Multi-reveal win condition (Phase E, Step 21) | 550 multi-reveal bets | 550 / 550 |
3.7Phase C — Bet-Size Equivalence (Step 10)
Phase C placed 200 bets at $10 on mines=12 — the same configuration tested at $0.01 elsewhere in the dataset. All 200 mine layouts were recomputed correctly from revealed seeds. All 105 winning bets' multipliers matched the same C(25,k) / C(25−m,k) × 0.999 formula applied to $0.01 bets in Phase A. Equivalence is proven deterministically — 200/200 exact mine-set matches. Phase C used 4 distinct client seeds to confirm client-seed independence at the elevated stake.
3.8Worked Example — Full Parity Verification
Real bet from Phase E2 — Round ID 25754001, mines=5, nonce 0, 3 safe reveals cashed out. Verified from mines-phaseE-550bets.json:
serverSeed = 5c0cb6589645e00abebd80b697cb739f2c4883a632fc10bdf4122c1e02ef0e22
clientSeed = BFkh4hKDVgdKYmrl
nonce = 0
mines_count = 5
reveals = [0, 1, 2]
cashed_out = true
reached_k = 3| Step | Process | Output |
|---|---|---|
| 1 | Backward Fisher-Yates: 24 HMAC-SHA256 calls | Shuffled positions array |
| 2 | positions[0..4] (sorted) → mine SET | {4, 6, 9, 13, 19} |
| 3 | Player reveals tiles 0, 1, 2 — none in mine set | 3 safe reveals (win) |
| 4 | Multiplier chain: C(25,k) / C(25−5,k) for k=1,2,3 (edge applied at cash-out) | 1.2500× → 1.5789× → 2.0175× → payout mult 2.0155× |
| 5 | Cash-out payout: 0.01 × 2.015526 | 0.020155 ✅ |
// computeMinePositions("5c0cb658...", "BFkh4hKDVgdKYmrl", 0, 5)// 24 Fisher-Yates steps → mine SET = {4, 6, 9, 13, 19}//// Reveal chain (player reveals tiles 0, 1, 2 — all safe).// Live reveal_steps multipliers are the raw combinatorial ratio (no edge):// k=1: mult = C(25,1) / C(20,1) = 25/20 = 1.250000×// k=2: mult = C(25,2) / C(20,2) = (25·24)/(20·19) = 1.578947×// k=3: mult = C(25,3) / C(20,3) = (25·24·23)/(20·19·18) = 2.017544×//// Cash-out at k=3 applies the 0.1% house edge:// no_house_edge_multiplier = 2.017544 × 0.999 = 2.015526// Payout: 0.01 × 2.015526 = 0.020155// Live amount_won: 0.020155263... ✅Technical Evidence & Verification5 sections
3.9Evidence Coverage Summary13 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Mine-set recomputation (Step 5) | 6,500 / 6,500 bets — full mine SET verified | Pass |
| Payout math (Step 7) | 6,500 / 6,500 bets (exact to 8 decimal places) | Pass |
| Multiplier formula integrity (Step 8) | 2,795 winning bets — formula C(25,1)/C(25−m,1)×0.999 matches | Pass |
| Win condition (Step 9) | 6,500 / 6,500 bets — outcome=win iff revealed ∉ mines | Pass |
| Phase C bet-size invariance (Step 10) | 200 / 200 at $10 | Pass |
| Config completeness (Step 11) | All 24 mine counts covered | Pass |
| House edge audit (Step 12) | 6,500 / 6,500 bets — effective_edge field is not a game-logic input; constant at 0.1, mine positions and payouts derive from the seed pair and multiplier formula, not the field | Pass |
| Phase D client-seed variation (Step 17) | 500 bets, 10 custom client seeds — 500 / 500 match | Pass |
| Multi-reveal multiplier chain (Step 20) | 795 reveal steps across k ∈ {1..5} | Pass |
| Mine-set invariance (Step 21) | 550 multi-reveal bets — mine layout fixed at round start | Pass |
| Reveal-position independence (Step 22) | 50 bets, tiles {3,7,11,17,23} | Pass |
| Cash-out payout correctness (Step 23) | 133 cash-outs across k ∈ {2,3,5} | Pass |
| Multi-phase coverage | 5 phases: A (4,800) + B (1,000) + C (200) + D (500) + E (550) | Pass |
3.10Code References5 source files
| File | Purpose |
|---|---|
| tests/steps/determinism.ts | Step 5: Mine position recomputation |
| tests/steps/payouts.ts | Steps 7–12: Payout math, multiplier formula, win condition, Phase C equivalence, config completeness, house edge |
| tests/steps/dataset.ts | Steps 13–17: Phase labels, dataset hash, epoch size, anti-circularity, Phase D client-seed variation |
| tests/steps/multireveal.ts | Steps 20–23: Multi-reveal chain, mine-set invariance, reveal-position independence, cash-out payout |
| src/rng.ts | HMAC-SHA256 backward Fisher-Yates, theoreticalMultiplier formula |
3.11Datasets UsedSource artifacts
3.12Verified Invariants11 invariants
| Invariant | Result |
|---|---|
| Computed mine SET matches live mines_positions for all 7,050 bets | Pass |
| amount_won = amount × no_house_edge_multiplier, exact to 8 decimal places for all 7,050 bets | Pass |
| Every multiplier matches C(25,k) / C(25−m,k) × 0.999 for all 24 configurations and all k ∈ {1..5} | Pass |
| Win condition holds: outcome=win iff revealed tile ∉ mines for all 7,050 bets | Pass |
| Phase C ($10) produces identical mine sets to $0.01 bets at mines=12 | Pass |
| Phase C multipliers match the $0.01 formula (same config, different bet size) | Pass |
| No hidden inputs beyond (serverSeed, clientSeed, nonce, mineCount) | Pass |
| All 24 mine-count configurations (1–24) present in live data | Pass |
| Phase D: 500/500 mine sets verified across 10 custom pfaudit client seeds | Pass |
| Mine set fixed at round start — unchanged by reveal sequence (Phase E, 550 bets) | Pass |
| Cash-out payouts correct at k > 1 — 133 cash-outs across k ∈ {2, 3, 5} | Pass |
3.13Reproduction InstructionsRun locally
git clone https://github.com/ProvablyFair-org/duel-mines.gitcd duel-mines && npm installnpm run verify# Expected output: Steps 5, 7–12, 17, 20–23 all PASSS3-related steps:
[PASS] Step 5 — Mine Position Recomputation
[PASS] Step 7 — Payout Math
[PASS] Step 8 — Multiplier Formula (C(25,1)/C(25-m,1) × edge)
[PASS] Step 9 — Win Condition (tile not in mines)
[PASS] Step 10 — Phase C Bet-Size Invariance ($10 bets)
[PASS] Step 11 — Config Completeness (24 mine counts)
[PASS] Step 12 — House Edge Audit (effective_edge = 0.1%)
[PASS] Step 17 — Phase D — Client Seed Variation
[PASS] Step 20 — Multi-Reveal Multiplier Chain
[PASS] Step 21 — Mine-Set Invariance Across Reveals
[PASS] Step 22 — Reveal-Position Independence (Phase E5)
[PASS] Step 23 — Cash-Out Payout Correctness (k>1)RTP & Payout Logic
Is the house edge what the casino claims?
This section mathematically verifies that the flat 0.1% house edge is exactly what's advertised across all 24 mine-count configurations. 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 produce exactly 0.999, independent of any casino-supplied probability data. We then confirm it against 24 million simulated rounds and test whether the casino pre-selected favourable seeds.
What we verified
- House edge is exactly 0.1% — flat across all 24 mine-count configurations and all bet sizes
- RTP proven from first principles: winChance × multiplier = 0.999 for every configuration — by algebraic cancellation, not summation
- 24M-round simulation converges on theoretical RTP (mean 99.904%)
- Cherry-pick detection: 130 casino seeds tested — no evidence of seed pre-selection
- Bet amount does not influence mine placement — confirmed at $0.01 and $10
What this means for you
- The house edge on Mines is a flat 0.1%, the same regardless of which configuration or bet size you choose
- The RTP proof is derived independently — it doesn't rely on trusting the casino
- The casino's seeds show no evidence of being chosen to produce favourable early outcomes
- Your bet amount doesn't affect where the mines are placed
- No scaling house edge — Mines' margin is uniform across all 24 configurations
- House edge is exactly 0.1% — flat across all 24 mine-count configurations and all bet sizes
- RTP proven from first principles: winChance × multiplier = 0.999 for every configuration — by algebraic cancellation, not summation
- 24M-round simulation converges on theoretical RTP (mean 99.904%)
- Cherry-pick detection: 130 casino seeds tested — no evidence of seed pre-selection
- Bet amount does not influence mine placement — confirmed at $0.01 and $10
- The house edge on Mines is a flat 0.1%, the same regardless of which configuration or bet size you choose
- The RTP proof is derived independently — it doesn't rely on trusting the casino
- The casino's seeds show no evidence of being chosen to produce favourable early outcomes
- Your bet amount doesn't affect where the mines are placed
- No scaling house edge — Mines' margin is uniform across all 24 configurations
Anti-circularityPass
House edge auditPass
× 0.999 factor in the multiplier formula 25/(25−m) × 0.999; flat across all bet sizesSimulated RTP (Pass 1)Pass
Cherry-pick detection (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
Config completenessPass
The 99.9% RTP is proven mathematically from combinatorial cancellation — winChance × multiplier = 0.999 for all 24 mine-count configurations, and the identity extends unchanged to any reveal count k. This is a first-principles proof, not a statistical estimate. 24M simulated rounds and cherry-pick detection confirm no anomalies. The house edge is flat at 0.1% — no scaling structure.
| Check | Result | Evidence |
|---|---|---|
| Anti-circularity | Pass | winChance × payoutMult = 0.999 for all 300 (mine count, reveals) combinations — proven by algebraic cancellation of C(25−m,k)/C(25,k) × C(25,k)/C(25−m,k) × 0.999, no casino data used |
| House edge audit | Pass | Flat 0.1% house edge across all 24 mine-count configurations — derived from × 0.999 factor in the multiplier formula 25/(25−m) × 0.999; flat across all bet sizes |
| Simulated RTP (Pass 1) | Pass | 24M rounds, avg RTP = 99.904%, 0/24 chi-squared failures, 0/24 serial independence failures |
| Cherry-pick detection (Pass 2) | Pass | 130 casino seeds tested at mines=3 × 10K nonces — Test A: 1/130 fails (expected), Test B: 7 cherry-pick flags, 0 broad — no evidence of seed pre-selection |
| Bet-size invariance | Pass | Bet amount is not an input to the RNG — same mine-set distribution at $0.01 and $10. Tested in Phase C (200/200) |
| Multiplier formula | Pass | C(25,k) / C(25−m,k) × 0.999 independently verified for all 24 configs at k=1 and all 795 multi-reveal steps at k ∈ {1..5} |
| Config completeness | Pass | All 24 mine-count configurations (1–24) covered |
Anti-circularityPass
House edge auditPass
× 0.999 factor in the multiplier formula 25/(25−m) × 0.999; flat across all bet sizesSimulated RTP (Pass 1)Pass
Cherry-pick detection (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
Config completenessPass
The 99.9% RTP is proven mathematically from combinatorial cancellation — winChance × multiplier = 0.999 for all 24 mine-count configurations, and the identity extends unchanged to any reveal count k. This is a first-principles proof, not a statistical estimate. 24M simulated rounds and cherry-pick detection confirm no anomalies. The house edge is flat at 0.1% — no scaling structure.
How It Works — RTP & Payout Logic8 sections
4.1Anti-Circularity Proof (Step 16)
The anti-circularity proof establishes the 99.9% 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:
| Component | Formula | Source |
|---|---|---|
| Win chance at reveal k | winChance(m, k) = C(25−m, k) / C(25, k) | Combinatorial — pure math, not from casino |
| Payout multiplier at reveal k | multiplier(m, k) = C(25, k) / C(25−m, k) × 0.999 | Derived from the same combinatorial ratio |
| RTP computation | winChance × multiplier (no summation — these are the only two terms that matter) | Independent probability × observed multiplier |
| Result | = 0.999 for all 24 mine counts and all valid k (300 combinations) | First-principles proof by exact cancellation |
| Mines (m) | winChance | Multiplier (with edge) | winChance × Multiplier | RTP |
|---|---|---|---|---|
| 1 | 24/25 = 0.960000 | 25/24 × 0.999 = 1.040625 | 0.999000000 | 99.9% |
| 3 | 22/25 = 0.880000 | 25/22 × 0.999 = 1.135227 | 0.999000000 | 99.9% |
| 5 | 20/25 = 0.800000 | 25/20 × 0.999 = 1.248750 | 0.999000000 | 99.9% |
| 12 | 13/25 = 0.520000 | 25/13 × 0.999 = 1.921154 | 0.999000000 | 99.9% |
| 20 | 5/25 = 0.200000 | 25/5 × 0.999 = 4.995000 | 0.999000000 | 99.9% |
| 24 | 1/25 = 0.040000 | 25/1 × 0.999 = 24.975000 | 0.999000000 | 99.9% |
Anti-Circularity Verification:
// Step 16: Probability Independence — Anti-Circularity// For every (mine count m ∈ [1,24], reveals k ∈ [1, 25−m]):// reach = C(25−m,k)/C(25,k) mult = C(25,k)/C(25−m,k) × 0.999// RTP = reach × mult = 0.999 (algebraic cancellation, exact)// 300 combinations. Independent derivation: no casino-supplied data used.let maxDeviation = 0;for (let m = 1; m <= 24; m++) { for (let k = 1; k <= 25 - m; k++) { const reach = comb(25 - m, k) / comb(25, k); // C(25−m,k)/C(25,k) const mult = comb(25, k) / comb(25 - m, k) * 0.999; // C(25,k)/C(25−m,k) × 0.999 const dev = Math.abs(reach * mult - 0.999); if (dev > maxDeviation) { maxDeviation = dev; worstConfig = { m, k }; } }}Why this proof is non-circular: winChance(m, k) and multiplier(m, k) are literally reciprocals of the same combinatorial ratio C(25, k) / C(25−m, k), scaled by the edge factor 0.999. They cancel exactly — no summation, no approximation, no "converges to". The identity holds by inspection for every (m, k) pair where k ≤ 25 − m, which means the 99.9% RTP is preserved regardless of how many tiles a player reveals before cashing out.
4.2House Edge Audit (Step 12)
The house edge is implemented by multiplying the no-edge payout multiplier by 0.999. For each mine count m, the no-edge multiplier is 25/(25−m) (pure probabilistic baseline), and the actual paid multiplier is 25/(25−m) × 0.999. The × 0.999 factor is what produces the flat 0.1% house edge across all 24 mine-count configurations and all bet sizes. Step 8 verifies the formula against every winning bet; Step 7 confirms payouts reconcile to it.
Every bet response also carries an effective_edge field. Per the operator's published mechanism, this field represents the player's net edge after Zero Edge rakeback is applied: bets are tagged 0.1 at settlement and updated to 0 asynchronously once an operator-side rewards queue processes the rakeback. All 6,500 primary bets in the captured dataset read 0.1, captured at settlement time. A sample re-fetch of bets initially captured at 0.1 showed the field had since updated to 0 — consistent with the operator's description of the rakeback mechanism as an asynchronous post-settlement queue. The audit does not verify the rewards transaction layer itself; it certifies the game-engine 0.1% house edge, which comes from the multiplier formula and is independent of the effective_edge field. See the audit scope exclusions for the rakeback layer.
25/(25−m) × 0.999 against every winning bet (Step 8), (ii) reconciling payouts to the formula (Step 7), and (iii) confirming the effective_edge field is not a game-logic input — it is constant at 0.1 across all 6,500 primary bets, and mine positions and payouts derive entirely from the seed pair and the 25/(25−m) × 0.999 multiplier formula, with no dependence on the field (Step 12). Flat across all 24 mine-count configurations — no scaling structure. The operator-side rakeback layer that updates net edge to 0% is outside this audit's scope.4.3Full RTP Table — All 24 Configurations
All 24 mine-count configurations produce the same theoretical RTP of 99.900%. The simulated RTP varies per config due to sampling variance, particularly for high-mine configurations where the rare but large winning multiplier dominates the RTP calculation.
| Config | Theoretical RTP | Simulated RTP | Deviation |
|---|---|---|---|
| mines=1 | 99.900% | 99.929% | +0.029% |
| mines=2 | 99.900% | 99.906% | +0.006% |
| mines=3 | 99.900% | 99.905% | +0.005% |
| mines=4 | 99.900% | 99.946% | +0.046% |
| mines=5 | 99.900% | 99.941% | +0.041% |
| mines=6 | 99.900% | 99.876% | −0.024% |
| mines=7 | 99.900% | 99.812% | −0.088% |
| mines=8 | 99.900% | 99.858% | −0.042% |
| mines=9 | 99.900% | 99.895% | −0.005% |
| mines=10 | 99.900% | 99.863% | −0.037% |
| mines=11 | 99.900% | 99.962% | +0.062% |
| mines=12 | 99.900% | 99.993% | +0.093% |
| mines=13 | 99.900% | 99.929% | +0.029% |
| mines=14 | 99.900% | 100.003% | +0.103% |
| mines=15 | 99.900% | 99.919% | +0.019% |
| mines=16 | 99.900% | 99.812% | −0.088% |
| mines=17 | 99.900% | 99.935% | +0.035% |
| mines=18 | 99.900% | 100.035% | +0.135% |
| mines=19 | 99.900% | 99.758% | −0.142% |
| mines=20 | 99.900% | 99.753% | −0.147% |
| mines=21 | 99.900% | 100.183% | +0.283% |
| mines=22 | 99.900% | 99.893% | −0.007% |
| mines=23 | 99.900% | 99.739% | −0.161% |
| mines=24 | 99.900% | 99.850% | −0.050% |
4.4Simulation Pass 1 — Fresh Seeds (Step 18)
Section 4.1 proves RTP = 99.9% mathematically. But does the game engine actually produce that in practice? To find out, we simulated 24 million rounds locally using the same RNG algorithm and independent auditor-generated seeds. If the simulated payouts converge on 99.9%, the implementation matches the math.
Chi-squared test: Compares observed mine-position frequencies to expected uniform frequencies across the 25 grid positions. 0/24 fail at uncorrected α=0.01. 0/24 fail at Bonferroni α/24 = 0.000417.
Serial independence: Lag-1 autocorrelation measures correlation between consecutive mine-count values. Runs test checks for non-random run structure. 0/24 configs fail either test.
RTP convergence: Avg simulated RTP = 99.904% vs 99.900% theoretical. Deviation of +0.004% is well within expected sampling variance.
// Pass 1 — Fresh random seeds// 1,000,000 rounds × 24 mine-count configs = 24,000,000 total// Per-config pinned seeds for reproducibility (S7)const seedPair = SIM_SEEDS[ci];const keyBuffer = Buffer.from(seedPair.server, 'hex');const clientSeed = seedPair.client;const mult = theoreticalMultiplier(mineCount, 1, 0.001); for (let nonce = 0; nonce < ROUNDS_PER_CONFIG; nonce++) { const mines = computeMinePositionsFromBuffer( keyBuffer, clientSeed, nonce, mineCount, GRID_SIZE, ); for (const pos of mines) positionFreq[pos]++; // chi-squared bins const revealed = nonce % GRID_SIZE; // rotating tile strategy const won = !mines.includes(revealed); if (won) totalPayout += mult;}What is a Monte Carlo simulation? Instead of proving fairness with algebra alone, we simulate millions of real game rounds. Each round computes a mine set from scratch using the same HMAC-SHA256 backward Fisher-Yates pipeline as the live game, reveals a tile, and records whether the bet won and what it paid. We record payouts, then compare the aggregate return to the theoretical 99.9%. If they match, the math holds in practice — not just on paper.
4.5Cherry-Pick Detection — Pass 2 (Step 19)
Could the casino have chosen server seeds that produce worse outcomes for players? Pass 2 takes every server seed the casino actually used in the primary dataset and simulates 10,000 rounds per seed at a representative mine count (m=3) to check whether any of them are statistically biased against players.
Test A — Overall distribution: Does any individual seed produce a win-rate distribution across 10,000 nonces that deviates from the expected 88% binomial? 1/130 fail at α=0.01 — well under the ⌈N×0.01⌉=2 threshold and exactly at the H₀ mean of ~1.3. Binomial p-value for the aggregate: 0.729 (no signal).
Test B — Early vs late window: Does any seed produce statistically better outcomes in the early nonces (0–49, where real players bet) compared to later nonces (50–9,999)? 7/130 flags, all early-only (no seed shows late-window deviation). Threshold is 2×⌈N×0.05⌉=14 flags, so 7 passes with headroom. Binomial p-value for the aggregate: 0.475 (no signal).
| Test | Result |
|---|---|
| Seeds tested | 130 (primary-dataset revealed seeds) |
| Nonces per seed | 10,000 |
| Mine count used | 3 (~88% win chance — above chi-squared minimum) |
| Test A fails (chi² at α=0.01) | 1 / 130 PASS (threshold ≤2; expected ~1.3 under H₀) |
| Test B cherry-pick flags | 7 / 130 PASS (threshold ≤14 = 2×⌈N×0.05⌉; expected ~6.5 under H₀) |
| Broad flags (early AND late deviate) | 0 / 130 PASS (any broad flag = investigate) |
// Step 19: Pass 2 Cherry-Pick Testconst pass2 = sim.pass2_casino_seeds;const N = pass2.seeds_tested;const flags = pass2.test_b_cherry_pick_flags;const testA = pass2.test_a_chi2_fails_at_alpha01;const expCP = Math.ceil(N * 0.05);const threshold = expCP * 2; let broadFlags = 0;let earlyOnly = 0;for (const r of pass2.results) { if (r.cherry_pick_flag) { if (r.earlyP < 0.05 && r.lateP < 0.05) broadFlags++; else earlyOnly++; }} const verdict = flags <= threshold && broadFlags === 0 ? 'PASS' : 'FLAG';4.6Bet-Size Invariance (Step 10)
Phase C placed 200 bets at $10 on mines=12 — the same configuration tested at $0.01 elsewhere in the dataset. All 200 mine layouts were recomputed correctly from revealed seeds using the same HMAC-SHA256 backward Fisher-Yates path. All 105 winning bets' multipliers matched the same C(25,k) / C(25−m,k) × 0.999 formula applied to $0.01 bets. Bet amount is not an input to the RNG and does not affect the mine distribution.
| Metric | Phase A ($0.01) | Phase C ($10) |
|---|---|---|
| Bets | 4,800 (all 24 mine counts) | 200 (mines=12) |
| Bet amount | $0.01 | $10.00 |
| Mine-set recomputation | 4,800 / 4,800 | 200 / 200 |
| Multiplier formula | C(25,k)/C(25−m,k) × 0.999 | C(25,k)/C(25−m,k) × 0.999 |
| RNG path | HMAC-SHA256 Fisher-Yates | HMAC-SHA256 Fisher-Yates |
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. 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. Expect large per-phase deviations; any RTP test at N ≤ 5,000 is underpowered for Mines' variance envelope.
| Item | Value | Context |
|---|---|---|
| Phase A empirical RTP | 100.369% | 4,800 bets at $0.01 (mines=1–24, 200/config) — broadest coverage |
| Phase B empirical RTP | 97.403% | 1,000 bets at $0.01 (mines=24) — max-variance edge case |
| Phase C empirical RTP | 100.861% | 200 bets at $10 (mines=12) — elevated stake |
| Phase D empirical RTP | 111.712% | 500 bets at $0.01 (mines=1–24) — 10 custom client seeds |
| Phase E empirical RTP | 97.804% | 550 multi-reveal bets at $0.01 — mines ∈ {3, 5, 12, 15, mixed} |
4.8Worked Example — Payout Verification
Real bet from Phase A — Round ID 25075415, mines=3, nonce 6. Verified from mines-master-6500bets.json:
serverSeed = 01b20487103f970a94c7d615ba53497605020ee1dc3e9e601261d4088275bbe3
clientSeed = NxcarOV28bkerHJk
nonce = 6
mines_count = 3
revealed = [0]
k = 1Step 1 — Mine computation: computeMinePositions(serverSeed, clientSeed, 6, 3) → 24 Fisher-Yates steps → mine SET = {5, 16, 21}
Step 2 — Win check: Player revealed tile 0. 0 ∉ {5, 16, 21} → win
Step 3 — Multiplier derivation: C(25,1) / C(25−3, 1) × 0.999 = 25/22 × 0.999 = 1.135227×
Step 4 — Payout: 0.01 × 1.135227 = 0.011352
| Step | Process | Output |
|---|---|---|
| 1 | Backward Fisher-Yates: 24 HMAC calls | mine SET = {5, 16, 21} ✅ |
| 2 | Win check: revealed tile ∈ mine set? | 0 ∉ {5,16,21} → win |
| 3 | Multiplier: 25/22 × 0.999 | 1.135227× |
| 4 | RTP check: winChance × multiplier | 22/25 × 1.135227 = 0.999 |
| 5 | Payout: 0.01 × 1.135227 | 0.011352 ✅ |
// mines=3, k=1 — anti-circularity proof// winChance(3, 0) = (25-3)/25 = 22/25 = 0.88// multiplier(3, 1, 0.001) = 25/(25-3) × 0.999 = 25/22 × 0.999 = 1.135227273 // RTP = winChance × multiplier// = 0.88 × 1.135227273// = 0.999000000 (99.9%) ✅ // Algebraic cancellation (exact, not an estimate):// (25-m)/25 × 25/(25-m) × 0.999// = (25-m) × 25 / (25 × (25-m)) × 0.999// = 1 × 0.999// = 0.999Technical Evidence & Verification5 sections
4.9Evidence Coverage Summary7 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Anti-circularity (Step 16) | 24/24 configs — RTP = 99.9% exactly (deviation < 10⁻¹⁶) | Pass |
| House edge audit (Step 12) | Derived from × 0.999 factor in multiplier formula — verified against 6,500 winning bets (Step 8) | Pass |
| Multiplier formula (Step 8) | 2,795 winning bets + 795 multi-reveal steps | Pass |
| Simulation Pass 1 (Step 18) | 24M rounds, 0/24 chi-squared, 0/24 serial | Pass |
| Simulation Pass 2 (Step 19) | 130 seeds, Test A 1/130, Test B 7/130, 0 broad | Pass |
| Bet-size invariance (Step 10) | 200/200 Phase C at $10 | Pass |
| Multi-reveal RTP preservation (Steps 20, 23) | 795 reveal steps + 133 cash-outs across k ∈ {2..5} | Pass |
4.10Code References7 source files
| File | Purpose |
|---|---|
| tests/steps/dataset.ts | Step 16: Anti-circularity proof (algebraic identity) |
| tests/steps/payouts.ts | Steps 8, 10, 12: Multiplier formula, bet-size invariance, house-edge audit |
| tests/steps/simulation.ts | Steps 18–19: Simulation integrity and cherry-pick detection |
| tests/steps/multireveal.ts | Steps 20, 23: Multi-reveal multiplier chain, cash-out payout correctness |
| src/simulate.ts | Monte Carlo simulation (24M rounds, two-pass) |
| src/stats.ts | chiSquaredTest, lag1Autocorrelation, runsTest |
| src/rng.ts | theoreticalMultiplier, winChance — closed-form formulas |
4.11Datasets UsedSource artifacts
4.12Verified Invariants11 invariants
| Invariant | Result |
|---|---|
| winChance × multiplier = 0.999 for all 300 (mine count, reveals) combinations (non-circular) | Pass |
| Identity extends to all valid k > 1 by the same cancellation | Pass |
Flat 0.1% house edge — derived from × 0.999 factor in 25/(25−m) × 0.999; effective_edge field is not a game-logic input — constant at 0.1 across all 6,500 primary bets, no dependence on the field | Pass |
| Formula C(25,k) / C(25−m,k) × 0.999 matches all 2,795 winning-bet multipliers | Pass |
| Simulated RTP average = 99.904% across 24M rounds | Pass |
| 0/24 configs reject at Bonferroni α/24 = 0.000417 | Pass |
| 0 serial independence failures (lag-1 + runs test) | Pass |
| No evidence of seed pre-selection across 130 casino seeds (Pass 2) | Pass |
| Phase C ($10) mine sets match Phase A ($0.01) algorithm | Pass |
| Bet amount absent from RNG input by construction | Pass |
| Multi-reveal RTP preserved at k ∈ {1..5} (795 steps, 133 cash-outs) | Pass |
4.13Reproduction InstructionsRun locally
git clone https://github.com/ProvablyFair-org/duel-mines.gitcd duel-mines && npm installnpm run simulate # 24M simulation + cherry-pick test (~11 min)npm run verify # Steps 8, 10, 12, 16, 18, 19, 20, 23 cover S4cat outputs/simulation-results.jsonS4-related steps:
[PASS] Step 8 — Multiplier Formula (C(25,1)/C(25-m,1) × edge)
[PASS] Step 10 — Phase C Bet-Size Invariance ($10 bets)
[PASS] Step 12 — House Edge Audit (effective_edge = 0.1%)
[PASS] Step 16 — Probability Independence (Anti-Circularity)
[PASS] Step 18 — Simulation Results — Pass 1 Integrity
[PASS] Step 19 — Simulation Results — Pass 2 Cherry-Pick Test
[PASS] Step 20 — Multi-Reveal Multiplier Chain
[PASS] Step 23 — Cash-Out Payout Correctness (k>1)Fairness Integrity Testing
Does the implementation maintain fairness under non-standard conditions?
Sections 1–4 prove the game is mathematically fair. Section 5 proves the implementation maintains integrity under non-standard conditions. We applied 16 fairness integrity tests covering nonce integrity, seed commitment, outcome determinism, cross-player isolation, and payout integrity (including Mines-specific multi-step game-state checks). All 16 tests passed, including one Mines-specific multi-step state integrity test.
What we verified
- Nonce tampering — can the sequence be forced, replayed, or skipped?
- Seed injection — can server or client seed fields be overridden via API?
- Outcome replay — can a completed bet be replayed for duplicate payouts?
- Cross-player isolation — can one player's seeds or outcomes affect another's?
- Payout tampering — can multiplier or payout values be injected client-side?
- Parameter limits — can invalid mine counts or grid sizes be submitted?
- Mines-specific — can the same tile be revealed twice to advance the multiplier?
What this means for you
- Across the 16 tests we ran, no API path allowed outcomes to be altered, replayed, or injected — by player or casino
- Once a bet is placed, the mine layout cannot be changed or replayed
- Each bet is cryptographically unique and isolated
- Your results are independent of every other player
- The server rejects malformed, out-of-range, and duplicate requests
- Nonce tampering — can the sequence be forced, replayed, or skipped?
- Seed injection — can server or client seed fields be overridden via API?
- Outcome replay — can a completed bet be replayed for duplicate payouts?
- Cross-player isolation — can one player's seeds or outcomes affect another's?
- Payout tampering — can multiplier or payout values be injected client-side?
- Parameter limits — can invalid mine counts or grid sizes be submitted?
- Mines-specific — can the same tile be revealed twice to advance the multiplier?
- Across the 16 tests we ran, no API path allowed outcomes to be altered, replayed, or injected — by player or casino
- Once a bet is placed, the mine layout cannot be changed or replayed
- Each bet is cryptographically unique and isolated
- Your results are independent of every other player
- The server rejects malformed, out-of-range, and duplicate requests
Nonce integrityPass
Seed commitment integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
16 fairness integrity tests: all 16 pass, including one Mines-specific multi-step state integrity test.
Category Coverage
| Check | Result | Evidence |
|---|---|---|
| Nonce integrity | Pass | Sequential, server-controlled, no gaps or duplicates across 130 primary epochs |
| Seed commitment integrity | Pass | Locked at bet acceptance, unique per epoch — 130/130 primary epochs verified |
| Outcome determinism | Pass | Identical inputs produce identical mine sets — 7,050/7,050 confirmed including multi-reveal |
| Round & player isolation | Pass | Per-user seeds, serial independence confirmed (0/24 fail in 24M-round simulation) |
| Payout integrity | Pass | Parameter limits enforced (5/5 invalid mineCount rejected); injected payout fields ignored (0 honoured); duplicate-reveal advancement blocked |
Nonce integrityPass
Seed commitment integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
16 fairness integrity tests: all 16 pass, including one Mines-specific multi-step state integrity test.
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. Five categories target specific fairness properties, with Payout Integrity extended to cover Mines' multi-step reveal and cash-out surface. Scope boundary: S5 tests whether fairness guarantees hold under non-standard API interaction. Platform-level implementation testing falls outside standard certification.
| Category | Tests | What It Catches |
|---|---|---|
| Nonce Integrity | 4 | Sequence gaps, server-side nonce manipulation, session continuity |
| Seed Commitment | 5 | Mid-epoch seed changes, seed reuse, predictable seed generation |
| Outcome Determinism | 2 | Non-deterministic outputs, outcome replay, cash-out manipulation |
| Player Isolation | 2 | Cross-round correlation, cross-user outcome dependence |
| Payout Integrity | 3 | Parameter enforcement, server-side computation, duplicate-reveal idempotency |
5.2Severity Framework & Hard Fail Criteria
Findings are classified by severity. Hard fail criteria — any one triggers a NOT PROVABLY FAIR verdict.
| Severity | Meaning | Action |
|---|---|---|
| PASS | Test passed — no issue detected | None |
| N/A | Test not applicable to this game type | None |
| FLAG | Anomaly detected, documented for transparency | Disclosed |
| HARD FAIL | Fairness guarantee cannot be confirmed | Certification blocked until remediated |
| Condition | Consequence |
|---|---|
| Nonce gap or duplicate within epoch | Outcome sequence integrity broken |
| Server seed changed mid-epoch | Commit-reveal guarantee broken |
| Mine-set recomputation mismatch | Undisclosed inputs affecting mine placement |
| Client seed not used in HMAC | Player has no influence on outcomes |
| Mine set changes during reveal sequence | Per-reveal manipulation possible |
| Duplicate reveal advances payout multiplier | Multi-step state corruption |
16 tests · 16 pass
Nonce Integrity4/4
FI-NONCE-001PassEach bet increments the nonce sequentially with no gaps, repeats, or resets — preventing the server from skipping unfavourable outcomes
FI-NONCE-002PassNonce progression is server-controlled — the client cannot inject, skip, or replay a nonce value via the API
S1, Step 4 — server controls nonce assignment. Client bet requests do not include a nonce field.
FI-NONCE-003PassSubmitting an invalid or out-of-sequence nonce does not produce a game outcome — the server rejects the request
API probe — 7 adversarial nonce values (−1, 0, 999999999, 'abc', INT32_MAX, INT32_MAX+1, 9e18) injected into mine-start requests. All 7 silently ignored; server assigned valid sequential nonces. Continuity preserved (baseline 21 → post-test 29); no stack traces leaked.
FI-NONCE-004PassNonce sequence continues correctly after disconnect/reconnect — no reset to zero mid-epoch
Seed Commitment5/5
FI-SEED-001PassEmpty, null, or invalid client seeds are handled deterministically — the server enforces input requirements
API probe — 6/7 invalid client seeds rejected (empty, null, special chars, emoji, CJK, whitespace-only). Oversized seed (>1000 chars) accepted — permissive but safe (long entropy). All Mines bets after each rejection remained functional; no stack traces leaked.
FI-SEED-002PassOnce a bet is accepted, the seed pair is locked for the epoch — no mid-epoch mutation of server or client seed is possible
FI-SEED-003PassEach epoch uses a unique server seed — no seed is reused across epochs or sessions
FI-SEED-004PassNo shared seed pool across users — each player's seeds are independently generated and isolated
API probe — 10 concurrent seed rotations on Account A and 10 on Account B. Zero hash collisions across 20 rotations; all hashes within each account distinct. Full account isolation confirmed.
FI-SEED-005PassServer seeds show no correlation with timestamps, sequential patterns, or other predictable inputs
API probe — 10 rotations spaced 5 seconds apart over ~52 seconds. All 10 server seed hashes distinct; no two shared 6+ leading hex characters. Time-correlated derivation ruled out.
Outcome Determinism2/2
FI-OUTCOME-001PassGiven identical inputs (serverSeed, clientSeed, nonce, mineCount), the game always produces the same mine set — verified across all 7,050 live bets including 550 multi-reveal bets with varied reveal positions
FI-OUTCOME-002PassA completed bet cannot be replayed to generate a duplicate payout — mine set is cryptographically fixed at bet time and cash-out payouts are deterministically bound to the formula
Player Isolation2/2
FI-ISO-001PassRNG state is fully independent across rounds — no carry-over from one bet to the next. Each nonce produces a fresh HMAC-SHA256 computation
FI-ISO-002PassOne player's seeds, nonces, and outcomes cannot be observed or influenced by another player — complete cross-user isolation
Follows from seed uniqueness (FI-SEED-003) — if each user has a unique server seed, outcomes are isolated by construction. Confirmed by FI-SEED-004 — zero hash collisions across 20 cross-account rotations.
Payout Integrity3/3
FI-PAYOUT-001PassGame parameters cannot exceed defined limits — only valid mine counts (1–24) for the 25-tile grid are accepted
API probe — 5/5 invalid minesCount values rejected with HTTP 422 (0, 25, 26, −1, 'abc'). All 3 valid boundary bets (minesCount=1, 3, 24) and recovery bet accepted. No out-of-range values silently coerced.
FI-PAYOUT-002PassMultiplier and payout fields in the API request are ignored — the server computes all values from the combinatorial formula, not from client-supplied values
API probe — 5 injected fields (multiplier='9999', mines_positions, is_win, amount_won, cashout_amount) all silently ignored. Server computed mine positions and payout values independently from the seed pair on every request. noInjectionHonoured=true.
FI-MINES-STATE-002PassRevealing the same tile twice does not advance the payout multiplier — the server must reject duplicate reveals or return an idempotent response (Mines-specific multi-step state integrity)
API probe — game started, safe tile revealed (multiplier 1.0416…), duplicate reveal of same tile rejected with HTTP 400. Multiplier did not advance from first-reveal value; cashout at first-reveal multiplier completed normally. multiplierAdvanced=false.
Technical Evidence & Verification4 sections
5.3Coverage Summary16 checks
| Test ID | Category | Verification Source | Status |
|---|---|---|---|
| FI-NONCE-001 | Nonce | S1, Step 4 (data-driven) | Pass |
| FI-NONCE-002 | Nonce | S1, Step 4 (data-driven) | Pass |
| FI-NONCE-003 | Nonce | API probe | Pass |
| FI-NONCE-004 | Nonce | S1, Step 4 (data-driven) | Pass |
| FI-SEED-001 | Seed | API probe | Pass |
| FI-SEED-002 | Seed | S1, Steps 3 & 6 (data-driven) | Pass |
| FI-SEED-003 | Seed | S1, Step 1 (data-driven) | Pass |
| FI-SEED-004 | Seed | API probe | Pass |
| FI-SEED-005 | Seed | API probe | Pass |
| FI-OUTCOME-001 | Determinism | S3, Steps 5, 21, 22 (data-driven) | Pass |
| FI-OUTCOME-002 | Determinism | S3, Steps 21 & 23 (data-driven) | Pass |
| FI-ISO-001 | Isolation | S2, Step 18 (simulation) | Pass |
| FI-ISO-002 | Isolation | Structural — seed uniqueness | Pass |
| FI-PAYOUT-001 | Payout | API probe | Pass |
| FI-PAYOUT-002 | Payout | API probe | Pass |
| FI-MINES-STATE-002 | Payout | API probe | Pass |
5.4Additional Integrity Evidence (S1–S4)8 records
| Property | Source | Finding |
|---|---|---|
| 130/130 seed hashes verified | S1, Step 1 | Commit-reveal chain intact |
| 134/134 next-seed promotions | S1, Step 2 | Seed rotation chain intact |
| 6,500/6,500 exact parity (primary) + 550/550 Phase E | S3, Steps 5 & 21 | No post-RNG conditional logic, mine set fixed at round start |
| Anti-circularity proven (algebraic) | S4, Step 16 | 0.1% house edge from first principles |
| 7 cherry-pick flags / 130 seeds | S4, Step 19 (Pass 2) | No seed pre-selection bias |
| 99.4% client seed influence (646/650) | S1, Step 6 | Player entropy is genuine |
| 133 cash-outs verified at k ∈ {2,3,5} | S3, Step 23 | Multi-step payout integrity intact |
| 795 reveal steps verified across k ∈ {1..5} | S3, Step 20 | Multi-reveal multiplier chain correct |
5.5Scope & LimitationsScope note
5.6Reproduction InstructionsRun locally
Data-driven tests (9 of 16): Fully reproducible from the open-source repo. These tests run against the captured dataset and produce deterministic results.
API probe tests (7 of 16): Verified by issuing live adversarial requests against the running game. Per-test evidence (HTTP status codes, server-assigned nonces, seed hashes, server-computed multipliers, mine-set decisions) is retained in our private adversarial-testing archive and summarised in the integrity test review document. The testing harness itself is kept private to avoid handing exploit primitives to players.
git clone https://github.com/ProvablyFair-org/duel-mines.gitcd duel-minesnpm installnpm run verifyExpected output (S5-related steps):
[PASS] Step 1 — Seed Hash Integrity → FI-SEED-003
[PASS] Step 3 — Hash Consistency → FI-SEED-002
[PASS] Step 4 — Nonce Audit → FI-NONCE-001, 002, 004
[PASS] Step 5 — Mine Position Recomputation → FI-OUTCOME-001
[PASS] Step 6 — Client Seed Influence → FI-SEED-002
[PASS] Step 18 — Simulation Pass 1 → FI-ISO-001
[PASS] Step 20 — Multi-Reveal Multiplier Chain → FI-OUTCOME-001 (multi-reveal)
[PASS] Step 21 — Mine-Set Invariance → FI-OUTCOME-001, 002
[PASS] Step 22 — Reveal-Position Independence → FI-OUTCOME-001
[PASS] Step 23 — Cash-Out Payout Correctness → FI-OUTCOME-002API Probe Tests (completed):
[PASS] FI-NONCE-003 — Invalid nonce handling → 7/7 invalid nonces ignored (HTTP 200)[PASS] FI-SEED-001 — Invalid client seed handling → 6/7 rejected; oversized accepted[PASS] FI-SEED-004 — Cross-user seed pool check → 0/20 collisions across 2 accounts[PASS] FI-SEED-005 — Seed timing analysis → 10 distinct hashes, no time correlation[PASS] FI-PAYOUT-001 — Invalid mineCount handling → 5/5 boundary violations rejected (HTTP 422)[PASS] FI-PAYOUT-002 — Field injection handling → 0 injections honoured[PASS] FI-MINES-STATE-002 — Duplicate reveal idempotency → multiplier did not advance, HTTP 400 on duplicatePlayer 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 your calculated mine layout matches the game result, the bet was provably fair. This section walks you through the process — and provides an independent verification tool built from the same code used in this audit.
Key principles
- Every Mines outcome can be independently reproduced
- No hidden variables — no private backend data
- If your computed mine SET matches the game result, the bet was provably fair
- Most players can verify directly through the Duel.com fairness UI
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; increments within each epoch)
- Mine Count — the number of mines you chose for the round (1–24)
- Every Mines outcome can be independently reproduced
- No hidden variables — no private backend data
- If your computed mine SET matches the game result, the bet was provably fair
- Most players can verify directly through the Duel.com fairness UI
- Server Seed — revealed after seed rotation (casino entropy)
- Client Seed — your player-controlled seed
- Nonce — the bet number in sequence (ensures uniqueness; increments within each epoch)
- Mine Count — the number of mines you chose for the round (1–24)
Player can access all required inputsPass
Client seed origin transparentPass
Verification process documentedPass
Casino UI exposes verification panelPass
Independent verifier availablePass
Manual recomputation possiblePass
No hidden or proprietary stepsPass
Only 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 platform determines the mine layout using the provably fair algorithm before you reveal any tile.
- 2
Open the Fairness Modal
Open the Provably Fair modal on the game page. You'll see the active client seed, the hashed server seed, and your current nonce. Rotate the seed pair to unlock the revealed plaintext server seed for your previous bets.
- 3
Open Past Bet to Reveal Seed
Open Transactions and click a past Mines round. The per-bet modal shows the revealed plaintext server seed alongside the bet ID, client seed, and nonce.
- 4
Recompute & Confirm
Click Verify in the per-bet modal to open the Provably Fair page with the seeds pre-populated. The page recomputes the mine positions inline — if the mine set matches your live game result, the bet was provably fair.
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 Duel.com.
Open Duel verifierVisual Walkthroughstep-by-step
Choose your mine count (1–24) for the 25-tile grid and place a bet. The platform locks the mine layout using the provably fair backward Fisher-Yates shuffle. The server has already committed to the outcome before you clicked — you just don't know which tiles are mines until you start revealing.

Duel.com Mines — 25-tile grid with mine-count selector. Mine placement is locked before you reveal any tile.
Open the Provably Fair modal on the game page. You'll see the active client seed, the hashed server seed (the casino's pre-commitment), and your current nonce. Below those, you can set the new client seed that will become active after rotation. Click 'Rotate seed' to retire the active seed pair and start a new one — this is what unlocks the revealed plaintext server seed for your previous bets in Step 3.

Provably Fair modal — active client seed, hashed server seed, nonce, customisable new client seed, next pair pre-commitment, and Rotate seed action.
Open Transactions and click a past Mines round. The per-bet modal shows the revealed plaintext server seed alongside the bet ID, client seed, and nonce. The pre-committed hash from Step 2 must match SHA-256 of this revealed seed:
Server Seed (revealed) — plaintext value the casino committed to before your bet. Hash it to confirm it matches the pre-commitment.
Client Seed — your player-controlled entropy input.
Nonce — the sequential bet counter for this seed pair.

Per-bet transaction modal — revealed plaintext server seed, client seed, and nonce for the round.
Click Verify in the per-bet modal to open the Provably Fair page with the seeds pre-populated. The page recomputes the mine positions from the disclosed inputs and renders the Game Result inline as a 25-tile grid. If the recomputed mine set matches your live game result, the bet was provably fair — the casino committed to the outcome before you bet, you contributed entropy via your client seed, and the result is mathematically reproducible by anyone.

Provably Fair page — seeds populated from the per-bet modal, recomputed mine set rendered inline matching the live game result.
Manual Verification (Advanced)6 sections
6.6Why Manual Verification Matters
True provably fair verification means you don't trust any casino-provided tool. Manual verification allows you to run calculations on your own machine, eliminate any possibility of a tampered verifier, and understand exactly how results are generated.
6.7How the Algorithm Works (Plain English)
Before you play, the server locks the mine layout using four ingredients:
- The server's secret seed — committed by publishing its hash before you bet
- Your client seed — generated by your browser, unknown to the server
- The nonce — a counter that makes each bet unique
- The mine count — how many mines you chose for the round (1–24)
These ingredients are combined with HMAC-SHA256 (a cryptographic function) to perform a backward Fisher-Yates shuffle of a 25-tile grid. The first N positions of the shuffled array — where N is your mine count — become the mine positions. Because the server committed to its seed before you bet, and your client seed is generated in your browser, neither party can predict or influence the outcome. Once the shuffle is done, the mine layout is cryptographically locked — revealing tiles in any order, or cashing out at any point, does not change which tiles are mines.
6.8Casino Verifier vs ProvablyFair.org Verifier4 records
Two verification tools are available. Both should produce identical results — if they don't, something has changed.
| Duel.com Verifier | ProvablyFair.org Verifier | |
|---|---|---|
| Source | Casino-controlled | Independent (audit codebase) |
| Access | duel.com/fairness/verify | audit.provablyfair.org/casino/duel/tools/verify-bets |
| Trust model | Requires trusting the casino | Open-source, version-controlled |
| Monitoring | No change detection | Mismatches detected if casino changes logic |
6.9JavaScript Verification Script
Copy and run this in Node.js to verify any Mines bet:
const crypto = require('crypto'); const GRID_SIZE = 25;const MAX_UINT32 = 0xFFFFFFFF; function computeMinePositions(serverSeed, clientSeed, nonce, mineCount) { const key = Buffer.from(serverSeed, 'hex'); const positions = Array.from({ length: GRID_SIZE }, (_, i) => i); for (let i = GRID_SIZE - 1; i > 0; i--) { const range = i + 1; const maxFair = MAX_UINT32 - (MAX_UINT32 % range); let cursor = GRID_SIZE - 1 - i; while (true) { const message = `${clientSeed}:${nonce}:${cursor}`; const hmac = crypto.createHmac('sha256', key).update(message).digest('hex'); let found = false; for (let off = 0; off + 8 <= hmac.length; off += 8) { const value = parseInt(hmac.substring(off, off + 8), 16); if (value < maxFair) { const j = value % range; [positions[i], positions[j]] = [positions[j], positions[i]]; found = true; break; } } if (found) break; cursor++; } } return positions.slice(0, mineCount).sort((a, b) => a - b);} function verifyHash(serverSeed, serverSeedHashed) { const hash = crypto .createHash('sha256') .update(Buffer.from(serverSeed, 'hex')) .digest('hex'); return hash === serverSeedHashed;} // Replace with your valuesconst serverSeed = 'YOUR_SERVER_SEED';const serverSeedHashed = 'YOUR_SERVER_SEED_HASH';const clientSeed = 'YOUR_CLIENT_SEED';const nonce = 0;const mineCount = 3; console.log('Hash check:', verifyHash(serverSeed, serverSeedHashed) ? 'PASS' : 'FAIL');console.log('Mine positions:', computeMinePositions(serverSeed, clientSeed, nonce, mineCount));6.10Python Verification Script
The same verification in Python (standard library only):
import hashlib, hmac GRID_SIZE = 25MAX_UINT32 = 0xFFFFFFFF def compute_mine_positions(server_seed, client_seed, nonce, mine_count): key = bytes.fromhex(server_seed) positions = list(range(GRID_SIZE)) for i in range(GRID_SIZE - 1, 0, -1): range_val = i + 1 max_fair = MAX_UINT32 - (MAX_UINT32 % range_val) cursor = GRID_SIZE - 1 - i while True: message = f'{client_seed}:{nonce}:{cursor}'.encode() h = hmac.new(key, message, hashlib.sha256).hexdigest() found = False for off in range(0, len(h) - 7, 8): value = int(h[off:off+8], 16) if value < max_fair: j = value % range_val positions[i], positions[j] = positions[j], positions[i] found = True break if found: break cursor += 1 return sorted(positions[:mine_count]) def verify_hash(server_seed, server_seed_hashed): computed = hashlib.sha256(bytes.fromhex(server_seed)).hexdigest() return computed == server_seed_hashed # Replace with your valuesserver_seed = 'YOUR_SERVER_SEED'server_seed_hashed = 'YOUR_SERVER_SEED_HASH'client_seed = 'YOUR_CLIENT_SEED'nonce = 0mine_count = 3 print('Hash check:', 'PASS' if verify_hash(server_seed, server_seed_hashed) else 'FAIL')print('Mine positions:', compute_mine_positions(server_seed, client_seed, nonce, mine_count))6.11Evidence Screenshots3 records
| Evidence | Description |
|---|---|
| E02 | Fairness page overview — "What is Provably Fair?" and "How it works" sections |
| E03 | Fairness verification tool — Mines selected, showing game-specific verification inputs (server seed, client seed, nonce, mine count) |
| E11 | Client seed rotation response — server echoes client-submitted seed, does not assign |
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 platform determines the mine layout using the provably fair algorithm before you reveal any tile.
- 2
Open the Fairness Modal
Open the Provably Fair modal on the game page. You'll see the active client seed, the hashed server seed, and your current nonce. Rotate the seed pair to unlock the revealed plaintext server seed for your previous bets.
- 3
Open Past Bet to Reveal Seed
Open Transactions and click a past Mines round. The per-bet modal shows the revealed plaintext server seed alongside the bet ID, client seed, and nonce.
- 4
Recompute & Confirm
Click Verify in the per-bet modal to open the Provably Fair page with the seeds pre-populated. The page recomputes the mine positions inline — if the mine set matches your live game result, the bet was provably fair.
Visual Walkthroughstep-by-step
Choose your mine count (1–24) for the 25-tile grid and place a bet. The platform locks the mine layout using the provably fair backward Fisher-Yates shuffle. The server has already committed to the outcome before you clicked — you just don't know which tiles are mines until you start revealing.

Duel.com Mines — 25-tile grid with mine-count selector. Mine placement is locked before you reveal any tile.
Open the Provably Fair modal on the game page. You'll see the active client seed, the hashed server seed (the casino's pre-commitment), and your current nonce. Below those, you can set the new client seed that will become active after rotation. Click 'Rotate seed' to retire the active seed pair and start a new one — this is what unlocks the revealed plaintext server seed for your previous bets in Step 3.

Provably Fair modal — active client seed, hashed server seed, nonce, customisable new client seed, next pair pre-commitment, and Rotate seed action.
Open Transactions and click a past Mines round. The per-bet modal shows the revealed plaintext server seed alongside the bet ID, client seed, and nonce. The pre-committed hash from Step 2 must match SHA-256 of this revealed seed:
Server Seed (revealed) — plaintext value the casino committed to before your bet. Hash it to confirm it matches the pre-commitment.
Client Seed — your player-controlled entropy input.
Nonce — the sequential bet counter for this seed pair.

Per-bet transaction modal — revealed plaintext server seed, client seed, and nonce for the round.
Click Verify in the per-bet modal to open the Provably Fair page with the seeds pre-populated. The page recomputes the mine positions from the disclosed inputs and renders the Game Result inline as a 25-tile grid. If the recomputed mine set matches your live game result, the bet was provably fair — the casino committed to the outcome before you bet, you contributed entropy via your client seed, and the result is mathematically reproducible by anyone.

Provably Fair page — seeds populated from the per-bet modal, recomputed mine set rendered inline matching the live game result.
Technical evidence
6.6Why Manual Verification Matters
True provably fair verification means you don't trust any casino-provided tool. Manual verification allows you to run calculations on your own machine, eliminate any possibility of a tampered verifier, and understand exactly how results are generated.
6.7How the Algorithm Works (Plain English)
Before you play, the server locks the mine layout using four ingredients:
- The server's secret seed — committed by publishing its hash before you bet
- Your client seed — generated by your browser, unknown to the server
- The nonce — a counter that makes each bet unique
- The mine count — how many mines you chose for the round (1–24)
These ingredients are combined with HMAC-SHA256 (a cryptographic function) to perform a backward Fisher-Yates shuffle of a 25-tile grid. The first N positions of the shuffled array — where N is your mine count — become the mine positions. Because the server committed to its seed before you bet, and your client seed is generated in your browser, neither party can predict or influence the outcome. Once the shuffle is done, the mine layout is cryptographically locked — revealing tiles in any order, or cashing out at any point, does not change which tiles are mines.
6.8Casino Verifier vs ProvablyFair.org Verifier4 records
Two verification tools are available. Both should produce identical results — if they don't, something has changed.
| Duel.com Verifier | ProvablyFair.org Verifier | |
|---|---|---|
| Source | Casino-controlled | Independent (audit codebase) |
| Access | duel.com/fairness/verify | audit.provablyfair.org/casino/duel/tools/verify-bets |
| Trust model | Requires trusting the casino | Open-source, version-controlled |
| Monitoring | No change detection | Mismatches detected if casino changes logic |
6.9JavaScript Verification Script
Copy and run this in Node.js to verify any Mines bet:
const crypto = require('crypto'); const GRID_SIZE = 25;const MAX_UINT32 = 0xFFFFFFFF; function computeMinePositions(serverSeed, clientSeed, nonce, mineCount) { const key = Buffer.from(serverSeed, 'hex'); const positions = Array.from({ length: GRID_SIZE }, (_, i) => i); for (let i = GRID_SIZE - 1; i > 0; i--) { const range = i + 1; const maxFair = MAX_UINT32 - (MAX_UINT32 % range); let cursor = GRID_SIZE - 1 - i; while (true) { const message = `${clientSeed}:${nonce}:${cursor}`; const hmac = crypto.createHmac('sha256', key).update(message).digest('hex'); let found = false; for (let off = 0; off + 8 <= hmac.length; off += 8) { const value = parseInt(hmac.substring(off, off + 8), 16); if (value < maxFair) { const j = value % range; [positions[i], positions[j]] = [positions[j], positions[i]]; found = true; break; } } if (found) break; cursor++; } } return positions.slice(0, mineCount).sort((a, b) => a - b);} function verifyHash(serverSeed, serverSeedHashed) { const hash = crypto .createHash('sha256') .update(Buffer.from(serverSeed, 'hex')) .digest('hex'); return hash === serverSeedHashed;} // Replace with your valuesconst serverSeed = 'YOUR_SERVER_SEED';const serverSeedHashed = 'YOUR_SERVER_SEED_HASH';const clientSeed = 'YOUR_CLIENT_SEED';const nonce = 0;const mineCount = 3; console.log('Hash check:', verifyHash(serverSeed, serverSeedHashed) ? 'PASS' : 'FAIL');console.log('Mine positions:', computeMinePositions(serverSeed, clientSeed, nonce, mineCount));6.10Python Verification Script
The same verification in Python (standard library only):
import hashlib, hmac GRID_SIZE = 25MAX_UINT32 = 0xFFFFFFFF def compute_mine_positions(server_seed, client_seed, nonce, mine_count): key = bytes.fromhex(server_seed) positions = list(range(GRID_SIZE)) for i in range(GRID_SIZE - 1, 0, -1): range_val = i + 1 max_fair = MAX_UINT32 - (MAX_UINT32 % range_val) cursor = GRID_SIZE - 1 - i while True: message = f'{client_seed}:{nonce}:{cursor}'.encode() h = hmac.new(key, message, hashlib.sha256).hexdigest() found = False for off in range(0, len(h) - 7, 8): value = int(h[off:off+8], 16) if value < max_fair: j = value % range_val positions[i], positions[j] = positions[j], positions[i] found = True break if found: break cursor += 1 return sorted(positions[:mine_count]) def verify_hash(server_seed, server_seed_hashed): computed = hashlib.sha256(bytes.fromhex(server_seed)).hexdigest() return computed == server_seed_hashed # Replace with your valuesserver_seed = 'YOUR_SERVER_SEED'server_seed_hashed = 'YOUR_SERVER_SEED_HASH'client_seed = 'YOUR_CLIENT_SEED'nonce = 0mine_count = 3 print('Hash check:', 'PASS' if verify_hash(server_seed, server_seed_hashed) else 'FAIL')print('Mine positions:', compute_mine_positions(server_seed, client_seed, nonce, mine_count))6.11Evidence Screenshots3 records
| Evidence | Description |
|---|---|
| E02 | Fairness page overview — "What is Provably Fair?" and "How it works" sections |
| E03 | Fairness verification tool — Mines selected, showing game-specific verification inputs (server seed, client seed, nonce, mine count) |
| E11 | Client seed rotation response — server echoes client-submitted seed, does not assign |
Only disclosed inputs are used. Identical inputs always produce identical output.
Reproducibility & Artifacts
Can anyone independently reproduce every finding in this audit?
This section consolidates the open-source repository, datasets, output artifacts, and reproducibility posture of the audit. Every finding, every statistic, every pass/fail result can be independently reproduced by anyone with a computer and an internet connection. The repository is the credential — not this report.
Repository Details
- GitHub: ProvablyFair-org/duel-mines
- Commit: 6927bec239024666a22a4767ec5c28461a857063
- Game: Mines (duel-mines)
- Public Verifier: audit.provablyfair.org/casino/duel/tools/verify-bets
Prerequisites
- Node.js 18+
- npm 8+
- Git
- TypeScript (installed via npm)
- GitHub: ProvablyFair-org/duel-mines
- Commit: 6927bec239024666a22a4767ec5c28461a857063
- Game: Mines (duel-mines)
- Public Verifier: audit.provablyfair.org/casino/duel/tools/verify-bets
- Node.js 18+
- npm 8+
- Git
- TypeScript (installed via npm)
All audit results can be independently reproduced using the pinned commit, datasets, and commands above. The dataset hashes ensure you're running against the same 7,050 bets.
Commands to Reproduce
Installs TypeScript, ts-node, and cryptographic dependencies. npm test runs mocha (20 unit tests), then the 24M-round simulation, then the 23-step verification pipeline.
Simulates 1,000,000 rounds for each of the 24 mine-count configurations (24M total). Runtime ~11 minutes. Output written to outputs/simulation-results.json and outputs/rtp-convergence.html.
Audit Reproducibility Pinning
- Git Commit
- 6927bec239024666a22a4767ec5c28461a857063
- Node Version
- v18+ (tested on v22.x)
- Primary Dataset
- data/mines-master-6500bets.json (6,500 bets, 130 epochs)
- Phase E Dataset
- data/mines-phaseE-550bets.json (550 bets, 12 epochs)
- Primary Dataset Hash (SHA-256)
- 331f74ff98b88d06242d57e806186548612f13c4269eb754ebb571d8a6fc9b20
- Phase E Dataset Hash (SHA-256)
- def563907949db10d584256a13b33102dfa46d33509424f642f803d74cd1b17b
- Audit Date
- April 2026
- Audit ID
- PF-2026-DL04
Output Artifacts5 files generated
| File | Contents | Size |
|---|---|---|
| verification-results.json | Steps 1–23 results, per-step pass/fail with evidence | ~6KB |
| simulation-results.json | 24M-round RTP, chi-squared, cherry-pick detection (Pass 1 + Pass 2) | ~66KB |
| determinism-log.json | Per-bet mine-set recomputation log (7,050 entries, 0 mismatches) | <1KB |
| chi-squared-results.json | Per-config chi-squared statistics and p-values | ~3KB |
| rtp-convergence.html | Interactive RTP convergence chart (self-contained HTML) | ~5KB |
Repository Structure
duel-mines/ ├── src/ │ ├── rng.ts → HMAC-SHA256 backward Fisher-Yates (25-tile grid) │ ├── simulate.ts → Monte Carlo — 1M rounds/config × 24 │ ├── stats.ts → Chi-squared, autocorrelation, runs test │ ├── loader.ts → Dataset loader + SHA-256 hash guard │ └── types.ts → Type definitions ├── tests/ │ ├── verify.ts → 23-step verification pipeline │ ├── steps/ │ │ ├── commitment.ts → Steps 1–4: Commit-reveal integrity + nonce audit │ │ ├── determinism.ts → Steps 5–6: Mine-set recomputation + client seed │ │ ├── payouts.ts → Steps 7–12: Payout math + multiplier + Phase C + house edge │ │ ├── dataset.ts → Steps 13–17: Dataset integrity + anti-circularity + Phase D │ │ ├── simulation.ts → Steps 18–19: Simulation integrity + cherry-pick │ │ ├── multireveal.ts → Steps 20–23: Phase E multi-reveal + cash-out verification │ │ ├── statistical.ts → Informational: RTP, serial, chi-squared │ │ └── context.ts → Shared context + pass/fail helpers │ └── mines/ │ └── MinesTests.ts → 20 unit tests (Mocha) ├── data/ │ ├── mines-master-6500bets.json → 6,500 primary bets (4 phases, 130 epochs) │ └── mines-phaseE-550bets.json → 550 multi-reveal bets (12 epochs) ├── outputs/ → Generated by npm test │ ├── verification-results.json → Steps 1–23 pass/fail │ ├── simulation-results.json → 24M rounds, per-config RTP + cherry-pick │ ├── determinism-log.json → Per-bet mine-set recomputation log │ ├── chi-squared-results.json → Distribution test results │ └── rtp-convergence.html → Interactive RTP convergence chart ├── evidence/ │ ├── E01–E11 *.png → Game UI, fairness page, phase captures, seed rotation │ └── client-seed-origin.png → Client seed origin evidence ├── capture/ │ ├── mines-capture.reference.js → Browser bet capture script (primary dataset) │ └── mines-capture-phaseE.reference.js → Browser bet capture script (Phase E multi-reveal) ├── results/ → Merged capture working directory ├── README.md ├── MANIFEST.md ├── package.json ├── tsconfig.json └── .mocharc.yml
Step-to-Section Cross-Reference23 verification steps mapped
| Step | Test Name | Report Section |
|---|---|---|
| 1 | Seed Hash Integrity | S1 |
| 2 | Next-Seed Promotion (Commitment Linkage) | S1 |
| 3 | Hash Consistency Within Epoch | S1 |
| 4 | Nonce Audit | S1, S5 |
| 5 | Mine Position Recomputation | S1, S2, S3 |
| 6 | Client Seed Influence | S1, S2 |
| 7 | Payout Math | S3, S4 |
| 8 | Multiplier Formula (C(25,1)/C(25-m,1) × edge) | S3, S4 |
| 9 | Win Condition (tile not in mines) | S3 |
| 10 | Phase C Bet-Size Invariance ($10 bets) | S3, S4 |
| 11 | Config Completeness (24 mine counts) | S3 |
| 12 | House Edge Audit (effective_edge = 0.1%) | S4 |
| 13 | Phase Labels | S3 |
| 14 | Dataset Hash | S7 |
| 15 | Epoch Size | S1, S3 |
| 16 | Probability Independence (Anti-Circularity) | S4 |
| 17 | Phase D — Client Seed Variation | S1, S2, S3 |
| 18 | Simulation Results — Pass 1 Integrity | S2, S4 |
| 19 | Simulation Results — Pass 2 Cherry-Pick Test | S4 |
| 20 | Multi-Reveal Multiplier Chain | S3 |
| 21 | Mine-Set Invariance Across Reveals | S3, S5 |
| 22 | Reveal-Position Independence (Phase E5) | S3 |
| 23 | Cash-Out Payout Correctness (k>1) | S3, S5 |
reproduce-audit.sh
All audit results can be independently reproduced using the pinned commit, datasets, and commands above. The dataset hashes ensure you're running against the same 7,050 bets.