Audit Verdict
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: 33 verification steps, the 10M-round simulation (10 streams Fisher-combined + Pass 2 cherry-pick detection), the independent infinite-deck EV solver, and 6,000 live bets re-verified card-by-card.
Blackjack Audit Overview
This audit independently validates the Blackjack 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,000 real bets across 120 seed pairs and independently verified every visible card — initial deal, hits, doubles, splits, and dealer play-out — using our own implementation of the algorithm.
What Was Audited
- The RNG algorithm is deterministic and verifiable
- Each round's server seed is cryptographically committed via SHA-256 before the round opens for betting
- Client seed is browser-generated and players can customize it
- Nonces increment correctly within each seed pair and never repeat
- Each visible card is computed via HMAC-SHA256 single-card draw with bias-free rejection sampling
- The card stream — initial deal, hits, doubles, splits, dealer play-out — is reproducible from (serverSeed, clientSeed, nonce, cursor)
- Dealer rule compliance: stand on soft 17 (S17), peek on Ace/10 upcards, no surrender, no re-split
- Optimal-play RTP is 99.4296% (independent recursive infinite-deck EV solver, cross-validated against Wizard of Odds)
- Bet amount does not influence the card stream — verified across $0.01 and $10 stakes
- Players can independently verify every bet — every visible card and every payout
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, supplied before each epoch — server commits before the seed is known |
| Seed Handling | Client seed control, nonce lifecycle, seed pair rotation |
| RNG Analysis | HMAC-SHA256 single-card draw, infinite-deck independent draws, rejection sampling, bias analysis |
| Game Flow | Initial deal (cursors 0–3), player actions (cursor 4+), dealer play-out — every visible card verified |
| Payout Logic | Main payouts (win, blackjack 3:2, push, doubled-hand 4×), Perfect Pairs + 21+3 side bets, bet-size invariance (Phase E $10) |
| Live Parity | Independent card-by-card recomputation vs live game results — 6,000 / 6,000 hands, 33,194 cursors |
| RTP Validation | Anti-circularity (independent EV solver), simulated RTP (10M rounds, 10 streams Fisher-combined), Wizard of Odds cross-check, cherry-pick detection (Pass 2) |
| Hand Resolution | Dealer S17 compliance, blackjack 3:2, double payout, split rules (DAS, no re-split, ace one-card), insurance prompt condition |
| Side Bet Coverage | Perfect Pairs + 21+3 across 5,800 active bets each — multiplier provenance, deal-time invariance, stake equality |
| Fairness Integrity | Standard integrity matrix — tests across commit-reveal, determinism, payout, isolation, and parameter enforcement |
What Audit Guarantees
- Outcomes are deterministic and reproducible from the recorded inputs
- Live game results match independent recomputation for the verified sample (6,000 / 6,000 hands; 33,194 card cursors)
- Every visible card is verified — initial deal, every hit, every double card, every split sub-hand card, every dealer draw
- Optimal-play RTP is proven analytically: independent recursive infinite-deck EV solver returns 99.4296%; cross-validated against Wizard of Odds for Duel's exact rule set (S17, DAS, no surrender, no re-split), Δ ≈ 0
- Client seed is a genuine, independent input that materially influences results (99% of sampled bets diverge under a wrong client seed; analytical baseline 51/52 ≈ 98.08% at cursor 0)
- The house edge is 0.5704% — derived from the EV solver, not from any casino-supplied figure
- Stake does not enter the card-generation algorithm
What Audit Excludes
- Infrastructure or server security
- Wallet, payments, or operational systems outside game logic
- Rakeback layer — 99.4296% optimal-play RTP is the certified figure; rakeback is operator-side
- Cross-account sampling
- Max win cap enforcement — not embedded in game logic
References
Blackjack — Game Rules6 sections▶
Blackjack is a single-hand game against the dealer — you try to get closer to 21 without going over. Choose from hit, stand, double, or split. The dealer stands on every 17 and blackjack pays 3:2. The deck is treated as infinite, so cards are drawn independently and identical-rank cards can appear in the same hand. Two optional side bets — Perfect Pairs and 21+3 — resolve on the deal.
1. Place your bet — Choose your stake on the main hand. You may also place independent stakes on Perfect Pairs and 21+3 side bets.
2. Receive the deal — You receive two face-up cards (cursors 0, 2). The dealer receives one face-up upcard (cursor 1) and one face-down hole card (cursor 3). Side bets resolve immediately.
3. Decide your action — Choose hit (take another card), stand (keep your hand), double (take exactly one more card and double your stake), or split (only if your two cards are the same rank — separate them into two hands).
4. Play out the dealer — Once you stand or bust, the dealer reveals their hole card and draws additional cards until they reach 17 or higher (or bust above 21).
5. Outcome — Win pays 1:1 on the main bet (return = 2× stake). A natural blackjack (Ace + 10-value on the deal) pays 3:2 (return = 2.5× stake) unless the dealer also has blackjack (push, return = 1×). A doubled win returns 4× stake; a doubled push returns 2× stake; a loss or bust returns 0.
The win condition compares the final hand totals after the dealer plays out.
| Outcome | Condition | Return on $1 main stake |
|---|---|---|
| Natural blackjack | Player has Ace + 10-value on the deal; dealer does not | $2.50 (3:2 payout) |
| Win (regular) | Player total ≤ 21 and beats dealer total | $2.00 (1:1) |
| Doubled win | Player doubled, hand ≤ 21, beats dealer | $4.00 |
| Push | Player and dealer have the same final total | $1.00 (stake returned) |
| Doubled push | Player doubled, ties dealer | $2.00 (doubled stake returned) |
| Loss / bust | Player total > 21 OR dealer beats player | $0.00 |
Blackjack's RTP is fixed by the rule set, not by a tunable house-edge knob.
- Optimal-play RTP is 99.4296% — verified by independent recursive infinite-deck EV solver enumerating all 1,000 (P1, P2, dealer-up) initial triples × dealer S17 distribution × max-EV player action
- House edge is 0.5704% — the operative number for long-run play under perfect basic strategy
- Decisions matter — sub-optimal play (e.g., hitting hard 17, splitting 10s) lowers the achievable RTP; the 99.4296% figure is the ceiling under perfect basic strategy
- Stake-invariant —
getCard(serverSeed, clientSeed, nonce, cursor)has no wager parameter; the same seed pair produces the same cards at $0.01 or $10
| Parameter | Value | Notes |
|---|---|---|
| Deck Model | Infinite (with replacement) | Each card drawn independently at 1/52; identical-rank cards can appear within the same hand |
| Dealer Rule | Stand on all 17 (S17) | Including soft 17 — verified across 75 in-range played-out hands (Step 19) |
| Blackjack Payout | 3:2 (return = 2.5× stake) | 260 / 260 naturals verified (Step 20) |
| Doubling | Available on any two-card hand; DAS allowed | 1,368 doubled hands across 1,291 bets reconciled (Step 21) |
| Splitting | Same-rank pairs only; one card on split aces; no re-splitting | 269 split bets across 33,194 cursors verified (Steps 22-24) |
| Surrender | Not available | Confirmed via available-actions audit (Step 28) |
| Insurance | Offered only on dealer Ace upcard; pays 2:1 | Step 25: insurance prompt condition verified |
| Side Bets | Perfect Pairs + 21+3 (independent, deal-time only) | 5,800 active PP bets + 5,800 active 21+3 bets verified (Steps 26-27) |
| House Edge | 0.5704% | Derived from optimal-play EV solver — not an API-reported value |
| RNG Algorithm | HMAC-SHA256 single-card draw | Per-cursor independent draws with bias-free rejection sampling at MAX_FAIR = 4,294,967,248 |
Every Blackjack bet uses three cryptographic inputs to generate the card stream.
| Seed Type | Format | Example | Purpose |
|---|---|---|---|
| Server Seed | 64-char hex (32 bytes) | b6f2cbcd411eedbd… | Casino-provided randomness |
| Client Seed | Alphanumeric string | pf_Naa0pBEOuWmz6 | Player-contributed entropy |
| Nonce | Integer ≥ 0 | 1 | Per-bet counter within the active seed pair |
serverSeedHashed (SHA-256 of the raw hex bytes) before play and revealed only on seed rotation. The HMAC key is the hex-decoded bytes of the server seed, not its UTF-8 text. Cursors 0..49 within a single bet use the same (serverSeed, clientSeed, nonce) triple, with the cursor index appended to the HMAC message: clientSeed:nonce:cursor.Every visible card is drawn from a specific cursor position. The cursor is appended to the HMAC message, so card N at cursor C is fully determined by (serverSeed, clientSeed, nonce, C). The first four cursors form the initial deal; cursors 4 onward are consumed in order as actions are taken.
| Cursor | Role | Notes |
|---|---|---|
| 0 | Player card 1 | Used for Perfect Pairs and 21+3 classification |
| 1 | Dealer upcard | Determines insurance prompt eligibility (Ace upcard) |
| 2 | Player card 2 | Used for Perfect Pairs (with cursor 0); not a 21+3 input |
| 3 | Dealer hole card | Revealed at play-out; the dealer hole card is not a 21+3 input (21+3 uses the upcard, cursor 1) (with cursors 0, 2) |
| 4 onward | Action cards | Hit cards, double cards, split sub-hand cards, dealer additional draws — consumed in order |
amount_won is the total return (stake-inclusive: e.g., a $0.01 win returns $0.02). For a doubled hand: win returns 4× original stake, push returns 2×, loss returns 0. Side bets are independent of the main outcome — a player can win the main bet and lose both side bets, or vice versa.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 the inputs that determine a result before the player bets
- A source of randomness exists that the casino cannot predict or control
- Anyone can verify the outcome after the fact
High-Level Overview8 sections▶
This section walks through how a Blackjack bet moves through the system end-to-end — from the server seed commitment, through the deal and player actions, to the post-rotation verification step that closes the loop.
Checklist Reference
Commit-Reveal
| Check | What it proves |
|---|---|
| Server seed committed before play | Casino cannot change outcome post-bet |
| SHA-256 of revealed seed matches committed hash | Revealed seed is the one that was actually committed |
| Hash identical across all bets in an epoch | All bets within a seed pair share the same commitment |
Seed System
| Check | What it proves |
|---|---|
| Nonces are sequential within an epoch | No nonce skipping or reuse |
| Single client seed per epoch | Client seed cannot be silently rotated mid-epoch |
| Next-seed pre-committed before rotation | Future commitment chain is locked in before reveal |
RNG & Parity
| Check | What it proves |
|---|---|
| Every visible card recomputes from seeds + nonce + cursor | Card stream is deterministic and verifiable |
| Independent verifier matches live API for every cursor | No hidden inputs to the RNG |
| Card sequence invariant across player actions | Cards fixed at deal time, not rewritten per action |
Payout & RTP
| Check | What it proves |
|---|---|
| Main payouts match rule set: win 2×, BJ 2.5×, doubled-win 4×, push 1× | Payout arithmetic matches published rules |
| Side-bet payouts match published multiplier table | Perfect Pairs + 21+3 returns are exact to API precision |
| Optimal-play RTP = 99.4296% from independent solver | RTP is mathematical, not empirical — cannot be silently inflated |
Integrity
| Check | What it proves |
|---|---|
| Wrong client seed produces different cards | Client seed genuinely influences outcome |
| Bet size does not influence card stream | Phase E $10 bets match Phase A-D $0.01 bets |
| No cherry-picking in seed selection (Pass 2) | Casino is not curating favourable seeds |
A Blackjack round runs through five stages — from the casino committing to a server seed before you play, through the deal and player actions, to the post-rotation verification that anyone can run.
1. Commit — Server generates server seed, publishes SHA-256 hash as `serverSeedHashed`. Player sees only the hash.
2. Seed pair activated — Client seed is browser-generated (or player-set). Nonce starts at 0. Next server seed is pre-committed.
3. Bet — Player places main bet (and optional Perfect Pairs / 21+3 side bets). Platform draws cards 0..3 (initial deal) via HMAC-SHA256 single-card draw using the active seed pair + nonce.
4. Actions — Player hits, stands, doubles, or splits. Each action consumes the next cursor from the same seed triple. Dealer reveals hole and plays out per S17 rule.
5. Rotate — On client-seed change or manual rotation, the active server seed is revealed. The next-committed seed becomes active.
6. Verify — Anyone with serverSeed, clientSeed, nonce, and the action sequence can recompute every visible card.
Blackjack is a commit-reveal provably fair game. The casino commits to a server seed by publishing its SHA-256 hash before any bet is placed. Once play on that seed is complete and the seed is rotated, the casino reveals the raw server seed. Anyone can then verify two things: (1) that the revealed seed hashes to the previously-published commitment, and (2) that every card for every bet in that epoch recomputes correctly from the seed triple. The model does not require trust in the casino — only that SHA-256 and HMAC-SHA256 are cryptographically sound, which is a well-established assumption.
Blackjack uses a four-phase commit-reveal cycle. Each phase has a single, well-defined purpose, and every phase produces artefacts that can be verified after the fact.
Commit phase
Server generates a 32-byte random server seed, computes `SHA-256(hex_bytes(serverSeed))`, and publishes the hash as `serverSeedHashed`. The raw seed is kept secret. A next-seed hash is also pre-committed.
Bet phase
Player places bets against the active seed pair. For each bet, every visible card is computed via `HMAC-SHA256(key=hex_bytes(serverSeed), msg=clientSeed:nonce:cursor)` followed by 4-byte chunk rejection sampling and modular reduction over 52. Nonce increments per bet.
Reveal phase
Player (or system) rotates the seed pair — typically by changing the client seed. The previously-active server seed is now revealed in the API. The pre-committed next-seed hash becomes the active `serverSeedHashed`.
Verify phase
Anyone with the seed triple can run HMAC-SHA256 + rejection sampling independently and confirm that every visible card matches the values returned by the live API. The commitment chain is preserved: revealed hash = prior commitment.
The client seed is the player's lever into the RNG. It is generated in the browser (not the server) and can be replaced at any time, which forces a seed rotation.
- Player-supplied — an alphanumeric string the player sets, submitted via the client-seed rotation endpoint
- Player-editable — the player can overwrite it with any value they choose; changing the client seed triggers a seed rotation and reveals the active server seed
- Materially influences outcome — we verified that changing
clientSeedto a random alternative produces different cards in 99 / 100 sampled bets at cursor 0 (matching the analytical baseline of 51/52 ≈ 98.08% — the 1/52 collision rate is expected when both seed-derived cards happen to map to the same card by coincidence) - Not known to the server at commit time — the server publishes
serverSeedHashedbefore your client seed is known, so it cannot cherry-pick a commitment that favours the casino against your specific seed
Nonce Lifecycle
The nonce is a per-bet counter within the active seed pair. It starts at 0, increments by 1 per bet, and resets only on seed rotation. The nonce is shared across all cursors in a single bet — every card in a hand uses the same nonce, with the cursor index distinguishing card positions.
- Starts at 0 on every new seed pair
- Increments sequentially per bet — no skipping, no reuse within an epoch
- Resets on rotation — every rotation produces a fresh nonce=0 sequence
- Shared across cursors — all 50 cursor positions in a single bet share the same nonce; the cursor index is appended to the HMAC message
// Per seed pair, nonce sequence:
// bet 1 → nonce=0 (cursors 0..49 share nonce=0)
// bet 2 → nonce=1 (cursors 0..49 share nonce=1)
// bet 3 → nonce=2
// ...
// On client seed change:
// serverSeed revealed, new pair activated, nonce resets to 0Determinism Guarantee
Given serverSeed, clientSeed, nonce, and cursor, the card at that cursor is fully determined — there is no randomness, no hidden input, and no dependence on bet amount, timestamp, or any other variable. The pipeline is:
// Inputs: serverSeed (hex), clientSeed (string), nonce (int), cursor (int 0..49)
key = Buffer.from(serverSeed, 'hex') // hex-decoded bytes, NOT utf-8
message = `${clientSeed}:${nonce}:${cursor}`
hash = HMAC-SHA256(key, message)
// Read 32-byte hash as 8 big-endian uint32 chunks; first chunk < MAX_FAIR accepted
MAX_FAIR = 52 * floor(2^32 / 52) // 4,294,967,248
for chunk in chunks_of_4_bytes(hash):
value = uint32_be(chunk)
if value < MAX_FAIR:
cardIndex = value % 52
return CARDS[cardIndex]
throw 'all 8 chunks rejected' // probability ≈ 1.12 × 10⁻⁸The audit captured 6,000 bets across six phases, each designed to stress a specific property of the system.
| Phase | Bets | Stake | Purpose |
|---|---|---|---|
| A | 3,300 | $0.01 main + $0.01 PP + $0.01 21+3 | Bulk capture: dealer-rule, payout, side-bet coverage |
| B | 1,000 | $0.01 main + $0.01 PP + $0.01 21+3 | Continued bulk capture for distribution coverage |
| C | 500 | $0.01 main only (no side bets) | Side-bet independence — confirms main-bet outcomes are unaffected by side-bet stake presence |
| D | 500 | $0.01 main + $0.01 side bets (10 auditor-controlled client seeds, `pfaudit_bj_seed00..09`) | Client-seed influence test — alternate seed produces different cards (Step 6, Step 15) |
| E | 200 | $10.00 main only (no side bets) | Bet-size invariance — verifies $10 stakes produce identical RNG behaviour to $0.01 stakes (Step 9) |
| F | 500 | $0.01 main + $0.01 PP + $0.01 21+3 | Trailing capture for split-bet coverage and convergence |
Technical Glossary7 categories▶
Terms and definitions used throughout this audit report, grouped by category.
| Term | Definition |
|---|---|
| Provably Fair | A system where every game outcome can be independently verified from public cryptographic inputs, without trusting the operator. |
| Commit-Reveal | A two-phase protocol where a party publishes a hash of a secret before play (commit), then reveals the secret afterward (reveal). The hash proves the secret was fixed in advance. |
| Determinism | A property where the same inputs always produce the same output. Blackjack is deterministic: `(serverSeed, clientSeed, nonce, cursor)` uniquely determines the card. |
| Epoch | The set of bets placed under a single active seed pair, from activation to rotation. The auditor chose to rotate every 50 bets to bound per-seed-pair exposure for analysis (50 is auditor methodology, not platform-enforced). |
| Term | Definition |
|---|---|
| Server Seed | A 32-byte random value generated by the casino. Represented as a 64-character hex string. Used as the HMAC key (hex-decoded, NOT utf-8). |
| Server Seed Hashed | `SHA-256(hex_bytes(serverSeed))`. The commitment published before play. |
| Client Seed | A player-controlled alphanumeric string, browser-generated by default. Part of the HMAC message. |
| Nonce | A per-bet counter within the active seed pair. Starts at 0, increments per bet, shared across all cursors within a single bet, resets on seed rotation. |
| Cursor | The card-position index appended to the HMAC message. Cursors 0-3 form the initial deal (P1, dealer up, P2, dealer hole); cursors 4 onward are consumed in order as actions are taken. |
| Seed Pair | The combination of one server seed and one client seed. A seed pair defines an epoch. |
| Next Server Seed Hash | A pre-commitment to the server seed that will become active after the current one is rotated. Locks the future chain in advance. |
| Term | Definition |
|---|---|
| SHA-256 | A 256-bit cryptographic hash function. Used here for server seed commitment: `SHA-256(hex_bytes(serverSeed)) = serverSeedHashed`. |
| HMAC-SHA256 | Hash-based message authentication code built on SHA-256. Takes a key and a message, returns a 256-bit (32-byte) digest. Used here for per-cursor card draw. |
| Single-Card Draw | The Blackjack RNG primitive: `getCard(serverSeed, clientSeed, nonce, cursor)` runs HMAC-SHA256 once per cursor and returns one card. Each cursor is independent (infinite-deck model). |
| Rejection Sampling | A technique for bias-free uniform sampling. A 4-byte chunk is accepted only if it falls below `MAX_FAIR = 52 × ⌊2³² / 52⌋ = 4,294,967,248`. Guarantees every card has probability exactly 1/52. |
| MAX_FAIR | The bias-free threshold: `4,294,967,248`. Per-chunk rejection probability is `48 / 2³² ≈ 1.12 × 10⁻⁸`. With 8 chunks per HMAC, the probability of all chunks rejecting is `≈ 5 × 10⁻⁶²` — never observed in practice. |
| Term | Definition |
|---|---|
| Parity | Agreement between the live game's reported outcome and an independent recomputation from the same inputs. 100% parity means zero card mismatches across all cursors. |
| Recomputation | Running the audit's standalone RNG implementation on captured seed triples and comparing every visible card to the live API response. |
| Anti-Circularity Proof | A proof that RTP is not an empirical observation but the output of an independent recursive infinite-deck EV solver. The solver consumes only the rule set + 13-rank distribution; no casino-supplied figure feeds in. |
| Cherry-Pick Detection (Pass 2) | A statistical test over revealed casino seeds that checks whether per-seed return distributions show systematic skew — the signature of seed pre-selection. Pass 2 result: 11 / 120 seeds flagged at α=0.05 (binomial p=0.038450), above the α=0.01 dataset reject threshold. |
| Optimal-Play EV Solver | The recursive infinite-deck expected-value enumerator in `src/optimal-play.ts`. Computes, for every initial (P1, P2, dealer-up) triple, the maximum-EV action sequence (hit/stand/double/split) under Duel's exact rule set. Returns the analytical RTP of 99.4296%. |
| Term | Definition |
|---|---|
| S17 | The dealer rule "stand on all 17" — including soft 17 (Ace counted as 11). Verified across 75 in-range played-out hands (Step 19). |
| DAS | "Double after split" — the player may double on a split sub-hand. Allowed in Duel's rules and confirmed in the EV solver. |
| Soft Hand | A hand containing an Ace counted as 11 (e.g., A-6 = 17 soft). If hitting would bust, the Ace counts as 1 instead (becomes a hard hand). |
| Natural Blackjack | A two-card 21 — Ace + 10-value (10, J, Q, K) on the deal. Pays 3:2 (return = 2.5× stake) unless the dealer also has blackjack (push). |
| Insurance | An optional side wager offered when the dealer's upcard is an Ace. The player wagers up to half the main bet; pays 2:1 if the dealer has blackjack. The audit confirms insurance is offered if and only if the upcard is an Ace (Step 25). |
| Perfect Pairs | A side bet on the player's two dealt cards (cursors 0, 2). Pays 27× for matching rank + suit, 11× for matching rank + colour, 7× for matching rank + different colour. Expected value is exactly 0 by algebraic identity ((27+11+14)/52 = 52/52). |
| 21+3 | A side bet on the player's two cards plus the dealer upcard (cursors 0, 2, 1). Pays 121.2307692× (suited trips), 53× (straight flush), 32× (three of a kind), 12× (straight), 5× (flush). Expected value is 0 to within 7-decimal multiplier rounding. |
| Term | Definition |
|---|---|
| Fairness Integrity (FI) Matrix | The standard test matrix applied to every audit, covering nonce integrity, seed commitment, outcome determinism, player isolation, payout integrity, and game-state integrity. For Blackjack: 17 tests (16 standard + 1 BJ-specific bonus race-condition probe). Documented in S5. |
| FLAG | A severity level for anomalies disclosed transparently with no observed operational effect on gameplay. FLAG findings do not block certification. |
| Hard Fail | A severity level for findings that invalidate the fairness guarantee. Any single hard fail blocks certification until remediated. Zero hard fails in this audit. |
| Bonferroni Correction | A multiple-comparison adjustment: when testing N hypotheses at significance α, the per-test threshold is α/N. Applied to per-stream serial-independence tests in Pass 1 (10 streams, α=0.01 → per-stream α=0.001, z-critical ≈ 3.291). |
| Fisher's Combined p-value | A meta-analytic combination of independent p-values: `T = -2 × Σ ln(p_i)` follows χ²(2K). Used in Pass 1 to combine 10 per-stream p-values into a single dataset-level test (combined p = 0.686432, no concentration in either tail). |
| Point-in-Time Audit | An audit verdict that applies to the code and configuration in force at the audit date. Subsequent changes are outside scope unless re-certified. |
| Term | Definition |
|---|---|
| blackjack-dataset-6000hands.json | Primary capture dataset. 6,000 bets across six phases (A: 3,300, B: 1,000, C: 500, D: 500, E: 200, F: 500), 120 epochs, 33,194 card cursors. SHA-256 pinned in S1 and S7. |
| verification-results.json | Output of the 33-step verification pipeline. Contains per-step pass/fail status and per-step detail strings. |
| simulation-results.json | Output of the 11.2M-round simulation: optimal-play RTP solver, Pass 1 (10M rounds, 10 streams Fisher-combined), Pass 2 (120 captured seeds × 10K nonces, cherry-pick detection). |
| rtp-convergence.html | Self-contained interactive RTP convergence chart for Pass 1 — viewable in any browser, no external dependencies. |
Every Blackjack hand 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 cards 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
- Players can set or change their client seed at any time via the rotation UI
- Nonce increments by 1 per bet across all 120 seed pairs
- Every visible card — initial deal, hits, doubles, splits, dealer play-out — is determined by `(serverSeed, clientSeed, nonce, cursor)` before any animation plays
- Identical inputs always produce the same card stream — confirmed across all 6,000 hands (33,194 card cursors)
- Your client seed is a genuine input — changing it changes the cards
What This Means for You
- The casino cannot change your hand 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 a card stream within a seed pair
- Any hand can be independently verified using the public tools and repo
- Outcomes are tamper-proof and verifiable even months later
- Cherry-picking favourable seeds is structurally impossible
| Test | Status | Finding |
|---|---|---|
| Server seed committed before bet | Pass | SHA-256 hash of server seed published before play — casino cannot change cards after betting |
| Client seed origin | Pass | Player-controlled — 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 120 seed pairs |
| Hash consistency within seed pair | Pass | server_seed_hashed constant across all bets within each of 120 seed pairs |
| Seed hash integrity | Pass | 120 / 120 revealed seeds hash-verified — commitment chain intact |
| Deterministic output | Pass | Same (serverSeed, clientSeed, nonce, cursor) always produces same card — 6,000 / 6,000 hands recompute (33,194 card cursors verified, 0 mismatches) |
| Client seed participation | Pass | Client seed is a genuine input — changing it changes the deck |
All 120 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, nonce, and cursor always produce the same card. The casino cannot change your hand after you bet.
This section verifies that Duel.com's Blackjack random number generation produces cryptographically sound, unbiased outputs using only the disclosed inputs. The RNG uses HMAC-SHA256 single-card draw — for each cursor in the 50-card stream, the HMAC produces 32 bytes read as 8 big-endian uint32 chunks; the first chunk below the MAX_FAIR ceiling (4,294,967,248) is taken modulo 52 to index into the canonical 52-card array. We independently implemented this algorithm, verified it produces the same cards as the live game, and confirmed no hidden inputs can influence outcomes.
What We Verified
- HMAC-SHA256 produces cryptographically sound, unpredictable output for each card draw
- Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
- Rejection sampling via the `MAX_FAIR` ceiling eliminates modulo bias for the 52-card range
- 10 million simulated rounds match the analytical optimal-play RTP under the disclosed rules
- Empirical infinite-deck confirmation: 1,354 of 6,000 hands contain ≥2 visible cards with identical rank+suit (impossible under any finite-deck model)
- Consecutive outcomes are statistically independent — no patterns, no streaks
- 99% of cards diverge with a different client seed (99/100 tested bets)
What This Means for You
- Each card is generated fairly and cannot be skewed
- All 52 cards are equally likely on every draw — no positional bias
- No hidden randomness or server-side tricks influence which cards appear
- Consecutive bets are not correlated — past results don't affect future outcomes
- The algorithm depends only on seeds you can verify
| Test | Status | Finding |
|---|---|---|
| RNG derived only from disclosed inputs | Pass | HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce:cursor) — no hidden entropy |
| Entropy purity | Pass | No timestamps, external APIs, Math.random, or server-side state |
| Algorithm independently implemented | Pass | Independent implementation produces identical results for all 6,000 bets (33,194 card cursors) |
| Modulo bias | Pass | Rejection sampling at MAX_FAIR = 4,294,967,248 — every card has probability exactly 1/52 |
| Key encoding verified | Pass | Server seed hex-decoded to bytes (not UTF-8) — confirmed via 6,000-bet recomputation |
| Analytical RTP convergence | Pass | 10M-round simulation converges to the optimal-play RTP across 10 streams × 1M rounds |
| Infinite-deck model | Pass | 1,354 / 6,000 hands contain ≥2 cards with identical rank+suit — only possible under infinite-deck draw with replacement |
| Serial independence | Pass | Lag-1 autocorrelation near zero and runs tests pass across all 10 streams at 1M rounds each |
| Client seed influence | Pass | 99% of cards diverge under a wrong client seed — confirmed across 100 sampled bets |
The Blackjack RNG uses only the disclosed inputs, produces cards with bias-free 1/52 probability, converges to the analytical optimal-play RTP across 10M simulated rounds with no serial dependence, and confirms infinite-deck behaviour. The client seed is a genuine input — changing it changes the cards.
This section validates that the independent verifier produces the exact same cards as the live game for every cursor in every bet. Any mismatch would invalidate the fairness guarantee. We verified all 33,194 card cursors across 6,000 hands — strict ordering for non-split hands, multiset ordering for splits. The section also confirms that each hand outcome is correctly resolved against Duel's published rule set and every payout — including the 3:2 natural and both side bets — matches the posted pay table.
What We Verified
- Every bet independently recomputed from seeds — every visible card verified, not just the payout
- All 33,194 card cursors (across 6,000 hands · 269 splits · 1,368 doubled hands) match strict cursor ordering or the multiset check
- Hand classification: every win / lose / push / bust / blackjack outcome recomputes from the cards
- Payout correctness: `amount_won = main + side bet wins`, exact for all 6,000 bets (±1×10⁻⁶ tolerance)
- Side-bet multiplier provenance: 5,800 PP + 5,800 21+3 classifications match independent re-evaluation from cursors 0, 2, 1
- Bet amount is not an input to the RNG — cards depend only on seeds, nonce, and cursor
- The player's actions (hit/stand/double/split) cannot change the cards — they only determine how many cursors are consumed
What This Means for You
- The verifier isn't a simulation — it produces the exact same cards, hand resolutions, and payouts as the live game
- Every bet you play can be independently recomputed by anyone after seed rotation
- No hidden logic alters outcomes based on how much you bet, what your client seed is, or which actions you take
- The game engine in production matches the published algorithm exactly
| Test | Status | Finding |
|---|---|---|
| Card stream recomputation | Pass | 6,000 / 6,000 bets · 33,194 cursors recompute byte-equal from (serverSeed, clientSeed, nonce, cursor) |
| Hand classification | Pass | All 6,000 bets: win / lose / push / bust / blackjack outcome reconciles from independent hand-value evaluator |
| Payout correctness | Pass | All 6,000 bets: amount_won = main return + side-bet wins, tolerance ±1×10⁻⁶ |
| Pay table integrity | Pass | Main payout tiers (win 2× · BJ 2.5× · push 1× · double 4×) + 5,800 PP + 5,800 21+3 multipliers match config for every bet |
| Bet-size independence | Pass | Phase E (200 bets at $10/hand) — same algorithm produces cards regardless of bet amount; getCard() has no wager parameter |
| Auditor seed coverage | Pass | Phase D (500 bets across 10 auditor seeds) — auditor-controlled seeds recompute byte-equal |
| Action coverage | Pass | All 14,313 available_actions lists are subsets of {hit, stand, double, split, insurance, no_insurance}; 0 surrender occurrences |
All 6,000 bets matched the independent verifier exactly — every visible card, every hit / double / split decision, every dealer play-out, every win / lose / push outcome, and every payout (main + side bets) reproduces from (serverSeed, clientSeed, nonce, cursor) across all six capture phases. Every cursor consumed by the bet is recoverable from the recorded inputs.
This section verifies the game's RTP and house edge exactly, and confirms every payout follows the published rules. The headline optimal-play RTP of 99.4296% (a 0.5704% house edge) is proven from first principles by an independent strategy solver — the optimal decision is enumerated for every player hand against every dealer up-card, with no operator-supplied figure entering the chain. It cross-validates against Wizard of Odds for Duel's exact rule set and is corroborated by 10 million simulated rounds. Every captured hand's payout reconciles against the published rules — including 3:2 naturals, doubled-hand settlement, and side-bet EV — and cherry-pick detection replays all 120 committed server seeds to rule out seed pre-selection.
What We Verified
- House edge is 0.5704% under Duel's rule set (S17, DAS, no surrender, no re-split) — flat across all bet sizes
- Optimal-play RTP 99.4296% proven from first principles: independent recursive infinite-deck EV solver consuming only the disclosed rules
- Cross-validated against Wizard of Odds for Duel's exact rule set — match to <1×10⁻⁶
- 10M simulated rounds converge to the analytical 99.4296% target — no drift across 10 independent streams
- Cherry-pick detection across all 120 captured server seeds — no evidence of seed pre-selection
- Doubled-hand payouts reconcile across 1,291 doubled bets (1,368 doubled hands · win 4× · push 2× · lose/bust 0)
- Natural blackjack pays 3:2 — 260 / 260 player naturals paid correctly
- Side bets: Perfect Pairs and 21+3 are both zero-EV — 11,600 active side-bet payouts reconcile
What This Means for You
- The 0.5704% figure is an expected-value average over many hands, not a per-hand deduction
- The 99.4296% claim is the optimal-play RTP — players who deviate from basic strategy earn less
- 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
- The side bets carry no built-in edge either way — they don't improve or worsen your expected return
- Your bet amount doesn't affect which cards are dealt
| Test | Status | Finding |
|---|---|---|
| Anti-circularity | Pass | Solver reads only rule constants and 13-rank infinite-deck probabilities — no casino-supplied RTP or edge values feed in |
| Optimal-play RTP solver | Pass | 99.4296% from independent recursive infinite-deck enumeration |
| Solver cross-validation | Pass | Cross-validated against Wizard of Odds for Duel's exact rule set — match to <1×10⁻⁶ |
| House edge audit | Pass | 0.5704% — flat across all bet sizes, no scaling structure |
| Simulated RTP (Pass 1) | Pass | 10M rounds converge to 99.4670% (Fisher's p = 0.686432; 0/10 streams reject independence) |
| Cherry-pick detection (Pass 2) | Pass | 11/120 flagged at α=0.05 (binomial p = 0.038450 — marginal under a single-test reading; not significant after multiple-comparisons correction) |
| Pay table integrity | Pass | All payout tiers (main + 5,800 PP + 5,800 21+3) match the deployed multiplier table for every bet |
| Side-bet EV | Pass | Perfect Pairs EV = 0 by algebraic identity; 21+3 EV ≈ 0 by table balance |
Both the optimal-play RTP and house edge are verified. Independent first-principles enumeration produces 99.4296%, cross-validated against Wizard of Odds for Duel's exact rule set. 10 million simulated rounds converge to that target without surprise. Cherry-pick detection across all 120 captured server seeds finds no evidence of seed pre-selection. The house edge is 0.5704% — flat across all bet sizes, fixed by the rules and not tunable via per-bet metadata.
Sections 1–4 prove the game is mathematically fair. Section 5 proves the implementation maintains integrity under non-standard conditions. We applied 17 fairness integrity tests covering nonce integrity, seed commitment, outcome determinism, cross-player isolation, payout integrity, and Blackjack-specific multi-step game-state checks. 16 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, card, or hand-total values be injected client-side?
- Parameter limits — can invalid bet amounts or actions be submitted?
- Blackjack-specific — do consecutive `hit` actions advance the cursor correctly? Do parallel hits get rejected?
What This Means for You
- Across the 17 tests we ran, no API path allowed outcomes to be altered, replayed, or injected — by player or casino
- Once a bet is placed, the 50-card sequence 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
| Test | Status | Finding |
|---|---|---|
| Nonce integrity | Pass | Sequential, server-controlled, no gaps or duplicates across 120 primary seed pairs · 7/7 invalid-nonce injections silently ignored, server-assigned nonces continued (98 → 106) |
| Seed commitment integrity | Pass | Locked at bet acceptance, unique per seed pair — 120/120 primary seed pairs verified · 6/7 adversarial client seeds rejected (HTTP 422); oversized accepted (permissive but safe); 0 cross-account hash collisions across 20 rotations |
| Outcome determinism | Pass | Identical inputs produce identical card stream — 6,000 / 6,000 hands recomputed (33,194 cursors); decision-replay coverage provided by FI-BJ-ACTION-001/002 |
| Round & player isolation | Pass | Per-user seeds, serial independence confirmed (10M-round simulation, Fisher's p = 0.686432, 0 / 10 streams reject) |
| Payout integrity | Pass | Invalid bet amounts rejected (3/3 — negative, oversized, non-numeric); injected payout fields ignored (0/4 honoured); replayed and parallel hit requests correctly handled by a deterministic idempotent response — no extra card drawn and no payout change |
17 fairness integrity tests: 16 PASS + 1 N/A. Two of the passes concern replayed and concurrent hit requests, which the server correctly handles by returning a deterministic idempotent response — identical hand data, no card drawn, no payout effect. Idempotent handling of duplicate requests is the robust, expected behaviour.
Every Blackjack outcome can be independently reproduced using publicly disclosed inputs. No hidden variables, no private backend data. If your computed card stream matches the cards you saw, 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 Blackjack outcome can be independently reproduced
- No hidden variables — no private backend data
- If your computed card stream matches the cards you saw, the bet was provably fair
- Most players can verify directly through the Duel.com fairness UI
What You Need
- Server Seed — revealed after seed rotation (casino entropy)
- Client Seed — your player-controlled seed
- Nonce — the bet number in sequence (ensures uniqueness; increments within each seed pair)
- Player actions — the sequence of hit / stand / double / split decisions you made (the verifier replays them against the card sequence)
Only disclosed inputs are used. Identical inputs always produce identical cards. Your hit / stand / double / split decisions consume cards from a pre-determined sequence — they cannot change the cards.
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-blackjack
- Commit: bff0ef5f791208731f9ef113dabb506204b3dbb0
- Game: Blackjack (duel-blackjack)
- Public Verifier: audit.provablyfair.org/casino/duel/tools/verify-bets
Prerequisites
- Node.js 18+
- npm 8+
- Git
- TypeScript (installed via npm)
Repository Structure
Commands to Reproduce
Audit Reproducibility Pinning
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,000 bets across 33,194 card cursors.



