
Dice
Independent verification, backed by open-source code.
audit and dataset.
- Live Bets Verified
- 6,700
- Parity Rate
- 100%
- Simulated Rounds
- 98M
- 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 6,700 recorded outcomes.
The casino result, reproduced.
- Casino outcome
- 36.01 · Win
- Our reproduction
- 36.01 · Win
- Recorded payout
- $0.0116
- Seed Hash IntegrityPass
- Commitment LinkagePass
- Hash ConsistencyPass
- Nonce AuditPass
- Result RecomputationPass
- Client Seed InfluencePass
- Payout MathPass
- Win ConditionPass
- Multiplier FormulaPass
- Win Chance FormulaPass
- Phase LabelsPass
- Dataset HashPass
- Anti-CircularityPass
- Epoch SizePass
- Client Seed VariationPass
- Simulation Pass 1Pass
- Cherry-Pick DetectionPass
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: 17 verification steps, 98M simulated rounds, 6,700 live bets re-verified.
This audit independently validates the Dice 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 6,700 real bets across 134 seed pairs 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.922% simulated (98M) · 0.1% flat house edge |
| Live ↔︎ Verifier Parity | Pass | 100% — 6,700 / 6,700 bets matched |
| Commit-Reveal System | Pass | SHA-256 verified, 134 / 134 revealed seeds — commitment chain intact across all rotations |
| Client Seed | Pass | Browser-generated + player customizable — server commits before client seed is known |
| RNG Analysis | Pass | HMAC-SHA256 with RANGE=10001 rejection sampling — unbiased integer [0, 10000] |
| Payout Logic | Pass | All 6,700 payouts verified — win condition, multiplier, and win chance exact to 14 decimal places |
| Anti-Circularity | Pass | Live multipliers and win chances match independently derived formulas on all 6,700 bets (Steps 9–10); sampling-uniformity precondition gated (Step 13) |
| Integrity Checks | Pass | 15 standard fairness integrity tests — 14 pass, 1 N/A (single-step game) |
| Determinism | Pass | Full reproducibility confirmed |
Overall StatusPass
RTP VerifiedPass
Live ↔︎ Verifier ParityPass
Commit-Reveal SystemPass
Client SeedPass
RNG AnalysisPass
Payout LogicPass
Anti-CircularityPass
Integrity ChecksPass
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
- Dice results are computed via HMAC-SHA256 with rejection sampling (RANGE=10001)
- Results are reproducible from server seed, client seed, and nonce
- Payout logic matches the published formulas — win condition, multiplier, and win chance verified to machine precision
- Theoretical RTP is 99.9% across all targets (flat 0.1% house edge)
- Bet amount does not influence the RNG or dice result
- 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 (0–49), seed pair rotation |
| RNG Analysis | HMAC-SHA256 rejection sampling algorithm verification, bias analysis, external entropy assessment |
| Payout Logic | Win condition accuracy, multiplier formula, win chance formula, bet-size invariance (Phase C) |
| Live Parity | Independent result recomputation vs live game results |
| RTP Validation | Anti-circularity via live formula verification (Steps 9–10) with sampling-uniformity gate (Step 13), simulated RTP (98M rounds), cherry-pick detection (Pass 2) |
| Fairness Integrity | Standard integrity matrix — 15 tests across nonce, seed commitment, determinism, isolation, and payout enforcement |
What Audit Guarantees
- Outcomes are deterministic and reproducible from the recorded inputs
- Live game results match independent recomputation for the verified sample (6,700 / 6,700)
- Result distribution follows uniform [0, 10000] with unbiased rejection sampling
- RTP is proven analytically: win_chance × multiplier = 0.999 for all targets
- Client seed is a genuine, browser-generated input that materially influences results (100% result change rate)
- The house edge is 0.1% flat across all targets
- All standard fairness integrity checks passed at audit time
- House edge mechanism is transparent — RANGE=10001 produces a flat 0.1% edge for every possible target
References
Dice — Game Rules7 sections▶
Dice is a variable-odds betting game: pick a target and a direction (Roll Over or Roll Under), and win if the result lands on your side. Higher risk means lower win probability and higher payouts.
Game Rules
Dice is a single-outcome betting game. The player chooses a target value and a direction — Roll Over (win if the result is greater than the target) or Roll Under (win if the result is less than the target). The result is an integer drawn from a uniform distribution over [0, 10000] — 10,001 equally probable outcomes — determined by a single HMAC-SHA256 computation with bias-free rejection sampling. The further the target is from 50, the lower the win probability and the higher the payout.
How to Play
1. Choose direction — Pick Roll Over (win if result is greater than target) or Roll Under (win if result is less than target). Toggling direction mirrors the win zone on the slider.
2. Choose target — Select a target value. The game UI displays this as 'Roll Over' or 'Roll Under' from 2.00 to 99.98 (API range: 200–9998). The further the target is from 50, the lower the win probability and the higher the payout.
3. Enter bet amount — Choose how much to wager.
4. Place the bet — The platform generates a dice result from the committed server seed, your client seed, and the current nonce.
5. Outcome — If the result is on your chosen side of the target, you win. Payout = bet amount × multiplier.
Win Conditions
The win condition in Dice is a simple threshold comparison against the chosen direction.
| Direction | Win Condition | Example |
|---|---|---|
| Roll Over | result > target | target=5000, result=7234 → Win at 1.998× |
| Roll Under | result < target | target=5000, result=3100 → Win at 1.998× |
Risk vs Reward
The core mechanic of Dice is the tradeoff between target distance and payout size.
- Target controls probability — targets close to 50 mean ~50% win chance and ~2× payout; targets far from 50 (in either direction) mean lower win chance and higher payouts
- House edge is flat — every target and direction has the same theoretical RTP (99.9%) and house edge (0.1%)
- Variance scales with distance from 50 — a 2% win chance / ~50× payout setting (Roll Over 98.00 or Roll Under 2.00) is far more volatile than a 80% / ~1.25× setting (Roll Over 20.00 or Roll Under 80.00)
Parameters
| Parameter | Value | Notes |
|---|---|---|
| Target | 200–9998 | Platform-enforced range; UI displays as "Roll Over" 2.00–99.98 |
| Bet Type | over | Result must exceed target |
| House Edge | 0.1% (flat) | Identical across all targets |
| Theoretical RTP | 99.9% | Verified for all targets |
| RNG Algorithm | HMAC-SHA256 | One HMAC call per bet; key = hex-decoded server seed |
| RANGE | 10001 | Produces result in [0, 10000]; source of 0.1% edge |
Seed Formats
Every Dice bet uses three cryptographic inputs to generate the result.
| Seed Type | Format | Example | Purpose |
|---|---|---|---|
| Server Seed | 64-char hex (32 bytes) | 528555039caf67c0… | Casino-provided randomness |
| Client Seed | Alphanumeric string | bRNvtSn0hp2uVFRe | Player-contributed entropy |
| Nonce | Integer (0–49) | 23 | Ensures uniqueness per bet within epoch |
Win Chance & Multiplier Formulas
Win chance and multiplier are fixed mathematical functions of the target. The 0.999 factor in the multiplier formula is the source of the 0.1% house edge.
win_chance = (10000 − target) / 10001
multiplier = 0.999 / win_chance| Target | Win Chance | Multiplier | RTP |
|---|---|---|---|
| 200 | 97.9902% | 1.0195× | 99.9% |
| 2000 | 79.9920% | 1.2489× | 99.9% |
| 5000 | 49.9950% | 1.9982× | 99.9% |
| 9000 | 9.9990% | 9.9910× | 99.9% |
| 9800 | 1.9998% | 49.9550× | 99.9% |
| 9998 | 0.0200% | 4995.4995× | 99.9% |
Why Provably Fair Matters▶
Traditional online casinos require players to trust that games are fair. Provably fair systems eliminate this trust requirement by allowing players to mathematically verify that outcomes were not manipulated. In a Provably Fair system:
- The casino commits to a result before the player bets
- The player contributes randomness that the casino cannot predict
- Anyone can verify the outcome after the fact
High-Level Overview8 sections▶
Checklist Reference
Based on the ProvablyFair.org Audit Execution Checklist, here are the tests covered under this audit document.
1. Commit-Reveal System & Seed Handling
| Test | Description |
|---|---|
| Server seed commit exists before play | SHA-256 hash shown to player before betting |
| Server seed reveal matches commit | SHA-256(hexDecode(revealed)) = committed hash |
| Client seed control | Player can set/change client seed via rotation UI |
| Nonce increments correctly | Starts at 0, +1 per bet, resets at epoch boundary |
| Full determinism | Same inputs → same result |
2. Randomness & Entropy Model
| Test | Description |
|---|---|
| RNG depends only on seeds + nonce | No external inputs — self-contained HMAC-SHA256 |
| No mixed entropy sources | No timestamps, Math.random, etc. |
| Unbiased mapping | Rejection sampling eliminates modulo bias for RANGE=10001 |
| No state leakage | Each bet uses unique HMAC message (clientSeed:nonce) |
3. Verifier ↔︎ Live Parity
| Test | Description |
|---|---|
| Live outcomes match verifier | 6,700 / 6,700 results recomputed with 0 mismatches |
| Multi-phase verification | Phases A (all targets), B (deep sample), C (bet-size check), D (client seed) |
| Bet-size invariance | $10 bets produce same RNG as $0.01 bets |
4. Game Logic & RTP Validation
| Test | Description |
|---|---|
| Anti-circularity (Steps 9–10 + 13) | Live multipliers and win chances match independently derived formulas; uniformity precondition gated |
| House edge audit | 0.1% flat confirmed across all targets |
| Payout rules correctness | Win amount matches multiplier × bet within 1e-8 |
| Simulated RTP convergence | 98M rounds converge on theoretical 99.9% |
| Cherry-pick detection | 9 flags, binomial p within expected range (not significant) |
5. Fairness Integrity & Player Verification
| Test | Description |
|---|---|
| Player can reproduce results offline | Using seeds + nonce |
| Verifier logic matches live logic | Same HMAC-SHA256 rejection sampling algorithm |
| Verifier publicly accessible | ProvablyFair.org verifier — no login required |
| No reliance on private APIs | Fully client-side verification |
| 15 fairness integrity tests | Nonce, seed commitment, determinism, isolation, payout enforcement |
High-Level Flow
To get an overview of how the process works, here is a high-level breakdown:
1. Player Bets — Selects target value (200–9998) and bet amount
2. Seeds Combined — HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce)
3. RNG Output — 4-byte hash chunks scanned; rejection sampling with RANGE=10001
4. Result Determination — result = chunk % 10001, integer in [0, 10000]
5. Win Check — win if result > target; multiplier = 0.999 / win_chance
6. Payout Result — win_amount = bet_amount × multiplier
Provably Fair Model
Provably fair gambling systems use cryptographic primitives to guarantee the integrity of outcomes. The model relies on three components: a server seed committed via hash before play, a player-controlled client seed, and an incrementing nonce. These inputs are combined using HMAC-SHA256 to produce deterministic, verifiable results. This section documents the global provably fair architecture used by almost all casinos and all relevant games.
Commit-Reveal Model
The Commit-Reveal model is integral to ensuring fairness and transparency in online gambling. This model involves several key phases:
Commit Phase:
Before any bets are placed, the casino generates a random server seed. To prove its authenticity and prevent later manipulation, only the SHA-256 hash of the hex-decoded seed is sent to the player. This ensures that while the player cannot know the seed initially, they can verify it later.
Bet Phase:
The player places their bet. The server combines the server seed with the player's client seed via HMAC-SHA256 to compute the dice result. Each epoch uses the same server seed and client seed pair.
Reveal Phase:
After the epoch ends, the server rotates seeds and reveals the plaintext server seed. The player can now independently verify SHA-256(hexDecode(serverSeed)) = committedHash.
Verify Phase:
Anyone can recompute every bet's result from the revealed server seed, client seed, and nonce using the published HMAC-SHA256 rejection sampling algorithm.
Player-Controlled Client Seed
The player's client seed is generated by the browser and submitted to the server via the seed rotation UI. Players can set their own client seed at any time. This ensures:
- The casino cannot predict the full RNG input
- Players contribute entropy that they control
- Results depend on both parties' inputs
Nonce Lifecycle
The nonce is a counter that increments with each bet within an epoch:
- Starts at 0 for each new server seed
- Increments by exactly 1 per bet
- Never reused within the same seed epoch
- Resets to 0 when the server rotates to a new server seed
Seed Epoch: (serverSeed, clientSeed) Bet 1: nonce = 0 → Result ABet 2: nonce = 1 → Result BBet 3: nonce = 2 → Result C ...[Player rotates seed — epoch complete]Next bet: nonce = 0 → Result X (new seed pair)Determinism Guarantee
Given identical inputs, the output is always identical:
key = hex_decode(serverSeed)message = clientSeed + ":" + noncehash = HMAC-SHA256(key, message)scan 4-byte chunks → reject if ≥ MAX_FAIR (4294959453)result = chunk % 10001 → integer [0, 10000]win = (result > target)Technical Glossary7 categories▶
Core Concepts
| Term | Definition |
|---|---|
| Provably Fair | A cryptographic system that allows players to mathematically verify that game outcomes were not altered. Relies on commit-reveal schemes and deterministic algorithms. |
| Commit-Reveal Protocol | A two-phase process in which the casino commits to a result (by showing its hash) before the player bets, then reveals the actual value after the bet. |
| Determinism | The property that identical inputs always produce identical outputs. Same server seed, client seed, and nonce must always generate the same dice result. |
| Client Seed Origin | The method by which the client seed is generated. Full Pass: browser-generated default + player customizable seed. Conditional Pass: server-assigned default + player customizable seed. Hard Fail: player cannot set own seed. |
Seed System
| Term | Definition |
|---|---|
| Server Seed | A random 32-byte value generated by Duel.com, transmitted as a 64-character hex string. Hashed and shown to players before betting, revealed after epoch rotation. |
| Client Seed | A random value controlled by the player, generated by the browser. Players can change it at any time via the seed rotation UI. |
| Nonce | A sequential counter (0–49) that increments per bet within an epoch. Resets to 0 when the server rotates to a new server seed. |
| Hashed Server Seed | SHA-256(hexDecode(serverSeed)) — the commitment hash shown before betting. After rotation, players verify the revealed seed produces this hash. |
| Epoch | A group of 50 consecutive bets sharing a single (serverSeed, clientSeed) pair. Each bet has a unique nonce (0–49). The epoch ends when seeds are rotated. |
Cryptographic Functions
| Term | Definition |
|---|---|
| HMAC-SHA256 | Hash-based Message Authentication Code using SHA-256. Duel Dice uses HMAC-SHA256 with the hex-decoded server seed as key and clientSeed:nonce as message. One call per bet. |
| Rejection Sampling | A technique to eliminate modulo bias. Hash chunks in the biased zone ([4294959453, 4294967295]) are discarded and the next chunk is tried. Ensures all 10001 outcomes are equally probable. |
| SHA-256 | Secure Hash Algorithm 256-bit. Used for server seed commitment: SHA-256(hexDecode(serverSeed)) = serverSeedHashed. |
| Hex Decoding | Converting a 64-character hex string to 32 raw bytes. Critical for the HMAC key — using the hex string as UTF-8 produces wrong outputs. |
Verification Terms
| Term | Definition |
|---|---|
| Verifier | A tool that independently calculates dice results using provided seeds and nonce. The ProvablyFair.org verifier is built from the audit codebase. |
| Parity | Degree of matching between verifier and live game results. 100% parity = every result matches. This audit: 6,700/6,700 exact match. |
| Anti-Circularity | Proof that RTP derives from first principles, not casino's own claims. For Dice: live multipliers and win chances are verified against independently derived formulas on all 6,700 bets (Steps 9–10), and the sampling-uniformity precondition behind the closed-form EV is gated (Step 13). |
| Cherry-Pick Detection | Test for selective seed deployment. Pass 2 simulation runs 134 revealed seeds × 10,000 nonces, comparing early vs late distributions to detect biased seed selection. |
Game Mechanics
| Term | Definition |
|---|---|
| Target | Player-selected threshold (200–9998). The dice result must exceed this value to win. Higher targets = lower win chance, higher multiplier. |
| Result | The integer output of the RNG, in [0, 10000]. 10,001 equally probable values produced by HMAC-SHA256 with rejection sampling. |
| Win Chance | The probability of winning: (10000 − target) / 10001. Exact for all targets. |
| Multiplier | The payout ratio for a winning bet: 0.999 / win_chance. The 0.999 factor is the source of the 0.1% house edge. |
| RANGE | The number of discrete outcomes (10001). Using 10001 instead of 10000 shifts the denominator by 1, producing a flat 0.1% house edge for every target. |
Audit Terms
| Term | Definition |
|---|---|
| Seed Pair | A (serverSeed, clientSeed) combination used for a sequence of bets. Each pair defines one epoch of 50 bets. |
| Phase | A structured data collection segment. Phase A: random targets (200–9998). Phase B: fixed target 9800. Phase C: $10 bet-size check. Phase D: custom client seed. |
| Scored Step | One of 17 verification checks that contribute to the audit verdict. PASS/FLAG/FAIL. All 17 passed in this audit. |
| Informational Context | Statistical observations reported for transparency but not scored — live-bet chi-squared, serial independence, and empirical RTP are underpowered at sample sizes below ~100K. |
Data Formats
| Term | Definition |
|---|---|
| dice-master-6700bets.json | The 6,700-bet dataset used throughout this audit. Contains all bets and 138 seed entries across four phases. SHA-256: 3550ffca07a6f7825f96cdd4e3d8c3cd57898b5b5238b9739753a754cff056d6. |
| verification-results.json | Output of the 17-step verification suite. Contains all step results, seed hash checks, result recomputation logs, and simulation artifact verification. |
| simulation-results.json | Two-pass simulation output: Pass 1 (98M rounds, fresh seeds) and Pass 2 (134 casino seeds × 10K nonces, cherry-pick detection). |
Seed, Nonce & Determinism
Can the casino change your outcome after you bet?
Every Dice game 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 134 seed pairs
- Dice results are fully determined by the seed inputs before the outcome displays
- Identical inputs always produce the same result — confirmed across all 6,700 bets
- Your client seed is a genuine input — changing it changes the outcome in 100% of sampled tests
What this means for you
- The casino cannot change your dice result 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
- 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 134 seed pairs
- Dice results are fully determined by the seed inputs before the outcome displays
- Identical inputs always produce the same result — confirmed across all 6,700 bets
- Your client seed is a genuine input — changing it changes the outcome in 100% of sampled tests
- The casino cannot change your dice result 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
Server seed committed before betPass
Client seed originPass
Client seed controlPass
Nonce sequencingPass
Hash consistency within seed pairPass
Seed hash integrityPass
Deterministic outputPass
Client seed participationPass
All 134 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 dice result. 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 randomness 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 seed pair, 0 gaps, 0 duplicates across 134 seed pairs (4 capture-retry epochs reconstructed from revealed seed) |
| Hash consistency within seed pair | Pass | server_seed_hashed constant across all bets within each of 134 seed pairs |
| Seed hash integrity | Pass | 134 / 134 revealed seeds hash-verified — commitment chain intact |
| Deterministic output | Pass | Same (serverSeed, clientSeed, nonce) always produces same result — 6,700/6,700 confirmed |
| Client seed participation | Pass | Client seed is a genuine input — changing it changes the dice result in 100% of sampled tests |
Server seed committed before betPass
Client seed originPass
Client seed controlPass
Nonce sequencingPass
Hash consistency within seed pairPass
Seed hash integrityPass
Deterministic outputPass
Client seed participationPass
All 134 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 dice result. 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:
{ "clientSeed": "bRNvtSn0hp2uVFRe", "serverSeedHashed": "91afc2eb50c2c801865a533f7b2a3177fc46bda00fcb500bd290ff73f09e1832", "serverSeed": "528555039caf67c0219c9537cf6626c63cb0bfa384b72571410e522773270eb5", "nonce": 0}Verification:
const crypto = require('crypto'); const serverSeed = "528555039caf67c0219c9537cf6626c63cb0bfa384b72571410e522773270eb5"; const hashedServerSeed = crypto .createHash("sha256") .update(Buffer.from(serverSeed, 'hex')) .digest("hex"); console.log(hashedServerSeed);// Output: 91afc2eb50c2c801865a533f7b2a3177fc46bda00fcb500bd290ff73f09e1832 ✅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)let promoChecked = 0, promoFails = 0;for (const s of seeds) { if (!s.nextSeedPromotion) continue; promoChecked++; // recompute linkage — don't trust a captured flag: const ok = s.nextSeedPromotion.previousNextHash === s.nextSeedPromotion.newActiveHash; if (!ok) promoFails++;}1.3Hash Consistency Within Epoch
For each of the 134 epochs, the server_seed_hashed 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: Hash Consistency Within Epochlet epochsWithMultipleHashes = 0;for (const [, epochBets] of byHash) { const distinctHashes = new Set(epochBets.map(b => b.response.server_seed_hashed)); if (distinctHashes.size !== 1) epochsWithMultipleHashes++;}server_seed_hashed 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 dice result. 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. Phase D specifically tested 10 distinct pfaudit-prefixed client seeds to confirm each is actively used.
07tNQYRfsrSPJ69E0p8VGIwVeOcA4HmwbRNvtSn0hp2uVFRepfauditmnugzvwfpfauditmnuh1gzz
dice-master-6700bets.json1.5Nonce Incrementation
The nonce begins at 0 and increments by 1 for each bet under the same server seed. In Dice, the nonce advances once per bet — each bet uses one nonce value. The nonce resets to 0 when the player rotates their seed, starting a new epoch. Four epochs exhibited a capture-retry nonce pattern (nonce 0–50 instead of 0–49 with one missed nonce); all four were reconstructed from the revealed seed — result recomputation passes for all captured bets in each affected epoch.
// Step 4: Nonce Audit (sequential continuity + capture-retry detection)for (const [hash, epochBets] of byHash) { const sorted = [...epochBets].sort((a, b) => a.response.nonce - b.response.nonce); const shortHash = hash.substring(0, 16); const nonces = sorted.map(b => b.response.nonce); const clientSeeds = new Set(sorted.map(b => b.response.client_seed)); 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)`); } // Check for capture-retry pattern (nonce 50 present, one missed) const hasNonce50 = nonces.includes(50); const missingNonces = Array.from({ length: 51 }, (_, i) => i).filter(i => !nonces.includes(i)); const isRetryPattern = hasNonce50 && missingNonces.length === 1 && nonces.length === 50;}1.6Deterministic Mapping
The RNG algorithm is fully deterministic: given the same server seed, client seed, and nonce, it always produces the exact same dice result. The hash function is HMAC-SHA256 with one call per bet. The server seed is hex-decoded to 32 raw bytes before use as the HMAC key. HMAC message format: clientSeed:nonce. The first 8 hex characters (4 bytes) of the HMAC output are scanned in 4-byte chunks. Each chunk is tested against MAX_FAIR (4294959453) — values below this threshold are accepted and reduced via chunk % 10001 to produce a result in [0, 10000]. Values at or above MAX_FAIR are rejected to eliminate modulo bias.
export function computeResultFromBuffer( keyBuffer: Buffer, clientSeed: string, nonce: number,): number { const message = `${clientSeed}:${nonce}`; const hash = crypto.createHmac('sha256', keyBuffer).update(message).digest('hex'); for (let off = 0; off + 8 <= hash.length; off += 8) { const value = parseInt(hash.substring(off, off + 8), 16); if (value < MAX_FAIR) { return value % RANGE; // 0–10000 integer } } // Probability ~1.24×10⁻⁴⁶ — practically unreachable throw new Error(`RNG hash exhausted for nonce=${nonce}`);}result. Zero mismatches.Real Bet Verified:
// Source: data/dice-master-6700bets.json// Bet ID: 50120996 (Phase A, target 4253, nonce 1)// ✅ VERIFIED — result recomputed from revealed server seed { "serverSeed": "528555039caf67c0219c9537cf6626c63cb0bfa384b72571410e522773270eb5", "serverSeedHashed": "91afc2eb50c2c801865a533f7b2a3177fc46bda00fcb500bd290ff73f09e1832", "clientSeed": "bRNvtSn0hp2uVFRe", "nonce": 1, "target": 4253, "result": 9793, "is_win": true, "multiplier": "1.738472072385592483", "amount_won": "0.017384720723855925"}Verification:
// computeResult("528555039caf67c0...", "bRNvtSn0hp2uVFRe", 1) // message = "bRNvtSn0hp2uVFRe:1"// hash = HMAC-SHA256(hexDecode("528555..."), message)// = 040798187b921e0524484516fd2db284caecc1f6f30a1c341d95d24ab634067f // First 4-byte chunk: 0x04079818 = 67606552// 67606552 < MAX_FAIR (4294959453) → accepted// result = 67606552 % 10001 = 9793 ✅ // Win: 9793 > 4253 = true ✅// Multiplier: 0.999 / ((10000 − 4253) / 10001) = 1.738472... ✅// Payout: 0.01 × 1.738472... = 0.017384... ✅1.7Client Seed Influence
To confirm the client seed is a genuine input to the HMAC-SHA256 computation, 5 bets per epoch (670 total across 134 epochs) were recomputed using a deliberately incorrect client seed (wrong-client-seed-test). All 670 sampled results changed — a 100% influence rate. This is expected: HMAC-SHA256 produces completely different output for different messages, and with RANGE=10001 the probability of a coincidental collision is approximately 1/10001 per bet. Phase D additionally verified 500 bets across 10 distinct pfaudit-prefixed client seeds — all produced independently distributed outcomes.
// Step 6: Client seed influenceconst WRONG_CLIENT = 'wrong-client-seed-test';let tested = 0;let changed = 0;// Sample ~5 bets per epochconst byEpoch = new Map<string, typeof bets>();for (const b of bets) { const arr = byEpoch.get(b.response.server_seed_hashed) ?? []; arr.push(b); byEpoch.set(b.response.server_seed_hashed, arr);}for (const [hash, epochBets] of byEpoch) { const ss = seedMap.get(hash); if (!ss) continue; const key = Buffer.from(ss, 'hex'); for (const b of epochBets.slice(0, 5)) { tested++; const correct = computeResultFromBuffer(key, b.response.client_seed, b.response.nonce); const wrong = computeResultFromBuffer(key, WRONG_CLIENT, b.response.nonce); if (correct !== wrong) changed++; }}Technical Evidence & Verification5 sections
1.8Evidence Coverage Summary6 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Seed hash integrity (Step 1) | 134/134 revealed | Pass |
| Commitment linkage (Step 2) | 138/138 next-seed promotions | Pass |
| Hash consistency (Step 3) | 134/134 epochs | Pass |
| Nonce audit (Step 4) | 0 gaps, 0 duplicates, 4 capture-retry verified | Pass |
| Result recomputation (Step 5) | 6,700/6,700 | Pass |
| Client seed influence (Step 6) | 670/670 sampled (100%) | Pass |
1.9Code References6 source files
| File | Purpose |
|---|---|
| tests/verify.ts | 17-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: Result recomputation and client seed influence |
| src/rng.ts | HMAC-SHA256 dice computation (computeResult, computeResultFromBuffer, verifyHash) |
| src/loader.ts | Dataset loading + seed/bet parsing |
| capture/capture-auto.reference.js | Browser-based data collection script |
1.10Datasets UsedSource artifacts
| Property | Value |
|---|---|
| Source | Live Dice game data from Duel.com |
| Total Records | 6,700 bets across 134 epochs (138 seed entries) |
| SHA-256 | 3550ffca07a6f7825f96cdd4e3d8c3cd57898b5b5238b9739753a754cff056d6 |
Fields used: serverSeed, serverSeedHashed, clientSeed, nonce, target, result, is_win, multiplier, win_chance, amount_won
1.11Verified Invariants9 invariants
| Invariant | Result |
|---|---|
| SHA-256(hexDecode(serverSeed)) = serverSeedHashed for all 134 revealed seeds | Pass |
| Next-seed promotion chain intact for all 138 transitions | Pass |
| server_seed_hashed constant within epoch for all 134 epochs | Pass |
| Zero nonce gaps within any epoch | Pass |
| Zero nonce duplicates within any epoch | Pass |
| Max nonce = 49 (or 50 in 4 capture-retry epochs) | Pass |
| Same inputs produce same result for all 6,700 bets | Pass |
| Wrong client seed changes result in 100% of sampled tests (670/670) | 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-dice.gitcd duel-dice && 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 — Result Recomputation (RNG determinism)
[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 Dice random number generation produces cryptographically sound, unbiased outputs using only the disclosed inputs. The RNG uses HMAC-SHA256 with one call per bet — the result is derived from 4-byte hash chunks via rejection sampling with RANGE=10001, producing a uniform integer in [0, 10000]. 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 bet
- Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
- Rejection sampling with RANGE=10001 eliminates modulo bias — all 10,001 outcomes equally probable
- Result distribution matches theoretical win rates for all 98 target values (confirmed over 98M simulated rounds)
- Consecutive outcomes are statistically independent — no patterns, no streaks
- 100% of outcomes change with a different client seed (670/670 sampled tests)
What this means for you
- Each dice roll is generated fairly and cannot be skewed toward any outcome
- All 10,001 possible results are equally likely — no value is favoured
- No hidden randomness or server-side tricks influence the result
- 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 bet
- Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
- Rejection sampling with RANGE=10001 eliminates modulo bias — all 10,001 outcomes equally probable
- Result distribution matches theoretical win rates for all 98 target values (confirmed over 98M simulated rounds)
- Consecutive outcomes are statistically independent — no patterns, no streaks
- 100% of outcomes change with a different client seed (670/670 sampled tests)
- Each dice roll is generated fairly and cannot be skewed toward any outcome
- All 10,001 possible results are equally likely — no value is favoured
- No hidden randomness or server-side tricks influence the result
- 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
Key encoding verifiedPass
Serial independencePass
Client seed influencePass
The Dice RNG uses only the disclosed inputs, produces uniform result distribution across all target values, and shows no serial dependence across 98M simulated rounds. Rejection sampling ensures all 10,001 outcomes are equally probable. The client seed is a genuine input — 100% of sampled outcomes change with a different seed.
| Check | Result | Evidence |
|---|---|---|
| RNG derived only from disclosed inputs | Pass | HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce) — no hidden entropy |
| Entropy purity | Pass | No timestamps, external APIs, Math.random, or server-side state |
| Algorithm independently implemented | Pass | Independent implementation produces identical results for all 6,700 bets |
| Modulo bias | Pass | Rejection sampling eliminates bias — RANGE=10001 maps uniformly onto [0, 10000] |
| Key encoding verified | Pass | Server seed hex-decoded to bytes (not UTF-8) — confirmed via 6,700-bet recomputation |
| Serial independence | Pass | Lag-1 autocorrelation near zero and runs tests pass across all 98 targets at 1M rounds each |
| Client seed influence | Pass | 100% of sampled outcomes change with an alternate client seed — confirmed across 670 sampled bets |
RNG derived only from disclosed inputsPass
Entropy purityPass
Algorithm independently implementedPass
Modulo biasPass
Key encoding verifiedPass
Serial independencePass
Client seed influencePass
The Dice RNG uses only the disclosed inputs, produces uniform result distribution across all target values, and shows no serial dependence across 98M simulated rounds. Rejection sampling ensures all 10,001 outcomes are equally probable. The client seed is a genuine input — 100% of sampled outcomes change with a different seed.
How It Works — RNG & Entropy Model7 sections
2.1RNG Function Implementation
Each Dice bet resolves with a single HMAC-SHA256 computation. The HMAC key is the server seed hex-decoded to 32 raw bytes. The message is clientSeed:nonce. The 64-character hex output is scanned in 4-byte (8-character) chunks. Each chunk is parsed as a big-endian uint32 and tested against MAX_FAIR (4294959453). Values below this threshold are accepted and reduced via chunk % 10001 to produce a uniform integer in [0, 10000]. Values at or above MAX_FAIR are rejected and the next chunk is tried — this rejection sampling eliminates modulo bias.
| Component | Detail |
|---|---|
| Hash function | HMAC-SHA256 |
| Key | Buffer.from(serverSeed, 'hex') — 32 bytes |
| Message | clientSeed:nonce |
| Extraction | 4-byte (8 hex char) chunks scanned left-to-right |
| Rejection | Values ≥ MAX_FAIR (4294959453) discarded |
| Reduction | uint32 % 10001 → integer [0, 10000] |
| Output range | 0 to 10000 (10,001 equally probable values) |
export function computeResultFromBuffer( keyBuffer: Buffer, clientSeed: string, nonce: number,): number { const message = `${clientSeed}:${nonce}`; const hash = crypto.createHmac('sha256', keyBuffer).update(message).digest('hex'); for (let off = 0; off + 8 <= hash.length; off += 8) { const value = parseInt(hash.substring(off, off + 8), 16); if (value < MAX_FAIR) { return value % RANGE; // 0–10000 integer } } // Probability ~1.24×10⁻⁴⁶ — practically unreachable throw new Error(`RNG hash exhausted for nonce=${nonce}`);}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, 0–49 per epoch) |
How we know: 6,700/6,700 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)
2.3Modulo Bias Analysis
Each bet's result is determined by chunk % 10001 where chunk is a uint32 extracted from the HMAC output. Modulo bias occurs when 2^32 is not evenly divisible by the modulus. For modulus 10001, 2^32 = 4,294,967,296 does not divide evenly — 4294967296 % 10001 = 7843 — so raw modulo would produce a non-uniform distribution. The implementation eliminates this bias via rejection sampling: chunks ≥ MAX_FAIR (4294959453) are discarded and the next 4-byte chunk is tried.
2^32 = 4,294,967,296
MAX_U32 = 4,294,967,295
MAX_U32 % 10001 = 7,842
MAX_FAIR = 4,294,967,295 − 7,842 = 4,294,959,453
Accepted range: [0, 4,294,959,452] → 4,294,959,453 values
4,294,959,453 / 10,001 = 429,453 (exact — zero remainder)
Each of the 10,001 outcomes maps to exactly 429,453 uint32 values.
Rejection probability per chunk: 7,843 / 4,294,967,296 ≈ 0.000183%The probability of all 8 hash chunks being rejected is approximately 1.24×10⁻⁴⁶ — practically unreachable. In practice, the first chunk is always sufficient. All 6,700 bets in the dataset used the first chunk.
2.4RNG Isolation
Each bet uses a unique nonce in the HMAC message (clientSeed:nonce), ensuring per-bet outputs are cryptographically independent. HMAC-SHA256 is a pseudorandom function — knowing one bet's output gives zero information about other bets' outputs. There is no shared state between bets or between epochs.
Evidence: The computeResultFromBuffer 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 (serverSeed, clientSeed, nonce) explicitly and returns a result. Same inputs always produce the same output.
2.5Monte Carlo Simulation (98M Rounds)
A 98,000,000-round Monte Carlo simulation (1,000,000 rounds per target, all 98 targets from 200 to 9900 in steps of 100) verified that the algorithm produces the expected win rate distribution at scale.
| Metric | Value |
|---|---|
| Average simulated RTP | 99.922% |
| Average theoretical RTP | 99.900% |
| Chi-squared (α=0.01) | 98/98 targets pass |
| Bonferroni-corrected (α/98) | 98/98 pass |
| Serial independence | 98/98 pass |
// 98 targets: 200, 300, 400, ..., 9900 (full range in steps of 100)const PASS1_TARGETS = Array.from({ length: 98 }, (_, i) => (i + 2) * 100);const PASS1_ROUNDS_EACH = 1_000_000;const PASS1_SEEDS = 1; // single seed per target, 1M roundsAll 98 targets pass individual chi-squared tests. Serial independence: 0 failures at Bonferroni-corrected threshold across 98M rounds.
Methodology: Per-target pinned seeds for reproducibility. Chi-squared goodness-of-fit on win/loss counts vs theoretical win_chance. Serial independence tested via lag-1 autocorrelation and Wald-Wolfowitz runs test.
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 target in the Pass 1 simulation (1,000,000 rounds per target):
Lag-1 autocorrelation: Measures correlation between consecutive result 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 lagOneAutocorrelation(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 — Bet ID 50121005, target 1831, nonce 2. Verified from dice-master-6700bets.json:
serverSeed = 528555039caf67c0219c9537cf6626c63cb0bfa384b72571410e522773270eb5
clientSeed = bRNvtSn0hp2uVFRe
nonce = 2
target = 1831| Step | Operation | Value |
|---|---|---|
| 1 | Key | hex_decode("528555039caf67c0...") → 32 bytes |
| 2 | Message | "bRNvtSn0hp2uVFRe:2" |
| 3 | HMAC-SHA256 | e5ff26928918c5736fcaa46aea8d9327102d14bb8b502a2a9200252babff96cc |
| 4 | Chunk[0] (offset 0) | 0xe5ff2692 = 3,858,704,018 |
| 5 | Bias check | 3,858,704,018 < 4,294,959,453 → accepted |
| 6 | Result | 3,858,704,018 % 10001 = 8187 |
| 7 | Win check | 8187 > 1831 = true |
| 8 | Multiplier | 0.999 / ((10000 − 1831) / 10001) = 1.2230× |
result = 8187 matches HMAC-SHA256 recomputation exactly. Win: 8187 > 1831 = true. Multiplier: 1.223038×. Payout: 0.01 × 1.223038 = 0.012230.Technical Evidence & Verification5 sections
2.8Evidence Coverage Summary5 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Algorithm implementation (Step 5) | 6,700/6,700 bets | Pass |
| Key encoding (hex vs UTF-8) | Confirmed via recomputation | Pass |
| Modulo bias analysis | Mathematical proof: rejection sampling with RANGE=10001 | Pass |
| Simulation chi-squared (Step 16) | 0/98 targets fail at α=0.01 | Pass |
| Serial independence (Step 16) | 0/98 targets fail (lag-1 + runs test) | Pass |
2.9Code References5 source files
| File | Purpose |
|---|---|
| src/rng.ts | HMAC-SHA256 implementation (computeResult, computeResultFromBuffer) |
| src/simulate.ts | Monte Carlo simulation (98M rounds, two-pass) |
| src/stats.ts | Chi-squared, lag-1 autocorrelation, Wald-Wolfowitz runs test |
| tests/steps/determinism.ts | Steps 5–6: Result recomputation and client seed influence |
| tests/steps/simulation.ts | Steps 16–17: Simulation integrity verification |
2.10Verified Invariants9 invariants
| Invariant | Result |
|---|---|
| HMAC-SHA256 output matches live game for all 6,700 bets | Pass |
| Key is hex-decoded (not UTF-8) — wrong encoding produces wrong results | Pass |
| Rejection sampling eliminates modulo bias for RANGE=10001 | Pass |
| No external entropy sources required for result computation | Pass |
| Win rate matches theoretical for all 98 targets (1M rounds each) | Pass |
| Lag-1 autocorrelation near zero for all 98 targets | Pass |
| Runs test p > 0.01 for all 98 targets | Pass |
| Per-target simulated RTP converges to 99.9% theoretical | Pass |
| Client seed change produces different result in 100% of sampled bets (670/670) | 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-dice.gitcd duel-dice && npm installnpm run simulate # 98M-round simulationnpm run verify # Steps 5, 16 cover S2S2-related steps:
[PASS] Step 5 — Result Recomputation (RNG determinism)
[PASS] Step 16 — 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 dice result 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 dice result verified, not just the payout
- Payout correctness: win_amount = bet × multiplier, exact to 8 decimal places for all 6,700 bets
- All formulas verified: win condition, multiplier, and win chance confirmed to machine precision
- Bet amount is not an input to the RNG — dice result depends only on seeds and nonce
- All four capture phases recomputed identically (target coverage, statistical validation, elevated stake, client seed variation)
What this means for you
- The verifier isn't a simulation — it produces the exact same result 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 you play
- The game engine in production matches the published algorithm exactly
- Every bet independently recomputed from seeds — full dice result verified, not just the payout
- Payout correctness: win_amount = bet × multiplier, exact to 8 decimal places for all 6,700 bets
- All formulas verified: win condition, multiplier, and win chance confirmed to machine precision
- Bet amount is not an input to the RNG — dice result depends only on seeds and nonce
- All four capture phases recomputed identically (target coverage, statistical validation, elevated stake, client seed variation)
- The verifier isn't a simulation — it produces the exact same result 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 you play
- The game engine in production matches the published algorithm exactly
Result recomputationPass
Payout correctnessPass
Win condition integrityPass
Multiplier formula integrityPass
Bet-size independencePass
Multi-phase coveragePass
All 6,700 bets matched the independent verifier exactly — dice results verified across all four capture phases. Payout math correct. Win condition, multiplier formula, and win chance formula all verified to machine precision.
| Check | Result | Evidence |
|---|---|---|
| Result recomputation | Pass | 6,700/6,700 exact match — dice result verified for every bet |
| Payout correctness | Pass | All 6,700 bets: win_amount = bet × multiplier, exact to 8 decimal places |
| Win condition integrity | Pass | All 6,700 bets: is_win = (result > target) with zero errors |
| Multiplier formula integrity | Pass | 0.999 / ((10000 − target) / 10001) verified for all 6,700 bets — zero mismatches |
| Bet-size independence | Pass | Bet amount is absent from the RNG input — dice result depends only on seeds and nonce |
| Multi-phase coverage | Pass | 4 structured phases: target coverage (A), statistical validation (B), elevated stake (C), client seed variation (D) |
Result recomputationPass
Payout correctnessPass
Win condition integrityPass
Multiplier formula integrityPass
Bet-size independencePass
Multi-phase coveragePass
All 6,700 bets matched the independent verifier exactly — dice results verified across all four capture phases. Payout math correct. Win condition, multiplier formula, and win chance formula all verified to machine precision.
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 dice result they experienced during live play.
3.2Four-Phase Collection Design
Data was collected across four structured phases, each designed to test a specific fairness property. The phases are complementary — together they cover target range breadth, statistical depth, bet-size invariance, and client seed verification.
| Phase | Bets | Config | Bet Amount | Purpose |
|---|---|---|---|---|
| A — Target coverage | 5,000 | Random targets (200–9998) | $0.01 | Verify full target range behaves correctly |
| B — Statistical validation | 1,000 | Target 9800 (fixed) | $0.01 | Deep win rate validation on high-target config |
| C — Code-path equivalence | 200 | Target 9700 (fixed) | $10.00 | Confirm bet amount is not in the RNG |
| D — Client seed verification | 500 | Target 9700 (fixed) | $0.01 | Confirm client seed is genuinely used in HMAC computation |
3.3Result Recomputation (Step 5)
For every bet belonging to an epoch with a revealed server seed, the verifier independently computed the dice result using computeResultFromBuffer(key, clientSeed, nonce) and compared it to the server-reported result. The computation uses HMAC-SHA256 with the hex-decoded server seed as key and clientSeed:nonce as message, followed by rejection sampling with RANGE=10001.
// Step 5: Result recomputationlet mismatches = 0;let skipped = 0;for (const b of bets) { const ss = seedMap.get(b.response.server_seed_hashed); if (!ss) { skipped++; continue; } const key = Buffer.from(ss, 'hex'); const computed = computeResultFromBuffer(key, b.response.client_seed, b.response.nonce); if (computed !== b.response.result) mismatches++;}3.4Payout Math (Step 7)
For every bet, the payout was independently verified: for wins, amount_won = amount × multiplier (tolerance 1e-8). For losses, amount_won = 0 (tolerance 1e-12). This confirms the platform applies payouts correctly and does not underpay winning bets or charge losing bets.
// Step 7: Payout mathlet payoutErrors = 0;for (const b of bets) { const mult = parseFloat(b.response.multiplier); const amt = parseFloat(b.request.amount); const won = parseFloat(b.response.amount_won); if (b.response.is_win) { if (Math.abs(amt * mult - won) > 1e-8) payoutErrors++; } else { if (Math.abs(won) > 1e-12) payoutErrors++; }}3.5Win Condition (Step 8)
For every bet, the win condition was independently computed as result > target (strict inequality) and compared against the API-reported is_win field. A result exactly equal to the target is a loss. This strict-greater-than rule is consistent and transparent.
// Step 8: Win conditionlet condErrors = 0;for (const b of bets) { const expected = b.response.result > b.request.target; if (expected !== b.response.is_win) condErrors++;}3.6Multiplier & Win Chance Formulas (Steps 9–10)
For every bet, the multiplier was independently computed as 0.999 / ((10000 − target) / 10001) and the win chance as (10000 − target) / 10001. Both were compared against the API-reported values. The multiplier matched to within 1e-6, and the win chance matched to 14 decimal places — machine precision.
// Step 9: Multiplier formulalet multErrors = 0;for (const b of bets) { const target = b.request.target; const expected = theoreticalMultiplier(target); const actual = parseFloat(b.response.multiplier); if (Math.abs(expected - actual) > 1e-6) multErrors++;} // Step 10: Win chance formulalet wcErrors = 0;for (const b of bets) { const target = b.request.target; const expected = theoreticalWinChance(target); const actual = parseFloat(b.response.win_chance); if (Math.abs(expected - actual) > 1e-14) wcErrors++;}multiplier × win_chance = 0.999 for every target.3.7Phase C — Bet-Size Equivalence
Phase C placed 200 bets at $10 on target 9700 — the same target as Phase D's $0.01 bets. All 200 dice results were recomputed correctly from revealed seeds. All 200 payout multipliers match the formula 0.999 / win_chance. Equivalence is proven deterministically — 200/200 exact result and multiplier matches. The RNG input is HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce) — bet amount does not appear.
3.8Worked Example — Full Parity Verification
Real bet from Phase C — Bet ID 50157310, target 9700, $10 stake, nonce 31. Verified from dice-master-6700bets.json:
serverSeed = b0ec2ba42759cc0368488642ab663ba527255dd0e7fd0dc81ca76883471069ec
clientSeed = 6RCe8mGoswFFojub
nonce = 31
target = 9700
bet amount = $10.00| Step | Process | Output |
|---|---|---|
| 1 | HMAC-SHA256(hexDecode(serverSeed), "6RCe8mGoswFFojub:31") | d5be73c59778355f... |
| 2 | First 4-byte chunk: 0xd5be73c5 | 3,586,028,485 |
| 3 | Rejection check: 3,586,028,485 < 4,294,959,453 | Accepted |
| 4 | result = 3,586,028,485 % 10001 | 9919 — matches live game ✅ |
| 5 | Win check: 9919 > 9700 | true ✅ |
| 6 | Multiplier: 0.999 / ((10000 − 9700) / 10001) | 33.30333× |
| 7 | Payout: $10 × 33.30333 | $333.03 ✅ |
// computeResult("b0ec2ba427...", "6RCe8mGoswFFojub", 31)//// message = "6RCe8mGoswFFojub:31"// hash = HMAC-SHA256(hexDecode("b0ec2ba4..."), message)// = d5be73c59778355f8c21352d82c35e6d025e5706825e3705ff914fdd91173f59//// Chunk[0]: 0xd5be73c5 = 3,586,028,485// 3,586,028,485 < 4,294,959,453 → accepted// result = 3,586,028,485 % 10001 = 9919 ✅//// Win: 9919 > 9700 = true ✅// Multiplier: 0.999 / ((10000 − 9700) / 10001) = 33.30333 ✅// Payout: 10 × 33.30333 = 333.0333 ✅Technical Evidence & Verification5 sections
3.9Evidence Coverage Summary8 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Result recomputation | 6,700 / 6,700 bets — full dice result verified | Pass |
| Payout math | 6,700 / 6,700 bets (exact to 8 decimal places) | Pass |
| Win condition | 6,700 / 6,700 bets — result > target verified | Pass |
| Multiplier formula | 6,700 / 6,700 bets — 0.999 / win_chance verified | Pass |
| Win chance formula | 6,700 / 6,700 bets — exact to 14 decimal places | Pass |
| Phase C result recomputation | 200 / 200 at $10 | Pass |
| Phase C multiplier match | 200 / 200 bets — identical formula to $0.01 bets | Pass |
| Multi-phase coverage | 4 phases: A (5,000) + B (1,000) + C (200) + D (500) | Pass |
3.10Code References4 source files
| File | Purpose |
|---|---|
| tests/steps/determinism.ts | Step 5: Result recomputation |
| tests/steps/payouts.ts | Steps 7–10: Payout math, win condition, multiplier formula, win chance formula |
| tests/steps/dataset.ts | Steps 11–15: Phase labels, dataset hash, sampling uniformity, epoch size, Phase D |
| src/rng.ts | HMAC-SHA256 dice computation + theoretical formula functions |
3.11Datasets UsedSource artifacts
3.12Verified Invariants8 invariants
| Invariant | Result |
|---|---|
| Computed result matches live result for all 6,700 bets | Pass |
| win_amount = bet × multiplier, exact to 8 decimal places for all 6,700 bets | Pass |
| is_win = (result > target) for all 6,700 bets — zero errors | Pass |
| multiplier = 0.999 / ((10000 − target) / 10001) for all 6,700 bets — zero mismatches | Pass |
| win_chance = (10000 − target) / 10001 exact to 14 decimal places for all 6,700 bets | Pass |
| Phase C ($10) produces identical dice results to $0.01 bets — 200/200 verified | Pass |
| No hidden inputs beyond (serverSeed, clientSeed, nonce) | Pass |
| Phase D: 500/500 results verified across 10 custom client seeds | Pass |
3.13Reproduction InstructionsRun locally
git clone https://github.com/ProvablyFair-org/duel-dice.gitcd duel-dice && npm installnpm run verify# Expected output: Steps 5, 7–10 all PASSS3-related steps:
[PASS] Step 5 — Result Recomputation (RNG determinism)
[PASS] Step 7 — Payout Math
[PASS] Step 8 — Win Condition (result > target)
[PASS] Step 9 — Multiplier Formula (0.999 / win_chance)
[PASS] Step 10 — Win Chance Formula ((10000 − target) / 10001)RTP & Payout Logic
Is the house edge what the casino claims?
This section mathematically verifies that the 0.1% house edge is exactly what's advertised across all target values. The key test is anti-circularity: we derive the win chance from the RNG's integer range (uniformity precondition gated in Step 13) and verify the published multiplier formula against all 6,700 live bets (Steps 9–10) — so no casino-supplied probability data enters the computation. We then confirm it against 98 million simulated rounds and test whether the casino pre-selected favourable seeds.
What we verified
- House edge is exactly 0.1% flat — confirmed from the published formulas and independently derived
- RTP proven from first principles: win_chance × multiplier = 0.999 for every target — algebraic identity
- 98M-round simulation converges on theoretical RTP (mean 99.922%)
- Cherry-pick detection: 134 casino seeds tested — no evidence of seed pre-selection
- Bet amount does not influence dice result — confirmed at $0.01 and $10
What this means for you
- The house edge on Dice is a flat 0.1% of every bet, the same regardless of the target you pick
- 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 the dice roll
- The flat 0.1% edge is transparent — implemented via RANGE=10001 in the denominator
- House edge is exactly 0.1% flat — confirmed from the published formulas and independently derived
- RTP proven from first principles: win_chance × multiplier = 0.999 for every target — algebraic identity
- 98M-round simulation converges on theoretical RTP (mean 99.922%)
- Cherry-pick detection: 134 casino seeds tested — no evidence of seed pre-selection
- Bet amount does not influence dice result — confirmed at $0.01 and $10
- The house edge on Dice is a flat 0.1% of every bet, the same regardless of the target you pick
- 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 the dice roll
- The flat 0.1% edge is transparent — implemented via RANGE=10001 in the denominator
Anti-circularityPass
House edge auditPass
Simulated RTP (Pass 1)Pass
Cherry-pick detection (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
Win chance formulaPass
The 99.9% RTP is proven mathematically — win_chance × multiplier = 0.999 is an algebraic identity for every target. This is a first-principles proof, not a statistical estimate. 98M simulated rounds and cherry-pick detection confirm no anomalies. The 0.1% house edge is flat and transparent.
| Check | Result | Evidence |
|---|---|---|
| Anti-circularity | Pass | Live values match independent formulas on all 6,700 bets (Steps 9–10); EV = 0.999 follows in closed form with the uniformity precondition gated (Step 13) |
| House edge audit | Pass | 0.1% flat across all targets — implemented via RANGE=10001 denominator |
| Simulated RTP (Pass 1) | Pass | 98M rounds, avg RTP = 99.922%, 0/98 chi-squared failures, 0/98 serial independence failures |
| Cherry-pick detection (Pass 2) | Pass | 134 casino seeds tested — no evidence of seed pre-selection |
| Bet-size invariance | Pass | Bet amount is not an input to the RNG — same result distribution at $0.01 and $10. Tested in Phase C (200/200) |
| Multiplier formula | Pass | 0.999 / ((10000 − target) / 10001) verified for all 6,700 bets — zero mismatches |
| Win chance formula | Pass | (10000 − target) / 10001 exact to 14 decimal places for all 6,700 bets |
Anti-circularityPass
House edge auditPass
Simulated RTP (Pass 1)Pass
Cherry-pick detection (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
Win chance formulaPass
The 99.9% RTP is proven mathematically — win_chance × multiplier = 0.999 is an algebraic identity for every target. This is a first-principles proof, not a statistical estimate. 98M simulated rounds and cherry-pick detection confirm no anomalies. The 0.1% house edge is flat and transparent.
How It Works — RTP & Payout Logic8 sections
4.1Anti-Circularity & Sampling Uniformity (Steps 9–10, 13)
The anti-circularity argument establishes the 99.9% RTP from first principles without trusting any casino-supplied probability data. The win chance is derived from the RNG's integer range, with its uniformity precondition gated by Step 13. The multiplier is the only casino-sourced input — verified against all 6,700 live bets (Step 9), with win chances confirmed exact to 14 decimal places (Step 10). With both components independently verified, the RTP follows in closed form:
| Component | Formula | Source |
|---|---|---|
| Win chance | win_chance = (10000 − target) / 10001 | Derived from RANGE=10001 — uniformity precondition gated by Step 13 |
| Multiplier | multiplier = 0.999 / win_chance | Published formula — verified on all 6,700 live bets (Step 9) |
| RTP computation | RTP = win_chance × multiplier = win_chance × (0.999 / win_chance) | Cancels algebraically |
| Result | = 0.999 for all targets | Closed form — not a statistical estimate |
| Target | Win Chance | Multiplier | RTP |
|---|---|---|---|
| 200 | 97.9902% | 1.0195× | 99.900% |
| 2000 | 79.9920% | 1.2489× | 99.900% |
| 5000 | 49.9950% | 1.9982× | 99.900% |
| 9000 | 9.9990% | 9.9910× | 99.900% |
| 9800 | 1.9998% | 49.9550× | 99.900% |
| 9998 | 0.0200% | 4995.4995× | 99.900% |
// Step 13: Sampling uniformity — closed-form RTP precondition// The closed-form house edge (EV = 0.999 for all targets 2–9998) holds iff the// rejection-sampling bound is an EXACT multiple of RANGE. If MAX_FAIR % RANGE ≠ 0,// residues 0..(MAX_FAIR % RANGE − 1) become over-represented and the per-target// win probability deviates from (10000−t)/10001 — breaking the EV proof.// We recompute the bound independently and assert it matches src/rng.ts.const MAX_FAIR_INDEP = 0xFFFFFFFF - (0xFFFFFFFF % 10001);const exactMultiple = MAX_FAIR_INDEP % 10001 === 0;const boundMatches = MAX_FAIR_INDEP === MAX_FAIR;const rangeMatches = RANGE === 10001;// MAX_FAIR = 4294959453 = 10001 × 429453 → result exactly uniform on {0..10000}Why this argument is non-circular: The win chance is derived from the RNG mechanics (RANGE=10001 outcomes, win if result > target), with the uniformity precondition gated by Step 13 — not taken from casino data. The multiplier formula is the only casino-sourced input, and it is verified against the live payouts of all 6,700 captured bets (Step 9). Multiplying the independently derived win chance by the live-verified multiplier gives exactly 0.999 for every target — proven, not estimated. The 0.999 factor in the multiplier formula is the direct cause of the 0.1% house edge.
4.2House Edge Mechanism
The house edge is implemented by using RANGE = 10001 discrete outcomes rather than 10000. This shifts the effective probability of any winning result by a factor of 10000/10001. For any target t, the win chance is (10000 − t) / 10001 rather than (10000 − t) / 10000. The multiplier is then set to 0.999 / win_chance, which closes the loop at 99.9% RTP regardless of target. The 0.999 numerator in the multiplier formula is what produces the flat 0.1% house edge; it is constant across all targets and bet sizes.
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,700 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.
Fair win chance (no edge): (10000 − t) / 10000
Actual win chance (0.1% edge): (10000 − t) / 10001
Ratio: 10000 / 10001 = 0.9999000099...
Multiplier = 0.999 / win_chance
RTP = win_chance × multiplier = 0.999 (exactly, for all t)
House edge = 1 − 0.999 = 0.1%0.999 / ((10000 − t) / 10001) against every bet (Step 9), (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,700 bets and an injected value is ignored by the engine (metadata-tampering check). No scaling brackets, no progressive edge — flat across all targets. The operator-side rakeback layer that updates net edge to 0% is outside this audit's scope.4.3Full RTP Table — All Targets
Unlike games with a fixed configuration matrix, Dice uses a single formula for all targets. The theoretical RTP is 99.900% for every target — this is an algebraic identity, not a per-config calculation. The table below shows 14 representative targets; all 98 simulated targets are available in outputs/simulation-results.json.
| Target | Win Chance | Multiplier | Theoretical RTP | Simulated RTP | Deviation |
|---|---|---|---|---|---|
| 200 | 97.99% | 1.0195× | 99.900% | 99.895% | -0.005% |
| 500 | 94.99% | 1.0517× | 99.900% | 99.918% | +0.018% |
| 1000 | 89.99% | 1.1101× | 99.900% | 99.873% | -0.027% |
| 2000 | 79.99% | 1.2489× | 99.900% | 99.847% | -0.053% |
| 3000 | 69.99% | 1.4273× | 99.900% | 100.019% | +0.119% |
| 4000 | 59.99% | 1.6652× | 99.900% | 99.827% | -0.073% |
| 5000 | 50.00% | 1.9982× | 99.900% | 99.851% | -0.049% |
| 6000 | 40.00% | 2.4977× | 99.900% | 99.971% | +0.071% |
| 7000 | 30.00% | 3.3303× | 99.900% | 99.842% | -0.058% |
| 8000 | 20.00% | 4.9955× | 99.900% | 99.816% | -0.084% |
| 9000 | 10.00% | 9.9910× | 99.900% | 99.676% | -0.224% |
| 9500 | 5.00% | 19.9820× | 99.900% | 99.083% | -0.817% |
| 9800 | 2.00% | 49.9550× | 99.900% | 99.515% | -0.385% |
| 9900 | 1.00% | 99.9100× | 99.900% | 100.599% | +0.699% |
4.4Simulation Pass 1 — Fresh Seeds (Step 16)
Section 4.1 proves RTP = 99.9% mathematically. But does the game engine actually produce that in practice? To find out, we simulated 98 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 win/loss counts to expected counts at each target. 0/98 fail at uncorrected α=0.01. 0/98 fail at Bonferroni α/98.
Serial independence: Lag-1 autocorrelation measures correlation between consecutive results. Runs test checks for non-random run structure. 0/98 targets fail either test.
RTP convergence: Avg simulated RTP = 99.922% vs 99.900% theoretical. Deviation of +0.022% is within expected sampling variance.
// 98 targets: 200, 300, 400, ..., 9900 (full range in steps of 100)const PASS1_TARGETS = Array.from({ length: 98 }, (_, i) => (i + 2) * 100);const PASS1_ROUNDS_EACH = 1_000_000;const PASS1_SEEDS = 1; // single seed per target, 1M roundsWhat is a Monte Carlo simulation? Instead of proving fairness with algebra alone, we simulate millions of real game rounds. Each round computes a dice result from scratch using the same HMAC-SHA256 rejection sampling pipeline as the live game. We record payouts, then compare the aggregate return to the theoretical 99.9%. If they match, the implementation matches the math — not just on paper.
4.5Cherry-Pick Detection — Pass 2 (Step 17)
Could the casino have chosen server seeds that produce worse outcomes for players? Pass 2 takes every server seed the casino actually used and simulates 10,000 rounds per seed to check whether any of them are statistically biased against players.
Test A — Overall win rate: Does any individual seed produce lower-than-expected win rate across all 10,000 nonces? A cherry-picked seed would show a statistically significant deficit.
Test B — Early vs late window: Does any seed produce worse outcomes in the early nonces (0–49, where real players bet) compared to later nonces (50–9999)? A sophisticated cherry-pick could target only the first 50 rounds.
| Test | Result |
|---|---|
| Seeds tested | 134 |
| Nonces per seed | 10,000 |
| Target | 5000 (50% theoretical win rate) |
| Test A fails (p<0.01) | 2 / 134 — within expected false positive rate |
| Cherry-pick flags (Test B) | 9 / 134 — within expected range under H₀ |
// Cherry-pick test at target=5000 (50% win rate)const CHERRY_PICK_TARGET = 5000;const CHERRY_PICK_THEOR = theoreticalWinChance(CHERRY_PICK_TARGET); const NONCES_PER_SEED = 10_000;const EPOCH_LENGTH = 50; // nonces 0–49: the live capture window (early) for (let n = 0; n < NONCES_PER_SEED; n++) { const result = computeResultFromBuffer(keyBuf, clientSeed, n); if (result > CHERRY_PICK_TARGET) { if (n < EPOCH_LENGTH) earlyWins++; else lateWins++; }}// Cherry-picking signature: early deviates from binomial, late does notconst cherry_pick_flag = pEarly < 0.05 && pLate >= 0.05;4.6Bet-Size Equivalence (Phase C)
Phase C placed 200 bets at $10 on target 9700. All 200 dice results were recomputed correctly from revealed seeds using the same HMAC-SHA256 algorithm as $0.01 bets. The bet amount does not appear in the HMAC input (clientSeed:nonce) — it is structurally absent from the RNG.
| Metric | Phase B ($0.01) | Phase C ($10) |
|---|---|---|
| Target | 9800 | 9700 |
| Bets | 1,000 | 200 |
| Results verified | 1,000/1,000 | 200/200 |
| RNG path | HMAC-SHA256 | HMAC-SHA256 |
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.
| Item | Value | Context |
|---|---|---|
| Phase A empirical RTP | 103.304% | 5,000 bets at $0.01 — random targets, within expected variance |
| Phase B empirical RTP | 124.887% | 1,000 bets at $0.01, target 9800 — high-variance, within expected range |
| Phase C empirical RTP | 83.258% | 200 bets at $10, target 9700 — high-variance, expected at N=200 |
| Phase D empirical RTP | 99.910% | 500 bets at $0.01, target 9700 — closest to theoretical |
4.8Worked Example — Payout Verification
Real bet from Phase A — Bet ID 50120996, target 4253, nonce 1. Verified from dice-master-6700bets.json:
serverSeed = 528555039caf67c0219c9537cf6626c63cb0bfa384b72571410e522773270eb5
clientSeed = bRNvtSn0hp2uVFRe
nonce = 1
target = 4253Step 1 — Result computation: computeResult(serverSeed, clientSeed, 1) → HMAC-SHA256 → chunk % 10001 = 9793
Step 2 — Win check: 9793 > 4253 = true → win
Step 3 — Multiplier: 0.999 / ((10000 − 4253) / 10001) = 1.738472×
Step 4 — Payout: 0.01 × 1.738472 = 0.017385
| Step | Process | Output |
|---|---|---|
| 1 | HMAC-SHA256 → rejection sampling | result = 9793 ✅ |
| 2 | Win check: 9793 > 4253 | true ✅ |
| 3 | RTP proof: win_chance × multiplier | (5747/10001) × 1.738472 = 0.999 |
| 4 | Payout: 0.01 × 1.738472 | 0.017385 ✅ |
// Target 4253 — closed-form RTP// win_chance = (10000 − 4253) / 10001 = 0.574642535746...// multiplier = 0.999 / 0.574642535746... = 1.738472072385...//// RTP = 0.574642... × 1.738472... = 0.999000 (99.9%)//// This is an algebraic identity, not a measurement.// The 0.999 factor is the source of the 0.1% house edge.Technical Evidence & Verification5 sections
4.9Evidence Coverage Summary5 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Anti-circularity (Steps 9–10 + 13) | Live formulas verified on all 6,700 bets; sampling bound exact multiple of 10001 — zero modulo bias | Pass |
| House edge audit | Derived from 0.999 numerator in multiplier formula — verified against all 6,700 bets (Step 9); effective_edge field is not a game-logic input — constant at 0.1, injected values ignored (metadata-tampering check) | Pass |
| Simulation Pass 1 (Step 16) | 98M rounds, 0/98 chi-squared, 0/98 serial | Pass |
| Simulation Pass 2 (Step 17) | 134 seeds × 10K nonces, 9 flags (expected range) | Pass |
| Bet-size invariance (Phase C) | 200/200 at $10 | Pass |
4.10Code References5 source files
| File | Purpose |
|---|---|
| tests/steps/dataset.ts | Step 13: Sampling Uniformity (closed-form RTP precondition) |
| tests/steps/simulation.ts | Steps 16–17: Simulation integrity and cherry-pick detection |
| src/simulate.ts | Monte Carlo simulation (98M rounds, two-pass) |
| src/rng.ts | theoreticalWinChance, theoreticalMultiplier |
| src/stats.ts | chiSquaredTest, lagOneAutocorrelation, waldsWolfowitzRunsTest |
4.11Datasets UsedSource artifacts
4.12Verified Invariants8 invariants
| Invariant | Result |
|---|---|
| win_chance × multiplier = 0.999 for all platform targets 200–9998 (non-circular) | Pass |
Flat 0.1% house edge — derived from 0.999 numerator in multiplier formula; effective_edge field is not a game-logic input — constant at 0.1 across all 6,700 bets, injected values ignored (metadata-tampering check) | Pass |
| Simulated RTP average = 99.922% across 98M rounds | Pass |
| 0/98 targets reject at Bonferroni α/98 | Pass |
| 0 serial independence failures at Bonferroni-corrected threshold | Pass |
| No evidence of seed pre-selection across 134 casino seeds (Pass 2) | Pass |
| Phase C ($10) produces identical results to $0.01 algorithm | Pass |
| Bet amount absent from RNG input by construction | Pass |
4.13Reproduction InstructionsRun locally
git clone https://github.com/ProvablyFair-org/duel-dice.gitcd duel-dice && npm installnpm run simulate # 98M simulation + cherry-pick testnpm run verify # Steps 13, 16, 17 cover S4cat outputs/simulation-results.jsonS4-related steps:
[PASS] Step 13 — Sampling Uniformity (closed-form RTP precondition)
[PASS] Step 16 — Simulation Results — Pass 1 Integrity
[PASS] Step 17 — Simulation Results — Pass 2 Cherry-Pick TestFairness 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 15 standard fairness integrity tests covering nonce integrity, seed commitment, outcome determinism, cross-player isolation, and payout integrity. 14 tests passed and 1 is not applicable to this game type.
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 target values or bet types be submitted?
What this means for you
- Across the 15 tests we ran, no API path allowed outcomes to be altered, replayed, or injected — by player or casino
- Once a bet is placed, the result 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 target values or bet types be submitted?
- Across the 15 tests we ran, no API path allowed outcomes to be altered, replayed, or injected — by player or casino
- Once a bet is placed, the result 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
15 standard fairness integrity tests: 14 pass, 1 N/A (single-step game).
Category Coverage
| Check | Result | Evidence |
|---|---|---|
| Nonce integrity | Pass | Sequential, server-controlled, no gaps or duplicates across 134 seed pairs |
| Seed commitment integrity | Pass | Locked at bet acceptance, unique per seed pair — 134/134 verified |
| Outcome determinism | Pass | Identical inputs produce identical outcomes — 6,700/6,700 confirmed |
| Round & player isolation | Pass | Per-user seeds, serial independence confirmed (0/98 fail in simulation) |
| Payout integrity | Pass | Parameter limits enforced (5/5 invalid rejected); injected payout fields ignored (0 honoured); server-computed multiplier consistent across all probes |
Nonce integrityPass
Seed commitment integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
15 standard fairness integrity tests: 14 pass, 1 N/A (single-step game).
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. 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 |
| Player Isolation | 2 | Cross-round correlation, cross-user outcome dependence |
| Payout Integrity | 2 | Parameter enforcement, server-side computation verification |
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 |
| Result recomputation mismatch | Undisclosed inputs affecting outcomes |
| Client seed not used in HMAC | Player has no influence on outcomes |
15 tests · 14 pass · 1 N/A
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 in request body. All 7 silently ignored; server assigned valid sequential nonces. Continuity preserved (baseline 72 → post-test 80); 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/6 invalid client seeds rejected (empty, null, special chars, emoji, CJK, whitespace-only). Oversized seed (>1000 chars) accepted — permissive but safe (long entropy). All Dice 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
S1, Step 1 — 134 distinct revealed server seeds, 0 reuse detected.
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), the game always produces the same result — verified across all 6,700 live bets
S3, Step 5 — 6,700/6,700 bets independently recomputed from seeds, 0 mismatches.
FI-OUTCOME-002N/AA completed bet cannot be replayed via the API to generate a duplicate payout
Dice auto-resolves — single-step game with no separate cashout action. Nonce increments server-side after each bet. No replay surface exists.
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 Integrity2/2
FI-PAYOUT-001PassGame parameters cannot exceed defined limits — valid target values within 0–10000 and bet types `over` or `under` are accepted; out-of-range targets, 0% win configurations, and unknown bet_type values rejected HTTP 400
API probe — 3 valid control bets accepted (target=5000 over, target=100 under, target=9900 over). All 5 invalid parameter combinations rejected HTTP 400: target=10001 (above max), target=−1 (below min), target=0 under (0% win chance), bet_type='invalid' (unknown), and target=10001 as numeric type. Recovery control 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 its own formula, not from client-supplied values
API probe — 5 injected fields (payout_multiplier=1000, roll_result=0, multiplier='99999', is_win=true with result=0, win_chance='1' with effective_edge=−99) all silently ignored. Server computed its own outcome on every bet at consistent 1.9982× multiplier. noInjectionHonoured=true.
Technical Evidence & Verification4 sections
5.3Coverage Summary15 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, Step 5 (data-driven) | Pass |
| FI-OUTCOME-002 | Determinism | Structural — single-step game | N/A |
| FI-ISO-001 | Isolation | S2, Step 16 (simulation) | Pass |
| FI-ISO-002 | Isolation | Structural — seed uniqueness | Pass |
| FI-PAYOUT-001 | Payout | API probe | Pass |
| FI-PAYOUT-002 | Payout | API probe | Pass |
5.4Additional Integrity Evidence (S1–S4)6 records
| Property | Source | Finding |
|---|---|---|
| 134/134 seed hashes verified | S1, Step 1 | Commit-reveal chain intact |
| 138/138 next-seed promotions | S1, Step 2 | Seed rotation chain intact |
| 6,700/6,700 exact parity | S3, Step 5 | No post-RNG conditional logic |
| Anti-circularity proven | S4, Steps 9–10 + 13 | Live formulas verified on all 6,700 bets; 0.1% edge follows in closed form |
| 9 cherry-pick flags (expected range) | S4, Step 17 | No seed pre-selection bias |
| 100% client seed influence | S1, Step 6 | Player entropy is genuine |
5.5Scope & LimitationsScope note
5.6Reproduction InstructionsRun locally
Data-driven tests (8 of 15): Fully reproducible from the open-source repo. These tests run against the captured dataset and produce deterministic results.
API probe tests (6 of 15): Verified by issuing live adversarial requests against the running game. Per-test evidence (HTTP status codes, server-assigned nonces, seed hashes, server-computed multipliers) 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-dice.gitcd duel-dicenpm 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 — Result Recomputation → FI-OUTCOME-001
[PASS] Step 6 — Client Seed Influence → FI-SEED-002
[PASS] Step 16 — Simulation Pass 1 → FI-ISO-001API 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/6 invalid seeds 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 parameter handling → 5/5 boundary violations rejected (HTTP 400)[PASS] FI-PAYOUT-002 — Multiplier field handling → 0 injections honoured (1.9982× consistent)Player Verification
Can a player verify their own bets without trusting anyone?
Every Dice outcome can be independently reproduced using publicly disclosed inputs. No hidden variables, no private backend data. If your calculated result 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 Dice outcome can be independently reproduced
- No hidden variables — no private backend data
- If your computed result 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)
- Every Dice outcome can be independently reproduced
- No hidden variables — no private backend data
- If your computed result 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)
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 Dice Bet
Choose your target value and bet amount. The game generates a result using the provably fair algorithm.
- 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 Dice 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 dice result inline — if it 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 direction (Roll Over or Roll Under), set your target (2.00–99.98) and bet amount, then place a bet. The dice rolls and shows a result on a 0.00–99.99 scale. The server has already committed to the outcome before you clicked.

Duel.com Dice — Roll Over / Roll Under slider with displayed result. Outcome is locked before the animation plays.
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 Dice 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 dice result from the disclosed inputs and renders the Game Result inline. If the recomputed result 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 result 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 dice result using three 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
These three ingredients are combined with HMAC-SHA256 (a cryptographic function) to produce a hash. The first valid 4-byte chunk of the hash is reduced modulo 10001 to produce an integer from 0 to 10,000 — your dice result. If the result is greater than your target, you win. 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.
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 Dice bet:
const crypto = require('crypto'); const RANGE = 10001;const MAX_U32 = 0xFFFFFFFF;const MAX_FAIR = MAX_U32 - (MAX_U32 % RANGE); function computeResult(serverSeed, clientSeed, nonce) { const key = Buffer.from(serverSeed, 'hex'); const hash = crypto.createHmac('sha256', key) .update(`${clientSeed}:${nonce}`).digest('hex'); for (let off = 0; off + 8 <= hash.length; off += 8) { const value = parseInt(hash.substring(off, off + 8), 16); if (value < MAX_FAIR) return value % RANGE; }} 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 target = 5000; console.log('Hash check:', verifyHash(serverSeed, serverSeedHashed) ? 'PASS' : 'FAIL');const result = computeResult(serverSeed, clientSeed, nonce);console.log('Computed result:', result);console.log('Win:', result > target ? 'YES' : 'NO');6.10Python Verification Script
The same verification in Python (standard library only):
import hashlib, hmac RANGE = 10001MAX_U32 = 0xFFFFFFFFMAX_FAIR = MAX_U32 - (MAX_U32 % RANGE) def compute_result(server_seed, client_seed, nonce): key = bytes.fromhex(server_seed) message = f'{client_seed}:{nonce}'.encode() h = hmac.new(key, message, hashlib.sha256).hexdigest() for off in range(0, len(h) - 7, 8): value = int(h[off:off+8], 16) if value < MAX_FAIR: return value % RANGE 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 = 0target = 5000 print('Hash check:', 'PASS' if verify_hash(server_seed, server_seed_hashed) else 'FAIL')result = compute_result(server_seed, client_seed, nonce)print('Computed result:', result)print('Win:', 'YES' if result > target else 'NO')6.11Evidence Screenshots6 records
| Evidence | Description |
|---|---|
| E01 | Dice game UI — target selector and result display |
| E02 | Fairness page overview — "What is Provably Fair?" and verification tabs |
| E03 | Fairness verification tool — Dice selected, showing result verification |
| E04 | Active server seed hash commitment before betting |
| E09 | Seed rotation revealing previous server seed |
| 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 Dice Bet
Choose your target value and bet amount. The game generates a result using the provably fair algorithm.
- 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 Dice 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 dice result inline — if it matches your live game result, the bet was provably fair.
Visual Walkthroughstep-by-step
Choose your direction (Roll Over or Roll Under), set your target (2.00–99.98) and bet amount, then place a bet. The dice rolls and shows a result on a 0.00–99.99 scale. The server has already committed to the outcome before you clicked.

Duel.com Dice — Roll Over / Roll Under slider with displayed result. Outcome is locked before the animation plays.
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 Dice 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 dice result from the disclosed inputs and renders the Game Result inline. If the recomputed result 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 result 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 dice result using three 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
These three ingredients are combined with HMAC-SHA256 (a cryptographic function) to produce a hash. The first valid 4-byte chunk of the hash is reduced modulo 10001 to produce an integer from 0 to 10,000 — your dice result. If the result is greater than your target, you win. 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.
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 Dice bet:
const crypto = require('crypto'); const RANGE = 10001;const MAX_U32 = 0xFFFFFFFF;const MAX_FAIR = MAX_U32 - (MAX_U32 % RANGE); function computeResult(serverSeed, clientSeed, nonce) { const key = Buffer.from(serverSeed, 'hex'); const hash = crypto.createHmac('sha256', key) .update(`${clientSeed}:${nonce}`).digest('hex'); for (let off = 0; off + 8 <= hash.length; off += 8) { const value = parseInt(hash.substring(off, off + 8), 16); if (value < MAX_FAIR) return value % RANGE; }} 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 target = 5000; console.log('Hash check:', verifyHash(serverSeed, serverSeedHashed) ? 'PASS' : 'FAIL');const result = computeResult(serverSeed, clientSeed, nonce);console.log('Computed result:', result);console.log('Win:', result > target ? 'YES' : 'NO');6.10Python Verification Script
The same verification in Python (standard library only):
import hashlib, hmac RANGE = 10001MAX_U32 = 0xFFFFFFFFMAX_FAIR = MAX_U32 - (MAX_U32 % RANGE) def compute_result(server_seed, client_seed, nonce): key = bytes.fromhex(server_seed) message = f'{client_seed}:{nonce}'.encode() h = hmac.new(key, message, hashlib.sha256).hexdigest() for off in range(0, len(h) - 7, 8): value = int(h[off:off+8], 16) if value < MAX_FAIR: return value % RANGE 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 = 0target = 5000 print('Hash check:', 'PASS' if verify_hash(server_seed, server_seed_hashed) else 'FAIL')result = compute_result(server_seed, client_seed, nonce)print('Computed result:', result)print('Win:', 'YES' if result > target else 'NO')6.11Evidence Screenshots6 records
| Evidence | Description |
|---|---|
| E01 | Dice game UI — target selector and result display |
| E02 | Fairness page overview — "What is Provably Fair?" and verification tabs |
| E03 | Fairness verification tool — Dice selected, showing result verification |
| E04 | Active server seed hash commitment before betting |
| E09 | Seed rotation revealing previous server seed |
| 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-dice
- Commit: 4380a5a9d715467f3a0184b32390dff60580fdb2
- Game: Dice (duel-dice)
- 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-dice
- Commit: 4380a5a9d715467f3a0184b32390dff60580fdb2
- Game: Dice (duel-dice)
- 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, dataset, and commands above. The dataset hash ensures you're running against the same 6,700 bets.
Commands to Reproduce
Installs TypeScript, ts-node, and cryptographic dependencies. npm test runs mocha (14 unit tests), then the 98M-round simulation, then the 17-step verification pipeline.
Simulates 1,000,000 rounds for each of the 98 targets (98M total). Output written to outputs/simulation-results.json and outputs/rtp-convergence.html.
Audit Reproducibility Pinning
- Git Commit
- 4380a5a9d715467f3a0184b32390dff60580fdb2
- Node Version
- v18+ (tested on v22.x)
- Dataset
- data/dice-master-6700bets.json (6,700 bets, 134 active seeds + 4 boundary)
- Dataset Hash (SHA-256)
- 3550ffca07a6f7…ff056d6
- Audit Date
- April 2026
- Audit ID
- PF-2026-DL02
Output Artifacts5 files generated
| File | Contents | Size |
|---|---|---|
| verification-results.json | Steps 1–17 results, per-step pass/fail with evidence | ~25KB |
| simulation-results.json | 98M-round RTP, chi-squared, cherry-pick detection (Pass 1 + Pass 2) | ~120KB |
| determinism-log.json | Per-bet (computed_result vs actual_result) for all 6,700 | ~1MB |
| chi-squared-results.json | Per-target chi-squared statistics and p-values | ~15KB |
| rtp-convergence.html | Interactive RTP convergence chart (self-contained HTML) | ~5KB |
Repository Structure
duel-dice/ ├── src/ │ ├── rng.ts → HMAC-SHA256 dice computation (rejection sampling) │ ├── simulate.ts → Monte Carlo — 1M rounds/target × 98 │ ├── stats.ts → Chi-squared, autocorrelation, runs test │ ├── loader.ts → Dataset loader + SHA-256 hash guard │ └── types.ts → Type definitions ├── tests/ │ ├── verify.ts → 17-step verification pipeline │ ├── steps/ │ │ ├── commitment.ts → Steps 1–4: Commit-reveal integrity │ │ ├── determinism.ts → Steps 5–6: Result recomputation + client seed │ │ ├── payouts.ts → Steps 7–10: Payout math + formulas │ │ ├── dataset.ts → Steps 11–15: Dataset integrity checks │ │ ├── simulation.ts → Steps 16–17: Simulation integrity │ │ ├── statistical.ts → Informational: RTP, serial, chi-squared │ │ └── context.ts → Shared context + pass/fail helpers │ └── dice/ │ └── DiceTests.ts → 14 unit tests (Mocha) ├── data/ │ └── dice-master-6700bets.json → 6,700 live bets (4 phases, 138 seeds) ├── outputs/ → Generated by npm test │ ├── verification-results.json → Steps 1–17 pass/fail │ ├── simulation-results.json → 98M rounds, per-target RTP + cherry-pick │ ├── determinism-log.json → Per-bet result recomputation log │ ├── chi-squared-results.json → Distribution test results │ └── rtp-convergence.html → Interactive RTP convergence chart ├── evidence/ │ ├── E01–E11 *.png → Game UI, fairness page, seed rotation evidence │ └── client-seed-origin.png → Client seed origin evidence ├── capture/ │ └── capture-auto.reference.js → Browser bet capture script ├── results/ → Merged capture working directory ├── README.md ├── MANIFEST.md ├── package.json ├── tsconfig.json └── .mocharc.yml
Step-to-Section Cross-Reference17 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 | Result Recomputation (RNG determinism) | S1, S2, S3 |
| 6 | Client Seed Influence | S1, S2 |
| 7 | Payout Math | S3 |
| 8 | Win Condition (result > target) | S3 |
| 9 | Multiplier Formula (0.999 / win_chance) | S3 |
| 10 | Win Chance Formula ((10000 − target) / 10001) | S3 |
| 11 | Phase Labels | S3 |
| 12 | Dataset Hash | S7 |
| 13 | Sampling Uniformity (closed-form RTP precondition) | S4 |
| 14 | Epoch Size | S1 |
| 15 | Phase D — Client Seed Variation | S1, S2 |
| 16 | Simulation Results — Pass 1 Integrity | S2, S4 |
| 17 | Simulation Results — Pass 2 Cherry-Pick Test | S4 |
reproduce-audit.sh
All audit results can be independently reproduced using the pinned commit, dataset, and commands above. The dataset hash ensures you're running against the same 6,700 bets.