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: 16 scored verification steps, 5M simulated rounds, 1,350 live bets re-verified, all 1,350 drand signatures matched byte-for-byte against the public drand chain.
Castle Roulette Audit Overview
This audit independently validates the Castle Roulette 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 1,350 real bets across five capture phases and independently verified every single wheel position using our own implementation of the algorithm — then re-fetched all 1,350 drand beacon signatures directly from the public drand chain to prove the operator did not fabricate external-entropy values.
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
- Every round uses external entropy from the drand quicknet public randomness beacon as its second input
- Server seeds are unique per round — no reuse, no chaining across rounds
- Wheel positions are computed via HMAC-SHA256 over the server seed and drand randomness, mapped to one of 48 positions via value mod 48
- Wheel positions are reproducible from (serverSeed, drandRandomness) — verified on all 1,350 rounds
- Every bet was placed before its round's drand beacon was published — pre-commitment proven for all 1,350 rounds
- Payout logic matches the zero-edge color-tier formula exactly — amount_won = stake × multiplier on wins (multiplier from the 6-tier table), 0 on losses
- Theoretical RTP is 100.0000% across every color tier — zero edge by construction, anti-circularity proven analytically
- Bet amount does not influence the RNG or the wheel position
- All players in a round share the same wheel position — no per-player RNG path
- Players can independently verify every bet using only public inputs
What Audit Covers
| Area | Description |
|---|---|
| Commit-Reveal System | SHA-256 seed hash committed before round, drand beacon published after commitment, server seed revealed after round |
| External Entropy | drand quicknet beacon (chain 52db9ba7…4e971, 3-second period) — second cryptographic input to every round |
| Pre-Commitment Timing | Authoritative proof: bet placed before drand published, via transactions-API timestamp + drand chain formula |
| RNG Analysis | HMAC-SHA256 outcome derivation, uint32 modulo-48 mapping, modulo bias analysis |
| Payout Logic | Zero-edge color-tier verification, bet-size invariance, win-condition logic across all 6 tiers |
| Live Parity | Independent wheel-position recomputation vs. live game results for every captured round |
| RTP Validation | Anti-circularity formula proof (P × multiplier = 1.000 per tier), multi-stream simulated RTP (5M rounds), cherry-pick detection (Pass 2) |
| Fairness Integrity | Standard 12-test integrity matrix + drand-specific adversarial tests (timing, prediction, round shopping) |
What Audit Guarantees
- Wheel positions are deterministic and reproducible from (serverSeed, drandRandomness) — verified on all 1,350 live bets
- Every bet landed on the operator's database before its round's drand beacon was published on the public chain (minimum observed margin: 14.763s, mean: 20.067s, max: 20.405s)
- All 1,350 drand signatures in the dataset match signatures independently re-fetched from the public drand chain (api.drand.sh, quicknet chain)
- The wheel-position distribution follows the uniform model — verified by 5M simulated rounds and Fisher's combined test
- The house edge is exactly 0% — proven analytically from the 6-tier payout table where count(tier) × multiplier(tier) = 48 for every tier
- drand round IDs are strictly increasing across all 1,350 rounds — no round shopping, no reuse
- Server seeds are unique across all 1,350 rounds — no reuse, no collision
- All 15 standard fairness integrity tests addressed at audit time — 14 pass, 1 N/A (player actions cannot influence the wheel position — global outcome model) — no hard failures
What Audit Excludes
- Infrastructure or server security
- Wallet, payments, or operational systems outside game logic
- Bankroll-scaled house edge — operator-disclosed via the metadata endpoint and live UI; the captured rounds settled at zero edge under the Zero Edge allowance and the scaled regime was not exercised
- Cross-account sampling
- Max win cap enforcement — not embedded in game logic
References
Castle Roulette — Game Rules7 sections▶
Castle Roulette is a multiplayer wheel game with 48 positions and 6 color tiers. At the start of each round, the server commits to a server seed and pairs it with a drand public randomness beacon to determine a single wheel position (0-47). Players choose a color before the spin — if the resolved position belongs to that color tier, the bet wins at the tier's fixed multiplier. Every player in a round shares the same wheel position; the round is a single shared outcome. The 6 tiers are sized so that count × multiplier = 48 in every case, which means every color bet has an expected value of exactly 1.000 — the house edge is 0%.
1. Place your bet — Enter a stake and choose one of the 6 color tiers: Green (48×), Red (24×), Purple (16×), Blue (8×), Grey (4×), or Dark Blue (2×). Multiple players can bet on different colors in the same round.
2. Round opens — The operator has already committed to a server seed (via SHA-256 hash) and assigned a drand round ID for this round.
3. Betting closes — No more bets accepted for this round.
4. drand beacon publishes — The public drand chain publishes the BLS signature for the committed drand round. The server seed and drand randomness together determine the wheel position.
5. Wheel resolves — The position is computed and the wheel animation lands on it. The position belongs to exactly one color tier.
6. Outcome — If the resolved position's color matches your bet, you win `stake × multiplier`. If not, you lose the stake.
7. Verify — After the round ends, take the revealed server seed, the drand signature (which you can independently fetch from api.drand.sh), and the formula and reproduce the wheel position yourself.
A Castle Roulette bet wins if the resolved wheel position (0-47) belongs to the player's chosen color tier. The payout is stake × multiplier, where the multiplier is fixed per tier. There are no partial wins — the outcome is binary per bet.
| Outcome | Condition | Example (stake $1, Dark Blue 2× bet) |
|---|---|---|
| Win | Position belongs to chosen color tier | Position = 27 (Dark Blue range 24-47) → payout = $1 × 2 = $2.00 |
| Loss | Position belongs to a different color tier | Position = 5 (Purple, not Dark Blue) → payout = $0 (stake lost) |
Castle Roulette's risk curve is controlled by which color the player chooses. Rarer colors have lower win probability but pay larger multipliers, and the six tiers are sized to produce identical expected value — every tier returns 100% in expectation.
- Dark Blue (2×) wins half the time — 24 of 48 positions belong to Dark Blue, so P(win) = 50.00%. This is the lowest-variance bet — frequent small wins.
- Green (48×) wins about once in 48 spins — Only position 0 is Green, so P(win) = 2.08%. This is the highest-variance bet — rare large wins.
- EV is constant — Every color tier returns exactly 1.000 in expectation (100% RTP). No tier is mathematically better or worse than any other in expected value.
- Variance scales inversely with win probability — Rarer-tier bets produce higher variance in the short run; short-horizon empirical RTP may swing far from 100% (see S4).
| Parameter | Value | Notes |
|---|---|---|
| Wheel Positions | 48 (0-47) | Fixed wheel, deterministic position-to-color mapping |
| Color Tiers | 6 | Green, Red, Purple, Blue, Grey, Dark Blue |
| Multiplier Range | 2× to 48× | Fixed per tier |
| House Edge | 0% | Zero edge by construction — every tier has EV = 1.000 |
| Theoretical RTP | 100.0000% | Formal proof from `count × multiplier = 48` per tier |
| RNG Algorithm | HMAC-SHA256 | Combines server seed bytes (key) with drand randomness (message) |
| External Entropy Source | drand quicknet | 3-second publishing period; chain `52db9ba7…4e971` |
| Round Structure | Multiplayer shared outcome | All players in a round share one (serverSeed, drandRoundId) pair |
Every Castle Roulette round uses two cryptographic inputs — a server seed committed by the operator and a drand beacon from the public randomness chain. Castle Roulette does not use a player-contributed client seed; the drand beacon provides the external entropy in its place.
| Seed Type | Format | Example (round 538493) | Purpose |
|---|---|---|---|
| Server Seed | 64-char hex (32 bytes) | `adace83dfe4957273…2420eb26` | Operator-provided randomness, revealed after the round ends |
| Server Seed Hash | 64-char hex SHA-256 | `ae7a5f98e2f47850…d18baaaa` | Published before round opens — commits the operator to the seed |
| drand Round ID | Integer | `28007049` | Identifies the specific drand beacon used by this round |
| drand Randomness | 96-char hex (BLS signature) | `8d59f6f2c17c6c3a…cd6621d` | Public randomness, fetched from the drand quicknet chain |
Castle Roulette uses a fixed payout table — every wheel position maps to one of 6 color tiers, each with a fixed multiplier. The wheel position itself is computed directly from a cryptographic formula, and the payout is stake × multiplier if the position's color matches the bet.
uint32 value = first 4 bytes of HMAC-SHA256(serverSeed_bytes, drandRandomness:0) as integer
position = value % 48 (0 to 47)
color, mult = PAYOUT_TABLE[position] (table lookup, 6 tiers)
If color matches the player's bet: amount_won = stake × mult
Otherwise: amount_won = 0| Color | Positions | Count | Multiplier | P(win) | Payout / $1 | RTP |
|---|---|---|---|---|---|---|
| Green | 0 | 1 | 48× | 2.08% | $48.00 | 100.0% |
| Red | 1-2 | 2 | 24× | 4.17% | $24.00 | 100.0% |
| Purple | 3-5 | 3 | 16× | 6.25% | $16.00 | 100.0% |
| Blue | 6-11 | 6 | 8× | 12.50% | $8.00 | 100.0% |
| Grey | 12-23 | 12 | 4× | 25.00% | $4.00 | 100.0% |
| Dark Blue | 24-47 | 24 | 2× | 50.00% | $2.00 | 100.0% |
count × multiplier = 48 for all six tiers — the wheel's structure guarantees P(color) × multiplier = 1.000 exactly. Castle Roulette has no edge factor in the formula. Modulo bias from value % 48 is negligible: positions 0-15 are favored by ~2.33 × 10⁻¹⁰ per position, undetectable at any practical sample size.Castle Roulette is not a per-player game like Plinko or Dice. The randomness is global to the round.
- One server seed per round — the operator commits a fresh server seed at round-open time
- One drand beacon per round — the round uses one specific drand round ID, published on the drand chain's fixed schedule
- One wheel position per round — computed once from
(serverSeed, drandRandomness)and shared by every player in the round - Per-player outcomes — each player's win/loss depends only on which color they bet on; the underlying position is identical for everyone
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 Overview7 sections▶
Checklist Reference
Based on the ProvablyFair.org Audit Execution Checklist, here are the tests covered under this audit document.
| Test | Description |
|---|---|
| Server seed commit exists before round | SHA-256 hash of server seed published before the drand beacon for the round is available |
| Server seed reveal matches commit | `SHA-256(hex_decode(serverSeed)) = serverSeedHash` for all 1,350 rounds |
| External entropy source is public | drand quicknet beacon — independently fetchable from api.drand.sh |
| Server seed uniqueness | 1,350 unique server seeds across 1,350 rounds — no reuse, no chain, no cursor |
| drand round monotonicity | drand round IDs strictly increasing across all rounds — no round shopping |
| Full determinism | Same (serverSeed, drandRandomness) → same wheel position |
| Test | Description |
|---|---|
| RNG depends only on server seed + drand randomness | No client seed, no nonce, no timestamp, no hidden inputs |
| drand is the external entropy source | Public randomness beacon, cryptographically signed, independently verifiable |
| No mixed entropy sources | No `Math.random`, no system clocks, no other RNG paths |
| drand signature authenticity | 1,350 / 1,350 drand signatures in the dataset match signatures fetched directly from api.drand.sh |
| Test | Description |
|---|---|
| Bet placed before drand published | For every round, the operator's signed `timestamp_raw` from the transactions API is earlier than the drand beacon's publish time (computed from the public chain formula) |
| Timing proof uses authoritative sources | Both endpoints of the margin are independently re-derivable — no trusted auditor clock |
| Every round satisfies the inequality | 1,350 / 1,350 rounds with positive pre-commitment margin (minimum: 14.763s) |
| Test | Description |
|---|---|
| Live outcomes match verifier | 1,350 / 1,350 wheel positions recomputed — 0 mismatches |
| Multi-phase verification | Phase A (baseline 2× Dark Blue), Phase B (all 6 colors rotating), Phase C ($1 stakes), Phase D (16× Purple), Phase E (48× Green) |
| Bet-size invariance | $1 bets produce the same wheel positions as $0.01 bets |
| Test | Description |
|---|---|
| Anti-circularity proof | RTP computed directly from the 6-tier payout table — `count × multiplier = 48` for every tier, yielding `P(color) × multiplier = 1.000` exactly |
| House edge audit | Zero edge by construction — every color tier returns 100% in expectation, no edge factor in the formula |
| Payout rules correctness | `amount_won = stake × multiplier` on wins, 0 on losses, for all 1,350 bets |
| Simulated RTP convergence | 5M rounds (10 streams × 500K) converge on theoretical 100.0% via Fisher's combined test (T = 15.09, p = 0.7712, 0/10 streams below α = 0.01) |
| Cherry-pick detection | Pass 2 — 15 chi² fails across 1,350 casino seeds (threshold ≤ 25 under H₀) — no evidence of seed pre-selection |
To get an overview of how a single Castle Roulette round works, here is a high-level breakdown:
1. Round Opens — Operator commits to a server seed (publishes its SHA-256 hash) and assigns the round a drand round ID from the upcoming quicknet schedule
2. Players Bet — Each player chooses a color and stake; bets are accepted before the committed drand round publishes
3. Betting Closes — No more bets for this round
4. drand Publishes — The drand quicknet chain publishes the beacon for the committed round ID (BLS signature, ~3 seconds after the previous beacon)
5. Wheel Position Computed — `HMAC-SHA256(serverSeed_bytes, drand_utf8:0)` → first 4 bytes as uint32 → `value % 48` → position 0-47
6. Wheel Resolves — On-screen wheel animation lands on the computed position; the position's color tier determines who won
7. Payout — For each player whose color matches the resolved tier, pay `stake × multiplier`; otherwise stake lost
8. Reveal — Operator reveals the plaintext server seed; player can recompute the wheel position from (serverSeed, drandRandomness) and verify
Provably fair gambling systems use cryptographic primitives to guarantee the integrity of outcomes. Castle Roulette's model relies on three components: a server seed committed via SHA-256 hash before the round's external-entropy source is available, a public randomness beacon (drand quicknet) whose publication time is independently verifiable, and HMAC-SHA256 as the deterministic function that combines them into a wheel position. Because the operator commits to the server seed before the drand beacon for the round is available, and because anyone can independently verify both the SHA-256 commitment and the drand signature, the operator cannot retroactively choose a server seed to produce a favorable outcome.
The Commit-Reveal model for Castle Roulette spans four phases, with an additional external-entropy step sitting between the commitment and the reveal:
Commit Phase:
Before the round's drand beacon is available, the operator generates a server seed and publishes its SHA-256 hash (`serverSeedHash`) alongside the round's assigned drand round ID. Only the hash is sent to the player — the seed itself stays hidden. Bets are accepted during this phase.
drand Phase:
The drand quicknet chain publishes the beacon for the committed drand round on its fixed 3-second schedule. This step is independent of both the operator and the player — drand's publishing time and content are cryptographically fixed and cannot be manipulated by either party.
Compute Phase:
Once the drand beacon publishes, the operator combines the server seed with the drand randomness via HMAC-SHA256 to produce the wheel position. The wheel animation begins.
Reveal Phase:
After the round ends, the operator reveals the plaintext server seed. Any player can now verify `SHA-256(hex_decode(serverSeed)) = serverSeedHash` and recompute the wheel position from (serverSeed, drandRandomness). Every subsequent round uses a fresh server seed — there is no seed chain or epoch.
Client-seed games (like Duel's Keno and Mines) require the player to supply an input the casino cannot predict — the client seed. This gives the player direct control over the unpredictable half of the RNG input. Castle Roulette takes a different approach: instead of a per-player client seed, it uses a public randomness beacon (drand quicknet) as the second cryptographic input. drand publishes a new randomness value every 3 seconds, signed by a distributed threshold of independent validators, on a fixed schedule. Because the beacon is public, everyone — including the operator — gets the same value, and no party can predict or bias it. This architecture produces the same cryptographic guarantee as a client seed:
- The operator commits to the server seed before the drand beacon for the round is available — so the operator cannot pick a seed that pairs favorably with the beacon
- drand is signed by a BLS threshold of validators distributed across multiple organizations — no single party can forge or withhold a beacon
- Any player can fetch the drand beacon directly from api.drand.sh and verify it matches the signature the operator claimed to use
- Every player in the same round shares the same (server seed, drand beacon) pair — the round has one shared outcome, not per-player outcomes
Why There's No Nonce or Epoch Chain
In client-seed games, the casino typically uses a single server seed for many rounds, incrementing a nonce counter per round to produce different outcomes. The seed is only rotated when the player requests it — ending the epoch and revealing the old seed. Castle Roulette takes a simpler approach: every round gets a brand-new server seed, and there is no nonce counter.
- Each round has exactly one server seed — no reuse, no chain, no cursor
- The nonce is effectively always
0(fixed in the HMAC message) because every seed is already unique - There is no epoch to rotate — the "reveal" happens after each round rather than after N rounds
- The 1,350-round dataset contains 1,350 unique server seeds and 1,350 unique server seed hashes — verified by Step 4
Round N: serverSeed_N (unique), drandRoundId_N → wheelPosition_N
[seed revealed after round ends]
Round N+1: serverSeed_(N+1) (new, unrelated), drandRoundId_(N+1) → wheelPosition_(N+1)
[seed revealed after round ends]
...
Every round is a fresh commitment. There is no N-th nonce — every HMAC
message embeds the fixed suffix ":0".Determinism Guarantee
Given identical inputs, the output is always identical:
HMAC-SHA256(hexDecode(serverSeed), drandRandomness_utf8 + ":0") → Always same hash
First 4 bytes of hash as uint32 → Always same value
value % 48 → Always same position
PAYOUT_TABLE[position] → Always same color/multiplier
color matches player's bet → Always same win/loss
stake × multiplier → Always same payoutTechnical Glossary5 categories▶
| Term | Definition |
|---|---|
| Provably Fair | A gambling model in which the inputs to every outcome are cryptographically committed before the outcome is known, and any player can independently verify the outcome after the fact without trusting the operator. |
| Commit-Reveal Protocol | A protocol in which one party publishes the hash of a secret before any dependent action, then reveals the secret afterward. In Castle Roulette, the operator commits to the server seed (via SHA-256) before the round's drand beacon is available, and reveals the seed after the round ends. |
| External Entropy Source | A source of randomness outside the operator's control that contributes to outcome generation. Castle Roulette uses the drand quicknet public randomness beacon as its external entropy source. |
| Determinism | The property that identical inputs always produce identical outputs. In Castle Roulette, any (serverSeed, drandRandomness) pair deterministically produces exactly one wheel position (0-47). |
| Term | Definition |
|---|---|
| Server Seed | A 64-character hex string (32 bytes) generated by the operator. Hex-decoded to raw bytes before use as the HMAC key. Each Castle Roulette round uses a unique server seed. |
| Server Seed Hash | The SHA-256 hash of the hex-decoded server seed bytes. Published by the operator before the round's drand beacon is available — commits the operator to the seed. |
| drand Round ID | An integer identifying a specific beacon in the drand quicknet chain. Committed by the operator at round start; the beacon for that round publishes on drand's fixed schedule. |
| drand Randomness | The 96-character hex BLS signature published by the drand quicknet chain for a given round ID. Used as the HMAC message (after hex→bytes→UTF-8 conversion) together with the `:0` suffix. |
| Single-Use Seed Model | Castle Roulette's seed-rotation pattern: every round gets a fresh server seed (no reuse, no chain, no nonce counter). The effective nonce is always `0` — embedded as a fixed `:0` suffix in the HMAC message. |
| Term | Definition |
|---|---|
| HMAC-SHA256 | Keyed hash function that combines the server seed bytes (key) with the drand randomness and `:0` nonce (message) to produce a 32-byte hash. The first 4 bytes are interpreted as a uint32 to derive the wheel position. |
| SHA-256 | Cryptographic hash function used for the commit-reveal proof. `SHA-256(hex_decode(serverSeed)) = serverSeedHash` is checked on every round. |
| BLS Threshold Signature | The signature scheme used by the drand quicknet chain. A drand beacon is a BLS signature produced by a threshold-quorum of independent validators — no single validator can forge or withhold a beacon. |
| Hex Decoding | Converting a hex-encoded string to raw bytes. The server seed is hex-decoded before being used as the HMAC key; the drand randomness is hex-decoded before being converted to a UTF-8 string for the HMAC message. |
| uint32 Modulo Mapping | The method by which the HMAC output is transformed into a wheel position. The first 4 hash bytes are interpreted as an unsigned 32-bit integer `value`; the position is `value % 48`. Modulo bias is ~1.55 × 10⁻¹⁰ per favored position (positions 0-15 are favored by one count out of 89,478,485) — negligible at any practical sample size. |
| Term | Definition |
|---|---|
| Verifier | An independent implementation of the RNG and payout logic used to recompute live-game outcomes from captured inputs. A verifier must produce identical results to the live game on every round. |
| Parity | The property that the verifier's computed wheel position equals the live game's reported position for every round. 1,350 / 1,350 parity means zero mismatches across the entire dataset. |
| Anti-Circularity | Verification that the RTP is not derived from the same table used to compute payouts. For Castle Roulette this is direct algebraic — every color tier satisfies `count(tier) × multiplier(tier) = 48`, so `P(color) × multiplier = 1.000` exactly for all 6 tiers. The proof uses no input from the runtime payout pipeline. |
| Direct Algebraic Proof | The analytical technique Castle Roulette uses for anti-circularity. The 6-tier payout table is verified to satisfy `count × multiplier = 48` for every tier, which yields theoretical RTP = 100.0000% by elementary arithmetic. This is the equivalent for Castle Roulette of the Combinatorial Identity (Mines), Hypergeometric Distribution (Keno), or Survival Probability Formula (Crash). |
| Term | Definition |
|---|---|
| Wheel Position | The integer 0-47 produced by `value % 48`, where `value` is the first 4 bytes of the HMAC output as a uint32. Each position belongs to exactly one of the 6 color tiers. |
| Color Tier | A group of wheel positions that share a payout multiplier. The 6 tiers are: Green (position 0, 48×), Red (1-2, 24×), Purple (3-5, 16×), Blue (6-11, 8×), Grey (12-23, 4×), Dark Blue (24-47, 2×). |
| Zero House Edge | Castle Roulette's edge structure. Every color tier has expected value exactly 1.000 because `count × multiplier = 48` for all 6 tiers. There is no edge factor in the wheel-position formula. |
| Shared Round | A single round's (server seed, drand beacon) pair is shared across every player who bets into it. Every player in the round sees the same wheel position. The operator commits to the pair at round-open time, before any individual bet is placed. |
| Multi-Stream Chi-Squared | The statistical methodology used in S4 Pass 1. Running 10 independent 500K-round streams and combining their p-values via Fisher's method (R.A. Fisher, 1925) produces a more robust uniformity test than a single long chi² because per-stream p-values aggregate independent Monte Carlo evidence. For Castle Roulette's 48-bin discrete distribution, both per-stream chi² and Fisher's combined statistic are reported. |
Every Castle Roulette round on Duel.com is generated from two cryptographic inputs: a server seed committed by the operator and a drand beacon published by the public randomness chain. The operator commits to its server seed by publishing a SHA-256 hash before the round's drand beacon is available — before any bet is placed. After the round ends, the server reveals the actual seed, and anyone can verify that the hash matches. This cryptographic commitment, paired with the external drand beacon, makes it impossible for the operator to secretly change your outcome after you bet.
What We Verified
- Casino commits to the server seed hash before the round's drand beacon is available
- Every bet was placed before its round's drand beacon was published on the public chain (mean margin 20.067s)
- drand round IDs are strictly increasing across all 1,350 rounds — no round shopping possible
- Server seeds are unique per round — no reuse, no chaining across rounds
- Wheel positions are fully determined by (serverSeed, drandRandomness) before the wheel animation plays
- Identical inputs always produce the same wheel position — confirmed across all 1,350 bets
What This Means for You
- The casino cannot change the wheel position after you bet
- The external entropy source (drand) publishes on a fixed public schedule outside the operator's control
- Every bet is unique — fresh server seed per round, no reuse
- Any result can be independently verified using only public inputs
- Outcomes are tamper-proof and verifiable even months later
- Cherry-picking favourable (seed, beacon) pairs is structurally impossible
| Test | Status | Finding |
|---|---|---|
| Server seed committed before round | Pass | SHA-256 hash of server seed published before the round's drand beacon is available — casino cannot change randomness after betting |
| drand pre-commitment timing | Pass | 1,350 / 1,350 bets placed before drand beacon publication (min margin 14.763s, mean 20.067s; authoritative transactions-API timestamp + drand chain formula) |
| drand round monotonicity | Pass | drand round IDs strictly increasing across all 1,350 rounds — 0 reuse, 0 round shopping |
| Server seed uniqueness | Pass | 1,350 unique server seeds and 1,350 unique hashes across 1,350 rounds — no reuse, no chain, no cursor |
| Seed hash integrity | Pass | SHA-256(hex_decode(serverSeed)) = serverSeedHash for all 1,350 revealed seeds — commitment intact |
| Deterministic output | Pass | Same (serverSeed, drandRandomness) always produces same wheel position — 1,350 / 1,350 confirmed |
| Bet-size invariance | Pass | Phase C verifies under the identical RNG code path — wheel position is independent of bet amount |
All 1,350 revealed server seeds hash-verified. Every bet landed on the operator's database before its round's drand beacon was published on the public chain, with a minimum margin of 14.763 seconds — proven from the operator's signed transactions-API timestamp and the public drand chain formula. Outcomes are fully deterministic — the same server seed and drand randomness always produce the same wheel position. The casino cannot change your result after you bet.
This section verifies that Duel.com's Castle Roulette random number generation produces cryptographically sound, unbiased outputs using only the disclosed inputs. The RNG uses HMAC-SHA256 keyed by the hex-decoded server seed with the drand randomness as message — the first 4 bytes of the output are interpreted as a uint32 and mapped through value % 48 to produce the wheel position (0-47), which then looks up the color tier and multiplier from the 6-tier PAYOUT_TABLE. We independently implemented this algorithm, verified it produces the same results as the live game for all 1,350 captured rounds, and re-fetched all 1,350 drand signatures from api.drand.sh to confirm the external entropy was not forged.
What We Verified
- HMAC-SHA256 produces cryptographically sound, unpredictable output for every round
- Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
- All 1,350 drand beacon signatures in the dataset match, byte-for-byte, signatures independently re-fetched from the public drand chain
- The uint32 → wheel position mapping is uniform with negligible modulo bias — ~1.55 × 10⁻¹⁰ per favored position, undetectable at any practical sample size
- Wheel-position distribution matches the theoretical `value % 48` uniform model — Fisher's combined p = 0.7712 across 10 independent 500K-round streams, 0 / 10 below α = 0.01
- Consecutive outcomes are statistically independent across 5M simulated rounds — no autocorrelation, no runs-test anomalies
What This Means for You
- Every wheel position is generated fairly — no position is more or less likely than the formula predicts (modulo bias is negligible)
- The external entropy comes from a public, cryptographically-signed source the casino cannot forge or withhold
- No hidden randomness or server-side tricks influence which position the wheel lands on
- Consecutive rounds are not correlated — past results don't affect future outcomes
- The algorithm depends only on inputs you can verify yourself against the public drand chain
| Test | Status | Finding |
|---|---|---|
| RNG derived only from disclosed inputs | Pass | HMAC-SHA256(hex_decode(serverSeed), drandRandomness_utf8:0) — no hidden entropy |
| Entropy purity | Pass | No timestamps, external APIs (other than drand itself), Math.random, or server-side state |
| External entropy authenticity | Pass | 1,350 / 1,350 drand signatures match, byte-for-byte, signatures re-fetched from api.drand.sh (chain 52db9ba7…4e971) |
| Algorithm independently implemented | Pass | Independent implementation produces identical wheel positions for all 1,350 live rounds |
| Uniform mapping | Pass | uint32 → position via value % 48 — modulo bias negligible (~1.55 × 10⁻¹⁰ per favored position; 16 of 4.29B values favor positions 0-15 by one count) |
| Simulation integrity | Pass | 5M rounds (10 streams × 500K) — Fisher's combined p = 0.7712, 0 / 10 streams below α = 0.01 |
| Serial independence | Pass | lag1Z = −2.384, runsP = 0.4312 across combined 5M-round sequence — both within acceptance bounds |
The Castle Roulette RNG uses only the two disclosed inputs. All 1,350 drand signatures in the dataset match the public drand chain byte-for-byte — the external entropy is verifiably real, not fabricated. The uint32 → position mapping is uniform with a modulo bias of ~1.55 × 10⁻¹⁰ per favored position (negligible). 5 million simulated rounds produce a wheel-position distribution statistically indistinguishable from the theoretical uniform model (Fisher's combined p = 0.7712). Consecutive outcomes are independent.
This section validates that the independent verifier produces the exact same wheel position as the live game for every single bet. Any mismatch would invalidate the fairness guarantee. The section also confirms that each bet's win condition is correctly resolved against the landing color and every payout matches the published per-tier multiplier table.
What We Verified
- Every round independently recomputed from (serverSeed, drandRandomness) — full wheel position verified, not just the payout
- Payout correctness: amount_won = stake × multiplier on wins, 0 on losses — exact for all 1,350 rounds
- Win/loss flags are correct — the round counted as a win exactly when the resolved color matched your bet, on all 1,350 / 1,350 rounds
- Bet amount is not an input to the RNG — wheel position depends only on the server seed and drand beacon
- All five capture phases verified under the identical RNG code path
What This Means for You
- The verifier isn't a simulation — it produces the exact same wheel position as the live game
- Every round you play can be independently recomputed by anyone with the revealed seeds
- No hidden logic alters your payout based on how much you bet or which color you pick
- The game engine in production matches the published algorithm exactly
| Test | Status | Finding |
|---|---|---|
| Wheel-position recomputation | Pass | 1,350 / 1,350 exact match — wheel position verified for every round from (serverSeed, drandRandomness) |
| Payout correctness | Pass | All 1,350 rounds: amount_won = stake × multiplier on wins, 0 on losses — exact to floating-point precision |
| Win-condition logic | Pass | 1,350 / 1,350 isWin flags correct (resolved-position color matches bet color) |
| Zero house edge | Pass | effectiveEdge = 0 on every round — no scaling, no per-tier variation |
| Bet-size invariance | Pass | 100 / 100 Phase C ($1) rounds verify under the identical RNG code path used at $0.01 stakes — bet amount is not an RNG input |
| Multi-phase coverage | Pass | 5 structured phases: baseline (A, 800), varied colors (B, 200), elevated stake (C, 100), rare 16× (D, 100), rare 48× (E, 150) |
All 1,350 wheel positions matched the independent verifier exactly. Payout math correct to floating-point precision on all 1,350 rounds. Win-condition logic (resolved-position color matches bet color) is correct for every bet. Zero house edge confirmed across all 6 color tiers — effectiveEdge = 0 on every round.
This section mathematically verifies the zero-edge base game — the RTP the wheel structure produces across all six color tiers — and documents the bankroll-scaled edge the operator applies beyond the Zero Edge allowance. The key test is anti-circularity: we prove the base RTP from first principles using the 6-tier `count × multiplier = 48` identity — no casino-supplied probability data is used. We then back up the first-principles proof with 5,000,000 simulated rounds across 10 independent streams, and run a cherry-pick detection pass against all 1,350 casino-chosen server seeds.
What We Verified
- House edge was 0% across every captured round — all 1,350 bets settled at zero edge under Duel's Zero Edge allowance
- RTP proven from first principles: count(tier) × multiplier(tier) = 48 for every tier, so P(color) × multiplier = 1.000 exactly — derived from the wheel structure, not from casino data
- 5M-round simulation converges on theoretical RTP (100.0070% mean across 10 streams — within 0.01% of 100.0000%)
- Cherry-pick detection: all 1,350 casino-chosen server seeds tested against 1,000 random drand values each — no evidence of seed pre-selection
- Bet amount does not influence wheel positions — confirmed at $0.01 and $1
What This Means for You
- Castle Roulette's base game is mathematically zero-edge — every captured bet returned 100% in expectation. Duel applies a bankroll-scaled edge that the Zero Edge allowance offsets, and the exact edge for any bet is shown live in the game before you place it.
- The RTP proof is derived independently — it doesn't rely on trusting the casino or any simulated data
- The casino's seeds show no evidence of being chosen to produce favourable outcomes (structurally impossible given drand's post-commitment publishing schedule)
- Your bet amount doesn't affect the wheel position
| Test | Status | Finding |
|---|---|---|
| Anti-circularity | Pass | count × multiplier = 48 for all 6 color tiers — algebraic identity, no casino data used (modulo bias ~1.55 × 10⁻¹⁰ per favored position, negligible) |
| House edge audit | Info | Base game is zero edge by construction — effectiveEdge = 0 on every captured round. A bankroll-scaled edge applies to bets beyond the Zero Edge allowance — operator-disclosed, not exercised by the captured rounds; see 4.2 |
| Simulated RTP (Pass 1) | Pass | 5M rounds, mean RTP = 100.0070%, Fisher's combined p = 0.7712 across 10 streams |
| Cherry-pick detection (Pass 2) | Pass | 1,350 casino seeds tested against random drand pairings — no evidence of seed pre-selection |
| Bet-size invariance | Pass | Bet amount is not an input to the RNG — same wheel position at any stake. Tested in Phase C (100/100) |
| Formula-based payout | Pass | amount_won = stake × multiplier verified for all 1,350 bets — multiplier from 6-tier table, no edge factor |
The 100.0% RTP is proven algebraically — count × multiplier = 48 for every color tier, derived from the wheel structure not measured from casino data. 5M simulated rounds confirm: mean RTP = 100.0070%, well within statistical tolerance. Cherry-pick detection across the casino's 1,350 actual server seeds shows no anomalies. Across all 1,350 captured rounds the house edge was 0%, settled under Duel's Zero Edge allowance. The base game has no edge factor by construction; a bankroll-scaled edge applies beyond the allowance and is disclosed by the operator via a public metadata endpoint and a live in-game display.
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 seed integrity, commitment timing, outcome determinism, cross-player isolation, payout integrity, and wheel-position distribution integrity. For Castle Roulette, the framework is adapted from the pan-game matrix to reflect the dual-entropy (server seed + drand) architecture and the discrete 48-position outcome model: there is no client seed to test, no nonce sequence per player, no per-player outcome divergence, and the commit-reveal chain is anchored against an external public beacon.
What We Verified
- Seed commitment — server seed hash published before drand beacon emits (1,350 / 1,350 verified)
- drand authenticity — every beacon value matches the public drand chain byte-for-byte (1,350 / 1,350)
- Pre-commitment timing — bet placed before drand publication for every round (min margin 14.763s)
- Outcome determinism — identical inputs produce identical wheel positions (1,350 / 1,350 recomputed)
- Cross-player isolation — all players see the same wheel position per round (global outcome model)
- Distribution integrity — wheel-position distribution is uniform across 48 positions (Fisher's `p = 0.7712`)
What This Means for You
- No one — not the player, not the casino — can alter the wheel position through the API
- The casino commits to each round's outcome before the external entropy is even known
- Every round is cryptographically isolated from every other — no state leakage
- The external entropy comes from a public beacon the casino cannot forge or withhold
- The server's outcome is computed from the canonical inputs only — adversarial probes confirmed the server rejects malformed bet parameters and ignores injected outcome fields
| Test | Status | Finding |
|---|---|---|
| Seed integrity | Pass | 5 tests — all SEED-001..005 pass (commitment, drand authenticity, uniqueness, determinism, statistical quality χ² p = 0.5505, H = 7.99583 bits/byte) |
| Commitment timing | Pass | Bet placed before drand in 1,350 / 1,350 rounds (min margin 14.763s); drand round IDs strictly increasing (28,007,049 → 28,035,974) |
| Outcome determinism | Pass | Identical inputs produce identical wheel positions — 1,350 / 1,350 confirmed. Bet-size invariance verified (Phase C 100 / 100). Player-action invariance: architecturally N/A under global outcome model |
| Cross-player isolation | Pass | RNG state independent (lag1Z = −2.384, runsP = 0.4312); global outcome model — all players see same wheel position |
| Payout integrity | Pass | 2 adversarial socket probes pass (FI-PAYOUT-001 parameter limits: 5/5 invalid bets dropped; FI-PAYOUT-002 field injection: 3/3 injected wheel_position/payout_multiplier/result fields ignored) |
| Distribution integrity | Pass | Wheel positions uniform across 48 bins (live position chi² p = 0.8650 on 1,350 rounds; Fisher's combined p = 0.7712 across 10 × 500K-round streams) |
15 standard fairness integrity tests: 14 pass, 1 N/A (player-action invariance — global outcome model).
Every Castle Roulette round can be independently reproduced using three publicly disclosed inputs: the casino's revealed server seed, the drand round ID, and the drand beacon's randomness value. No hidden variables, no private backend data. If your calculated wheel position matches the game result — and the drand beacon matches the public chain at `api.drand.sh` — the round 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 Castle Roulette round can be independently reproduced
- No hidden variables — no private backend data, no server-side state
- If your computed wheel position matches the game result AND the drand beacon matches the public chain, the round was provably fair
- Most players can verify directly through the Duel.com fairness UI
What You Need
- Server Seed — revealed after the round ends (casino's committed entropy, hash published before the round)
- drand Round ID — the external beacon round number (committed before the casino's seed is revealed)
- drand Randomness — the BLS-signed output from drand's public chain, fetchable from `api.drand.sh`
Only disclosed inputs are used. Identical inputs always produce identical wheel positions. The external entropy (drand) is independently verifiable against a public chain.
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-castle-roulette
- Commit: e57da3e
- Game: Castle Roulette (duel-castle-roulette)
- 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 1,350 rounds.



