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

Dice
Independent verification, backed by open-source code.
- Live Bets Verified
- 6,700
- Parity Rate
- 100%
- Simulated Rounds
- 98M
- Mean RTP 99.0203% over the 98 targets × 1M bets of Pass 1
- Proven RTP
- 99.0000%
Verification pipeline
Real bets. Independently reproduced.
Captured on LIQD’s pre-launch environment. We rebuilt the game logic from scratch and matched all 6,700 recorded outcomes.
The casino result, reproduced.
- Casino outcome
- 83.66 · Loss
- Our reproduction
- 83.66 · Loss
- Recorded payout
- $0.0000
- Seed Hash IntegrityPass
- Next-Seed ChainPass
- Hash ConsistencyPass
- Nonce AuditPass
- Roll RecomputationPass
- Win-Rule ReproductionPass
- Multiplier DerivationPass
- Credit ArithmeticPass
- Loss SettlementPass
- Wallet Credit & Odds BoundsPass
- Dataset IntegrityPass
- Phase & Mode CoveragePass
- Anti-CircularityPass
- Custom Client-Seed ControlPass
- Odds Boundary & EdgePass
- Simulation Pass 1Pass
- Cherry-Pick DetectionPass
- Bet-Size InvariancePass
- Artifact IntegrityPass
- Settlement DeterminismPass
- Multiplier MonotonicityPass
- Live Serial ScreenPass
The repo is the credential. You don’t have to trust us — every scored check ships as code. Run npm test to re-run the full audit: 123 unit tests, the 22 verification steps and 6,700 live bets re-verified — or node test.js && node verify.js with no dependency install at all.
This audit independently validates the Dice game operated by LIQD under the ProvablyFair.org Audit Framework 1.1. We placed 6,700 real bets across 134 seed epochs — every mode, two stakes, ten custom client seeds — and recomputed every roll, every win flag and every credited amount with our own implementation of the algorithm, written from a specification without LIQD’s code. The capture ran on LIQD’s pre-launch environment, so the certificate is provisional until an anonymous recapture on liqd.com confirms the same behaviour.
Audit Verdict
| Check | Result | Evidence |
|---|---|---|
| Overall Status | Pass | PROVABLY FAIR — Full Pass · Passed 22/22 (21 scored + 1 informational) · Hard fails 0 · Flags 0. Provisional: captured on LIQD’s pre-launch environment; an anonymous recapture on liqd.com completes certification |
| RTP Verified | Pass | 99.0000% Proven RTP · 99.0203% simulated (mean of 98 targets × 1M bets) · RTP claim rule: LIQD’s published 99% holds on all 9,800 settings, worst gap 0.000000 pp against the ±0.1 pp band (RTP-CLAIM 1.2.0, Step 15) |
| Live ↔︎ Verifier Parity | Pass | 100% — 6,700 / 6,700 rolls reproduced byte-identically and 6,700 / 6,700 win flags reproduced from the revealed seeds (Steps 5, 6) |
| Commit-Reveal System | Pass | 134 / 134 revealed seeds hash to their commitments; the next-seed chain is consistent on 133 / 133 rotations plus the pre-capture link into epoch 0 (Steps 1–3) |
| Client Seed | Pass | Player-controlled and customizable — 500 / 500 custom-seed bets recompute, and a different client seed changes the roll on 8 / 8 probes (Step 14) |
| RNG Analysis | Pass | Our own implementation, written from the specification: HMAC-SHA256, one 32-bit draw per bet, values at or above 4,294,960,000 rejected — worst-case modulo bias 0 (Step 5) |
| Payout Logic | Pass | All 6,700 settlements verified — multiplier 9900 / b bit-exact on all 2,807 wins; every credit reproduced by the two-stage 8-decimal settlement with zero tolerance; 3,893 losses pay 0 (Steps 7–9, 20) |
| Anti-Circularity | Pass | P(win) × m = (b / 10,000) × (9900 / b) = 0.99 from grid arithmetic alone — the closed form equals a literal 10,000-point tally on all 5,304 captured bands (Step 13) and the integer identity holds on 6,700 / 6,700 bets (Step 15) |
| Fairness Integrity | Pass | 34 fairness integrity tests across 12 categories — 33 pass, 1 remediated (overload handling under extreme bursts, Low — fixed by LIQD and re-verified); no test altered, replayed or injected an outcome |
| Determinism | Pass | Full reproducibility confirmed — 6,700 / 6,700 outcomes and credits are determined by (roll, band, stake) alone (Step 20) |
Overall StatusPass
RTP VerifiedPass
Live ↔︎ Verifier ParityPass
Commit-Reveal SystemPass
Client SeedPass
RNG AnalysisPass
Payout LogicPass
Anti-CircularityPass
Fairness IntegrityPass
DeterminismPass
What Was Audited
✓ Audited
- The RNG is deterministic: every roll reproduces from server seed, client seed and nonce
- Server seeds are committed by SHA-256 hash, and each rotation pre-commits the next seed’s hash
- The client seed is player-controlled and changes the roll
- Nonces run 0–49 in every seed epoch with no gaps or reuse
- The roll is one HMAC-SHA256 draw on the 10,000-point grid, with bias-free rejection sampling
- The win rule is the half-open band [lower, upper) — lower bound in, upper bound out — with
outsideits exact complement - Winning multipliers equal 9900 / b for the band’s basis points — bit-exact on every captured win — and settle through a reproducible two-stage 8-decimal rounding
- Proven RTP is 99.0000% on every band in every mode (flat 1.00% house edge), asserted in exact integer arithmetic
- LIQD’s published 99% RTP holds on all 9,800 settings under the RTP claim rule (±0.1 pp)
- Bet amount does not influence the roll or the edge
- Players can independently verify every bet
What Audit Excludes
- Infrastructure or server security
- Wallet, payments, or operational systems outside game logic
- Cross-account sampling
What Audit Covers
| Area | Description |
|---|---|
| Commit-Reveal System | SHA-256 server seed hashing, pre-bet commitment, next-seed pre-commitment, reveal on rotation |
| Client Seed Origin | Player-controlled seed set after the server commitment — our own random seeds in 124 epochs and 10 custom seeds in Phase D |
| Seed Handling | Client seed control, nonce lifecycle, seed epoch rotation |
| RNG Analysis | Our own HMAC-SHA256 implementation, written from the specification; LIQD’s verifier used as a cross-check; rejection sampling, bias bound, boundary semantics |
| Payout Logic | Quotient multiplier 9900 / b, two-stage 8-decimal settlement, loss settlement, wallet-field reconciliation, bet-size invariance (Phase C) |
| Live Parity | Independent roll and win-flag recomputation vs live game results across all four capture phases |
| RTP Validation | Integer edge identity, literal grid enumeration, the RTP claim rule over all 9,800 settings, simulated RTP (98 targets × 1M bets), cherry-pick test on the served-band return |
| Fairness Integrity | Integrity tests on LIQD’s running service — 34 across 12 categories, including race conditions, input validation, idempotency, authorization, outcome and payout fields, nonce and seed handling, information disclosure and the provably fair surface |
What Audit Guarantees
- Outcomes are deterministic and reproducible from the recorded inputs
- Live game results match independent recomputation for the verified sample (6,700 / 6,700)
- Rolls are consistent with the uniform distribution expected from the rejection-sampled HMAC draw — 2,000,000 simulated draws and the 6,700 captured rolls both pass their uniformity screens
- RTP is proven analytically: P(win) × multiplier = (b / 10,000) × (9900 / b) = 0.99 for every band, asserted in exact integers on all 6,700 captured bets
- LIQD’s published 99% RTP is met by every one of the 9,800 settings, each exactly 99.000000%
- Mean simulated RTP of 99.0203% across 98 targets × 1,000,000 bets (over 2.00 to over 99.00) — 0.98 standard errors from the proven 99.0000%, with every target inside the 5·SE tolerance
- The roll is fixed by the seed pair and nonce before the band is applied — which band you choose and how much you stake never change the number rolled
- Client seed is a genuine, player-controlled input that materially influences results (500 / 500 custom-seed bets recompute; 8 / 8 sensitivity probes change the roll)
- The house edge is a flat 1.00% on every band in every mode — settlement rounding at the 8th decimal is directionless (1,341 wins rounded down, 1,316 up, 150 exact) and keeps the settled RTP between 98.999995% and 99.000005%
- Every fairness integrity test passed or was remediated and re-verified — no test altered, replayed or injected an outcome, and the one finding never touched the provably fair layer
References
Dice — Game Rules7 sections▶
How LIQD Dice plays, exactly as verified by this audit — the interactive slider below is wired to the audited win rule and multiplier formula.
Game Rules
Dice rolls a single number from 0.00 to 99.99 — 10,000 equally likely two-decimal outcomes. Before you roll, you choose a band on that scale in one of four modes: under (from 0.00 up to a limit), over (from a limit up to 100), inside (between two limits) or outside (everything except an inside band). You win if the roll lands in your winning region. The region is half-open — a roll exactly on the lower bound wins, a roll exactly on the upper bound loses — and outside is the exact complement. The roll itself comes from one HMAC-SHA256 draw seeded by (serverSeed, clientSeed, nonce), fixed before your band or stake is applied; your choices set the odds and the price, never the number.
How to Play
1. Choose a mode — under, over, inside or outside — and set the band limits; the win chance is the width of the winning region in percentage points.
2. Set the stake — the multiplier is quoted immediately as 9900 / b, where b is the win chance in basis points (a 50% band pays 1.98×, a 1% band 99×).
3. Roll — the platform derives the roll from the committed server seed, your client seed and the current nonce, then applies your band to it.
4. Settle — a win credits stake × multiplier, rounded in two stages at the 8th decimal; a loss pays 0. The nonce advances by 1.
5. Verify — after rotating your seed pair, recompute any roll from the revealed seed (Section 6 walks it).
Win Conditions
Four modes, one rule — inBand = roll ≥ lower AND roll < upper, inverted for outside. Every example below is a real captured bet from epoch 0, recomputed by this audit:
| Mode | Winning region | Example (epoch 0) |
|---|---|---|
| Under | 0.00 ≤ roll < upper | Under 92.37 · roll 24.01 → win, 1.07177655× (bet kzLjSr11…) |
| Over | lower ≤ roll < 100 | Over 73.31 · roll 88.28 → win, 3.70925440× (bet M6RZjoxr…) |
| Inside | lower ≤ roll < upper | Inside 59.31–83.26 · roll 42.76 → loss, payout 0 (bet sbQbiJS7…) |
| Outside | roll < lower OR roll ≥ upper | Outside 16.81–85.72 · roll 11.94 → win, 3.18430363× (bet 9uY-gCHz…) |
Risk vs Reward
Every band returns the same 99.0000% — the win chance changes the shape of the risk, never the fairness of the price:
- Even money: a 50% band (under 50, over 50, inside 25–75, outside 25–75) pays
1.98× - Long shots: a 10% band pays
9.9×; a 2% band49.5×; a 1% band (over 99)99×— the largest multiplier won in this capture - The tail: a 0.01% band ([0, 0.01), one grid point) quotes
9900×— the UI’s ceiling, accepted live on a losing bet (E14, S4.7) - Variance scales with the multiplier, the edge does not:
P(win) × multiplier = 0.99on every band
Parameters
| Parameter | Value | Notes |
|---|---|---|
| Roll domain | 0.00 – 99.99 | 10,000 grid points (RANGE = 10000), two decimals |
| Modes | under · over · inside · outside | All four captured live — 1,362 / 2,592 / 1,360 / 1,386 bets |
| Win chance | 0.01% – 98% | Every basis point from 0.01% to 98.00% — 9,800 settings, all held to the RTP claim (S4.2); settings API reports minOdds 1.0102 |
| House edge | 1.00% flat | Applied once inside the multiplier — identical on every band |
| Multiplier | 9900 / b | b = win chance in basis points; 1.0105× – 99× captured |
| Seed epoch | 50 bets | Nonces 0–49, then the pair rotates — the audit's cadence, not a platform limit |
| Settlement | 8-decimal, two-stage | round8(m) then stake × round8(m) rounded half-even at 1e-8 — reproduced with zero tolerance |
| Stakes captured | $0.10 and $10.00 | 6,500 bets at $0.10, 200 at $10.00 (Phase C) |
Seed Formats
The three verification inputs, exactly as they appear in LIQD's panel and the bet record:
| Seed Type | Format | Example | Purpose |
|---|---|---|---|
| Server seed | 32-char hex (16 bytes) | 015307d5a3f62db476210c3797bac8b7 | HMAC key (decoded); committed via SHA-256 of the string before play |
| Client seed | free-form string | auditf2456fc140ac | Player-editable entropy — mixed into every draw |
| Nonce | integer 0–49 | 0 | The bet counter under the active pair |
Multiplier & Payout
The payout is a formula — no paytable file exists. The winning width in percentage points becomes basis points, and 9900 (10,000 less the 1% edge) is divided by it:
W = inverted ? 100 − (upper − lower) : upper − lower
b = round(100 × W)
m = 9900 / b| Band | Win chance | b | Multiplier |
|---|---|---|---|
| under 50 | 50.00% | 5000 | 1.98000000 |
| inside 25–75 · outside 25–75 | 50.00% | 5000 | 1.98000000 |
| under 92.37 (featured bet) | 92.37% | 9237 | 1.07177655 |
| over 90 | 10.00% | 1000 | 9.90000000 |
| over 98 | 2.00% | 200 | 49.50000000 |
| over 99 | 1.00% | 100 | 99.00000000 |
| under 0.01 | 0.01% | 1 | 9900.00000000 |
multiplier field equals the binary64 quotient 9900 / b bit-for-bit on all 2,807 captured wins (Step 7). Your balance is credited stake × round8(m), rounded half-even at the 8th decimal (Step 8) — in this capture that rounding went the player’s way 1,316 times, against them 1,341 times and landed exactly 150 times.Why Provably Fair Matters▶
Traditional online casinos require players to trust that games are fair. Provably fair systems eliminate that requirement — three properties, all verified in this audit, make trust unnecessary:
- The casino commits to the inputs that determine a result before you play — the SHA-256 commitment locks the server seed while it is still secret
- A source of randomness exists that the casino cannot predict — your client seed, editable after the commitment is published
- Anyone can verify the outcome after the fact — the revealed seed plus public code reproduces every roll exactly
High-Level Overview8 sections▶
The whole system in one pass — from commitment to payout, with the capture design that tested it.
Checklist Reference
Commit-Reveal
| Check | What it proves |
|---|---|
| Server seed hash shown before play | With the hash shown first, the casino cannot change the roll after you bet |
| SHA-256 of the revealed seed matches the commitment | The revealed seed is the one that was committed |
| Next epoch's hash pre-committed at rotation | The casino cannot mint a fresh seed in response to your play |
| One commitment per epoch, no mid-epoch switching | Every bet is pinned to a single verified commitment |
Seed System
| Check | What it proves |
|---|---|
| Client seed is player-editable after the commitment exists | The operator cannot pre-evaluate seeds against yours |
| Client seed changes the roll when substituted | Your seed genuinely enters every derivation |
| Nonces run contiguously 0–49 in every epoch | No skipped bet where a discarded outcome could hide |
RNG & Parity
| Check | What it proves |
|---|---|
Roll recomputes from (serverSeed, clientSeed, nonce) | No hidden inputs influence the number rolled |
Rejection sampling below maxFair = 4,294,960,000 | Every one of the 10,000 grid points is exactly equally likely |
| Half-open win rule reproduces 6,700 / 6,700 win flags | The game settles under the half-open rule its own verifier returns at the bounds (E13) |
Payout & RTP
| Check | What it proves |
|---|---|
Wins credit stake × round8(9900 / b) half-even at 1e-8 | The advertised 1.00% edge is the real edge |
100 · winCount · 9900 == 99 · 10,000 · b on every captured bet, as integers | The RTP is an identity, not a sampled estimate |
| Closed-form win count equals a literal 10,000-point tally | The probability side cannot share an arithmetic slip with the formula |
Integrity
| Check | What it proves |
|---|---|
| Losses pay exactly 0 with multiplier 0 | No round settles money against a losing roll |
| Identical engine at $0.10 and $10.00 | Stake size cannot reach the roll |
| Dataset SHA-256 pinned (a mismatch stops the suite); simulation and chart pins scored in Step 19 | The evidence itself is tamper-evident |
High-Level Flow
One round of LIQD Dice, from commitment to settlement:
1. Commit — the server generates a 32-hex server seed and publishes SHA-256(utf8(seed)) as hashedServerSeed. You see only the hash.
2. Seed pair active — your client seed (platform default or your own) and a nonce starting at 0 pair with the committed seed.
3. Bet — you pick a mode, a band and a stake. The roll is already fully determined by (serverSeed, clientSeed, nonce).
4. Roll — one HMAC-SHA256 draw, reduced to 0.00–99.99, is compared with your band under the half-open rule.
5. Settle — wins credit stake × 9900 / b through the two-stage 8-decimal rounding; losses pay 0. The nonce increments by 1.
6. Rotate & verify — rotation reveals the outgoing seed (and pre-commits the next). Check the hash, recompute any roll.
Provably Fair Model
The model splits control of the randomness. The operator controls the server seed but must commit to it before play; the player controls the client seed and can change it after seeing the commitment; the nonce is a public counter. Because the roll is a pure function of those three inputs — and the band and stake enter only afterwards, as the rule and the price — neither side can steer an outcome without the other side's input, and everything needed to check the result is disclosed by design.
Commit-Reveal Model
Three phases, each independently verified by this audit:
Commit phase
The server generates a 32-hex seed and publishes SHA-256 of that string (UTF-8) as hashedServerSeed. The raw seed stays secret. Verified for all 134 epochs (Step 1).
Bet phase
Bets settle under the committed pair; the nonce increments 0–49. The roll is fixed per (seed, clientSeed, nonce) before the band is applied. Verified by full recomputation (Step 5).
Reveal phase
Rotation reveals the outgoing seed and pre-commits the next epoch’s hash — the chain is consistent on 133 / 133 transitions, plus the pre-capture link into epoch 0 (Step 2).
Verify phase
Anyone re-hashes the reveal against the commitment and recomputes every roll it served — the panel, the ProvablyFair.org verifier, or the standalone scripts in S6 all work.
Player-Controlled Client Seed
The client seed is the mechanism that structurally contains server-seed cherry-picking — when it is unpredictable and set after the commitment — and this audit exercised the control live:
- Editable at any time in the fairness panel — including immediately after a new commitment is published
- Mixed into every HMAC message (
clientSeed:nonce:cursor) — it mathematically cannot be ignored - Phase D played 500 rounds under 10 auditor-chosen
pfaudit-seeds — all recomputed exactly - Sensitivity probe: appending one character to the client seed changed the roll on 8 / 8 probed Phase D bets (Step 14)
Nonce Lifecycle
The nonce is the bet counter under an active pair. In this capture the audit rotated the pair after every 50 bets: the nonce starts at 0 and increments by 1 per bet. A gap or reuse would be the fingerprint of a discarded outcome — the audit found neither, anywhere.
- Contiguous 0–49 in every one of the 134 epochs
- 0 gaps, 0 duplicates, 0 resets across 6,700 bets
- Each seed record pins
nonceStart/nonceEnd— the capture’s own record of the window it served
"epoch": 0,"nonceStart": 0,"nonceEnd": 49,"hashedServerSeed": "918c2a302c7219c91cf31ce3619acb0a…"Determinism Guarantee
Determinism is what turns a fairness claim into a checkable fact: the same inputs must always produce the same roll. LIQD's derivation is a pure function of three arguments — verified by reproducing every captured roll with an independent implementation, byte-identical on the two-decimal grid.
diceRoll(serverSeed, clientSeed, nonce) → 2401 // the roll in hundredths (24.01 on the 0.00–99.99 grid) — nothing else in, nothing else outPhase Map
The capture is engineered — four phases, each isolating one claim:
| Phase | Rounds | What it isolates |
|---|---|---|
| A | 5,000 | Random bands across all four modes — 5,304 distinct bands over the whole capture, $0.10 |
| B | 1,000 | The high-multiplier tail — 500 bets on over 98 (49.5×) and 500 on over 99 (99×) |
| C | 200 | Stake independence — over 50 at $10.00, 100× the $0.10 stake |
| D | 500 | Client-seed influence — 10 auditor-chosen seeds, live |
Technical Glossary7 categories▶
Every term used in this report, defined the way the audit uses it.
Core Concepts
| Term | Definition |
|---|---|
| Provably Fair | A cryptographic scheme in which the game outcome can be independently verified by the player after the fact, using seeds committed to before the round. The player cannot alter the server seed; the server cannot alter it after committing to its hash; neither party can change the roll without detection. |
| Commit-Reveal | The mechanism underlying provably fair. The server publishes hashedServerSeed (a commitment) before any bet, then reveals the plaintext serverSeed after the seed rotates. The auditor checks SHA-256(utf8(serverSeed)) == hashedServerSeed for every epoch. All 134 revealed seeds in this dataset passed. |
| House Edge | The operator's cut. Nominal houseEdge = 1 (%) per /system/get-game-settings (an advertised label). The effective edge is derived independently and is exactly 1.00% on every band, because the winning region holds exactly b of the 10,000 grid points and the multiplier is 9900 / b — the product is 0.99 as an integer identity, before the 8-decimal settlement rounding (a ≤ 5.5e-9 residual per win, in both directions). |
| RTP (Return to Player) | 1 − houseEdge. For LIQD Dice the theoretical RTP is a flat 99.0000% on every band in every mode — proven analytically (integer identity, Step 15), by literal enumeration (Step 13) and checked against 98M simulated bets across 98 targets. Live-bet RTP (103.56%) is informational only. |
| Anti-Circularity | The requirement that theoretical RTP and simulation expectations derive the win probability from the grid geometry alone (b / 10,000), never from an operator-supplied odds or win-chance field — so the RTP claim cannot validate itself. |
| RTP Claim Rule | RTP-CLAIM 1.2.0, part of the audit method: the RTP a casino publishes — on the game page, fairness page, help page, bet panel, rules or game settings — must hold at every setting to within ±0.1 percentage points. A house-side miss ends the verdict as RTP FIGURE OVERSTATED. LIQD’s published 99% holds on all 9,800 Dice settings. |
Seed System
| Term | Definition |
|---|---|
| Server Seed | A 32-character (16-byte) hexadecimal string generated by LIQD. Kept secret during betting, revealed after epoch rotation. Used as the HMAC key in its hex-decoded form: Buffer.from(serverSeed, 'hex'). |
| Commitment / Server Seed Hash | SHA-256(utf8(serverSeed hex string)) — the text-hash convention: LIQD hashes the UTF-8 bytes of the 32-character hex string, not the 16 decoded bytes. Published before any bet under the pair. A revealed seed that fails this check would prove tampering. Distinct from the HMAC key, which uses the decoded bytes. |
| Client Seed | A string controlled by the player (or generated by the platform), mixed into the HMAC message as ${clientSeed}:${nonce}:${cursor}. Changing it changes every roll under the same server seed. When the player sets an unpredictable seed after the server-seed commitment is published, it is the primary structural defence against server-seed cherry-picking; a predictable seed does not deliver that protection. |
| Nonce | An incrementing integer identifying a bet's position within an epoch. Range 0–49 (50 bets per epoch). Part of the HMAC message. Every epoch in this dataset has contiguous nonces 0–49 — no gaps. |
| Epoch | A sequence of exactly 50 bets under one (serverSeed, clientSeed) pair, nonces 0–49. At epoch end the seed rotates and the previous server seed is revealed. This dataset covers 134 epochs. |
Cryptographic Functions
| Term | Definition |
|---|---|
| HMAC-SHA256 | Hash-based Message Authentication Code using SHA-256. Produces a 256-bit (32-byte) deterministic output from a key and message. Here it generates the single uniform integer behind each roll: the digest is scanned in 4-byte big-endian uint32 chunks, the first chunk below the modulo-bias ceiling is taken % 10000. |
| Cursor | The third component of the HMAC message. Dice makes one draw per bet, so cursor = 0 for every roll. If all eight chunks of a digest were rejected — about 7 × 10⁻⁴⁷ per bet — the specification defines no further draw, and our implementation stops with an error; no captured bet comes close. |
| Modulo-Bias Rejection | Discarding uint32 values at or above maxFair = floor(2³² / 10000) × 10000 = 4,294,960,000 before taking % 10000, so every grid point is exactly equally likely. Without it the lowest 7,296 residues would be favoured by one part in 429,496. |
| Roll | draw / 100 — the accepted integer draw in [0, 10000) placed on the two-decimal scale 0.00 – 99.99. The served field is compared with the recomputed value byte-for-byte on that grid (Step 5). |
Verification Terms
| Term | Definition |
|---|---|
Basis Points (b) | The winning width in hundredths of a percent — round(100 × W) — and, under the half-open rule, exactly the number of grid points that win. An auditor-derived quantity: the field does not appear in the capture. |
| Quoted Multiplier | 9900 / b — the value LIQD serves and pays. Reproduced bit-for-bit as a binary64 quotient on all 2,807 wins; the equivalent float form (100 / W) × 0.99 is the same rational but agrees bit-for-bit on only 1,719 of them, which is why the audit pins the quotient. |
| Two-Stage Settlement | round8(m) (half-up at the 8th decimal), then stake × round8(m) rounded half-even at the 8th decimal, in exact integer arithmetic on 1e-8 units. Reproduces all 6,700 credited amounts with zero tolerance; 274 wins sit on an exact tie where the half-even rule decides the last unit. |
| Half-Open Band | [lower, upper) — the lower bound wins, the upper bound loses; outside is the exact complement. Reproduces all 6,700 win flags and is pinned at the boundary by LIQD's verifier endpoint (E13: two discriminating probes). |
Game Mechanics
| Term | Definition |
|---|---|
Band (params) | The requested {lower, upper, inverted} — two-decimal bounds on the roll scale plus the inversion flag. The only game-specific inputs; there are no risk levels or variants. 5,304 distinct bands were captured. |
| Mode | How the audit classifies a band for reporting: outside if inverted, under if lower = 0, over if upper = 100, otherwise inside. All four are present at volume (1,362 / 2,592 / 1,360 / 1,386 bets). |
| Win Chance | The continuous width of the winning region in percentage points, W = inverted ? 100 − (upper − lower) : upper − lower. Under the half-open rule the discrete probability b / 10,000 equals it exactly. |
Audit Terms
| Term | Definition |
|---|---|
| Draw Uniformity | A simulation test: 2,000,000 draws binned into 100 cells against N/100, χ² with df = 99 from the exact regularized incomplete gamma. Two-sided at α/3 — the lower tail rejects a statistic too uniform to be a real 2,000,000-draw sample. χ² = 108.885, p = 0.2335. |
| Serial Screen | Lag-1 autocorrelation (z = r₁ · √n) and the Wald-Wolfowitz runs test on the first 200,000 simulated rolls, each judged at α/3. z = 0.159 against the applied critical 2.9352; runs p = 0.6910. |
| Effective-Edge Bands | Nine named bands (under 50, over 50, under 10, over 90, inside 25–75, outside 25–75, over 98, over 99, under 1) simulated at 2,000,000 bets each; each simulated RTP must sit within 5 × SE + 0.0001 of 0.99 and reconstruct bit-for-bit from an integer win count. |
| 98 Over-Mode Targets | The main RTP simulation: every over-mode band from over 2.00 to over 99.00 (win chance 98% to 1%, 1.0102× to 99×), 1,000,000 bets each on our own seeds. Each target must sit within 5 × SE + 0.0001 of its theoretical RTP and reconstruct bit-for-bit from an integer win count; the mean over the 98 targets is the convergence chart in S4. |
| Bonferroni Correction | The three Pass 1 screens (uniformity, lag-1, runs) each run at α/3 = 3.333e-3 so the family has level α = 0.01. |
| Served-Band Return | The Pass 2 cherry-pick statistic: each of the 134 revealed seeds is replayed for 10,000 nonces with its recorded client seed, and the payout-weighted return of the bands LIQD actually served in the captured window (nonces 0–49) is compared with its exact expectation and variance. A one-sided test at α = 0.01: a seed set chosen against the player would show a return deficit. |
| Exact Discrete Null | The reference distribution of the supplementary draw-uniformity screen in Pass 2: for a 50-draw window over 20 bins the statistic S = Σ hᵢ² takes only 832 attainable values, enumerated exactly from 181,274 occupancy partitions. No bootstrap and no asymptotic approximation — it replays identically on every run. |
| Cherry-Picking (Server-Seed) | The residual risk in any beacon-free commit-reveal scheme: an operator with foreknowledge of the upcoming client seed could pre-evaluate candidate server seeds and commit to an unfavourable one. The structural defence is a client seed set after the commitment; the statistical check is the served-band return in Pass 2 (z = 0.752, p = 0.7738), which shows no return deficit. |
| Selective Orphaning | A distinct residual channel: an operator observing a round server-side and orphaning or re-issuing an unfavourable result rather than surfacing it. In this capture the nonces run contiguously 0–49 in all 134 epochs (Step 4); a signed server-side audit log closes the channel in general. |
| Hard Fail | Any FAIL on a scored step — a broken commitment or chain link, a roll, win-flag, multiplier or credit mismatch, an operator-side nonce gap, a population or artifact-integrity fault. A hard fail ends the verdict as NOT PROVABLY FAIR. None triggered: Passed 22/22, Hard fails 0, Flags 0. |
| Capture Flag | A flag raised when a gap in the record comes from our capture script — for example, a retried request that skipped a nonce — rather than from the casino. It is disclosed with a limitation row and does not change the verdict; an operator-side gap is a hard fail. This audit has 0 capture flags. |
Data Formats
| Term | Definition |
|---|---|
winningAmount | The settle-response field holding the amount credited on a win or 0 on a loss. The audit's payout evidence; verified against the two-stage settlement of betAmount × 9900 / b at zero tolerance, and reconciled with the wallet-denominated winningAmountWallet on all 6,700 bets (the capture records per-bet amounts, not running wallet balances). |
roll, win, multiplier | Response values stored after numeric conversion. roll is compared byte-for-byte on the two-decimal grid; multiplier is 9900 / b on wins and 0 on losses; win is re-derived from the roll and band. |
| Phase A / B / C / D | The four capture phases. A: 5,000 bets, random bands across all modes. B: 1,000 bets on over 98 and over 99. C: 200 bets on over 50 at $10.00 (stake independence). D: 500 bets under 10 custom pfaudit- client seeds. |
Seed, Nonce & Determinism
Can the casino change your outcome after you bet?
Every Dice roll on LIQD is generated from three inputs: server seed, client seed, and nonce. LIQD commits to its server seed by publishing a SHA-256 hash before the seed is used — and pre-commits the next epoch’s hash at every rotation. After you rotate your seed, the server reveals the actual seed, and anyone can check it against the hash. A matching hash shows which seed goes with which bets and in what order, and our capture records show each hash was published before its bets — so the casino cannot change the roll after you bet.
What we verified
- Every revealed server seed hashes to the commitment published for its epoch — 134 / 134
- The next server seed’s hash is pre-committed at every rotation — the chain is consistent on 133 / 133 links, plus the pre-capture link into epoch 0
- Client seed is player-controlled and set after the server commitment
- Players can set or change their client seed at any time via the fairness panel
- Nonce increments by 1 per bet with no gaps or duplicates across 134 seed epochs — 0 capture flags
- The roll is fully determined by the seed inputs before the band or stake is applied
- Identical inputs always produce the same roll — confirmed across all 6,700 bets, byte-identical on the two-decimal grid
- Your client seed is a genuine input — changing it changes the roll
What this means for you
- With the commitment shown first, the casino cannot change the roll after you bet
- You contribute randomness the server cannot predict or pre-select against
- Every bet has its own derivation — the nonce guarantees no two bets share the same inputs
- Any result can be independently verified using the public tools and repo
- Outcomes stay verifiable months later — the revealed seeds and the algorithm are all you need
- Server-seed cherry-picking is structurally contained — you can set or rotate your client seed after the commitment is published, so the operator cannot pre-evaluate seeds against it
- Every revealed server seed hashes to the commitment published for its epoch — 134 / 134
- The next server seed’s hash is pre-committed at every rotation — the chain is consistent on 133 / 133 links, plus the pre-capture link into epoch 0
- Client seed is player-controlled and set after the server commitment
- Players can set or change their client seed at any time via the fairness panel
- Nonce increments by 1 per bet with no gaps or duplicates across 134 seed epochs — 0 capture flags
- The roll is fully determined by the seed inputs before the band or stake is applied
- Identical inputs always produce the same roll — confirmed across all 6,700 bets, byte-identical on the two-decimal grid
- Your client seed is a genuine input — changing it changes the roll
- With the commitment shown first, the casino cannot change the roll after you bet
- You contribute randomness the server cannot predict or pre-select against
- Every bet has its own derivation — the nonce guarantees no two bets share the same inputs
- Any result can be independently verified using the public tools and repo
- Outcomes stay verifiable months later — the revealed seeds and the algorithm are all you need
- Server-seed cherry-picking is structurally contained — you can set or rotate your client seed after the commitment is published, so the operator cannot pre-evaluate seeds against it
Seed hash integrityPass
Next-seed chainPass
One commitment per epochPass
Nonce sequencingPass
Client seed originPass
audit… seeds in 124 epochs (Phases A–C) and 10 custom pfaudit- seeds in Phase DClient seed controlPass
Client seed influencePass
Deterministic outputPass
Win-rule reproductionPass
All 134 revealed seeds match their commitments, and the next-seed chain is consistent across 133 rotations plus the pre-capture link into epoch 0. Nonces run 0–49 in every epoch with no gaps. Outcomes are fully deterministic — the same server seed, client seed and nonce always produce the same roll. With each commitment shown before its bets, as our capture records show, the casino cannot change your result after you bet.
| Check | Result | Evidence |
|---|---|---|
| Seed hash integrity | Pass | 134 / 134 revealed seeds hash to their commitments — SHA-256 of the seed text, 0 mismatches (Step 1) |
| Next-seed chain | Pass | Consistent on 133 / 133 rotations, plus the pre-capture commitment that links into epoch 0 (Step 2) |
| One commitment per epoch | Pass | All 50 bets in each of 134 epochs carry the same commitment — no mid-epoch switching (Step 3) |
| Nonce sequencing | Pass | Nonces 0–49 in every epoch: 0 gaps, 0 duplicates, 0 capture flags (Step 4) |
| Client seed origin | Pass | Set after the server commitment — our own random audit… seeds in 124 epochs (Phases A–C) and 10 custom pfaudit- seeds in Phase D |
| Client seed control | Pass | Player can set or change the client seed at any time — 500 / 500 Phase D bets recompute under their custom seeds (Step 14) |
| Client seed influence | Pass | A one-character change to the client seed changes the roll on 8 / 8 probed bets (Step 14) |
| Deterministic output | Pass | Same (serverSeed, clientSeed, nonce) always produces the same roll — 6,700 / 6,700 confirmed, byte-identical (Step 5) |
| Win-rule reproduction | Pass | 6,700 / 6,700 served win flags reproduced from the recomputed roll under the half-open band rule (Step 6) |
Seed hash integrityPass
Next-seed chainPass
One commitment per epochPass
Nonce sequencingPass
Client seed originPass
audit… seeds in 124 epochs (Phases A–C) and 10 custom pfaudit- seeds in Phase DClient seed controlPass
Client seed influencePass
Deterministic outputPass
Win-rule reproductionPass
All 134 revealed seeds match their commitments, and the next-seed chain is consistent across 133 rotations plus the pre-capture link into epoch 0. Nonces run 0–49 in every epoch with no gaps. Outcomes are fully deterministic — the same server seed, client seed and nonce always produce the same roll. With each commitment shown before its bets, as our capture records show, the casino cannot change your result after you bet.
How It Works — Seed, Nonce & Determinism7 sections
1.1Server Seed Commitment
Before any bet is placed under a seed pair, LIQD shows the SHA-256 hash of a secret 32-character hex server seed as hashedServerSeed. The commitment hashes the seed text as a UTF-8 string, not its 16 decoded bytes: SHA-256(utf8(serverSeed)). When the pair rotates, the server seed is revealed and anyone can recompute the hash. A match shows the revealed seed is the committed one: a substitute would need a second preimage of SHA-256, and would fail every recomputation that follows.
// §1.2: the published commitment is SHA-256 over the seed's ASCII characters,// NOT over the bytes the hex decodes to.export function commitHash(serverSeedHexString: string): string { assertServerSeed(serverSeedHexString); return sha256Buffer(Buffer.from(serverSeedHexString, "ascii"));}Real Example from Live Data:
{ "epoch": 0, "phase": "A", … "clientSeed": "auditf2456fc140ac", "hashedServerSeed": "918c2a302c7219c91cf31ce3619acb0ac820b9b7760e8ea3889787bfcb5d14d8", "nextHashedServerSeed": "2e788a2ef11d3214cb57a6dbfa0fff43df1c972617325e9b8bf157158e5d08b5", "serverSeed": "015307d5a3f62db476210c3797bac8b7", "nonceStart": 0, "nonceEnd": 49, …}Verification:
// verify-seed.js — check any revealed LIQD server seed against its commitmentconst { createHash } = require('node:crypto');const serverSeed = '015307d5a3f62db476210c3797bac8b7'; // revealed 32-hex seed (epoch 0)const committed = '918c2a302c7219c91cf31ce3619acb0ac820b9b7760e8ea3889787bfcb5d14d8';const recomputed = createHash('sha256').update(serverSeed, 'utf8').digest('hex');console.log(recomputed === committed ? 'MATCH — seed is the committed one' : 'MISMATCH');1.2Next-Seed Pre-Commitment Chain
At every seed rotation, the nextHashedServerSeed LIQD pre-commits during an epoch must equal the hashedServerSeed that becomes active after the rotation, so the successor seed is fixed before the previous epoch ends. Step 2 walks all 133 epoch transitions and checks each link, and also checks the pre-capture record: the seed active before the first audited bet (2ca3cfde…) hashes to its own commitment (1bf54b7d…), and its pre-committed next hash is epoch 0’s hashedServerSeed (918c2a30…). LIQD’s own seeds panel shows the pre-committed next hash alongside the active one (E11; the Seeds tab in S6 shows the same panel). A hash chain shows the values are consistent and fixes their order; it does not carry a date. Our capture records show each commitment was published before the bets it covers.
// ── Step 2: Next-seed pre-commitment chain ────────────────────────────────── const byEpoch = [...seeds].sort((a, b) => a.epoch - b.epoch); let promoChecked = 0, promoFails = 0; for (let i = 0; i + 1 < byEpoch.length; i++) { promoChecked++; if (byEpoch[i].nextHashedServerSeed !== byEpoch[i + 1].hashedServerSeed) promoFails++; } const expectedTransitions = seeds.length - 1; // … const preCommitOk = commitHash(pre.revealedServerSeed) === pre.hashedServerSeed; const preLinkOk = pre.nextHashedServerSeed === epoch0.hashedServerSeed; preOk = preCommitOk && preLinkOk; // … const s2 = step(2, 'Next-Seed Pre-Commitment Chain', promoFails === 0 && promoChecked === expectedTransitions && preOk ? 'PASS' : 'FAIL',1.3Hash Consistency Within an Epoch
Each bet record carries its epoch’s hashedServerSeed — the commitment recorded when the pair became active, which the capture writes onto every row. Step 3 groups the 6,700 bets by epoch and confirms that all 50 bets in each epoch carry the same commitment, and that the number of epochs seen equals the number of seed records. More than one distinct hash within a single epoch would indicate a mid-epoch seed substitution — a critical commit-reveal violation. What binds each bet to its seed is the recomputation: the bet’s hashedServerSeed matches its seed record, the record’s hash matches the reveal (Step 1), and the revealed seed reproduces every roll in the epoch (Step 5).
// ── Step 3: Hash consistency within epoch ──────────────────────────────────── const betsByEpoch = new Map<number, typeof bets>(); for (const b of bets) { const arr = betsByEpoch.get(b.epoch) ?? []; arr.push(b); betsByEpoch.set(b.epoch, arr); } let epochsMultipleHashes = 0; for (const [, epochBets] of betsByEpoch) { const hashes = new Set(epochBets.map(b => b.hashedServerSeed)); if (hashes.size !== 1) epochsMultipleHashes++; } const s3CoverageOk = betsByEpoch.size === seeds.length; const s3 = step(3, 'Hash Consistency Within Epoch', epochsMultipleHashes === 0 && s3CoverageOk ? 'PASS' : 'FAIL', `${betsByEpoch.size}/${seeds.length} epochs: all bets within each epoch share the same hashedServerSeed; ${epochsMultipleHashes} violations`, );hashedServerSeed constant across all 50 bets within every epoch. Zero mid-epoch changes detected.1.4Client Seed Origin & Control
Players have full control over their client seed through the LIQD fairness panel, allowing them to view, modify, or rotate it at any time — including after the server-seed commitment is already published. The client seed is an input to every HMAC-SHA256 computation — changing it produces a completely different roll. LIQD generates a default value, and the server commits to its seed before the client seed for that epoch is set, which structurally contains cherry-picking when that seed is unpredictable: an operator that cannot know the upcoming client seed cannot pre-evaluate candidate server seeds against it. In this capture, 124 epochs (Phases A–C) used our own random audit… seeds — 48 bits of fresh CSPRNG material each, set after the commitment — and the 10 Phase D epochs used custom pfaudit- seeds. During capture the audit exercised this control directly:
- Player-editable at any time via the fairness panel — no wager required to change it
- Rotatable after the server-seed commitment is published — the operator commits blind
- Mixed into every draw as the HMAC message
clientSeed:nonce:cursor— never decorative - Phase D set 10 auditor-chosen
pfaudit-seeds; all 500 rounds recompute under them (Step 14) - Sensitivity probe: appending a single character to the client seed changed the roll on 8 / 8 probed bets (Step 14)
audit… seeds throughout Phases A–C (e.g. auditf2456fc140ac in epoch 0, audit4195522a3970 in epoch 113), plus the 10 custom pfaudit- seeds of Phase D. Evidence: dice-master-6700bets.json1.5Nonce Incrementation
The nonce begins at 0 and increments by 1 for each bet under the same seed pair — one nonce per roll. The nonce resets to 0 when the player rotates their seed, starting a new epoch. In this audit we rotated every 50 bets as our chosen sampling cadence (nonces 0–49 per epoch, 134 epochs); epoch length is not enforced by the casino — players can rotate at any time. Step 4 audits every epoch for gaps, reuse, and resets: a skipped nonce is where a discarded unfavourable outcome would hide, so the sequence must be contiguous. The epoch size is bound to the constant in src/config.ts rather than read from the dataset header, so a withheld bet cannot be absorbed by shrinking the declared epoch. A gap caused by our own capture script — a retried request whose bet still recomputes — would be a capture flag: disclosed with a limitation row and verdict-neutral. A gap or repeat on the operator side is a hard fail. This capture has no gaps and 0 capture flags.
if (missing.length > 0) { const ss = seedMap.get(hash); let allVerify = ss !== undefined; if (ss) { for (const b of sorted) { const served = Number(b.roll); const local = diceRoll(ss, b.clientSeed, b.nonce) / SCALE; if (!servedRollMatches(served, local)) { allVerify = false; break; } } } if (allVerify) disclosedGaps.push(`epoch ${epochNum} (Phase ${phase}), nonce ${missing.join(',')} orphaned; all recorded bets verify`); else hardFailures.push(`Epoch ${epochNum}: unverifiable nonce gap at ${missing.join(',')}`); } // … if (hardFailures.length > 0) { s4status = 'FAIL'; s4detail = `${hardFailures.length} violations: ${hardFailures.slice(0, 3).join('; ')}`; } else if (disclosedGaps.length > 0) { s4status = 'FLAG'; s4origin = 'capture'; // …1.6Deterministic Mapping
The RNG algorithm is fully deterministic: given the same server seed, client seed and nonce, diceRoll always produces the exact same roll. The algorithm is a single HMAC-SHA256 draw at cursor 0 — the first 32-bit big-endian chunk below the rejection ceiling, reduced modulo 10,000 — returning the roll in hundredths (2401 for a roll of 24.01), which the suite divides by 100. No timing, no bet amount, no band, no UI state enters the derivation. Our implementation, written from the specification, reproduced all 6,700 captured roll values with a byte-identical comparison on the two-decimal grid. The bet record below is the featured bet used throughout this report — recompute it yourself with the snippet.
// §2 Step 2: the dice roll is the draw over N = 10000 outcomes at cursor 0,// an integer in hundredths (0..9999 shown as 0.00..99.99).export function diceRoll(serverSeed: string, clientSeed: string, nonce: number): number { return generateProvablyFairNumber(serverSeed, clientSeed, nonce, 0, 10000);}roll byte-identically (Step 5). Zero mismatches, zero skipped. No external entropy is mixed into the Dice RNG.Real Bet Verified:
{ … "epoch": 0, "phase": "A", "id": "kzLjSr11zq5GvEOHlj9n6", "params": { "lower": 0, "upper": 92.37, "inverted": false }, "nonce": 0, "clientSeed": "auditf2456fc140ac", … "roll": 24.01, "win": true, "multiplier": 1.071776550828191, "result": "won", "betAmount": 0.1, "winningAmount": 0.10717766, …}Verification:
// verify-dice.js — recompute a LIQD Dice roll from its seedsconst { createHmac } = require('node:crypto');function draw(seed, client, nonce) { const digest = createHmac('sha256', Buffer.from(seed, 'hex')) .update(`${client}:${nonce}:0`).digest(); const maxFair = Math.floor(0x1_0000_0000 / 10000) * 10000; // 4,294,960,000 for (let o = 0; o + 4 <= digest.length; o += 4) { const chunk = digest.readUInt32BE(o); if (chunk < maxFair) return chunk % 10000; } throw new Error('all eight chunks rejected (not specified; about 7e-47 per bet)');}const roll = draw('015307d5a3f62db476210c3797bac8b7', 'auditf2456fc140ac', 0) / 100;console.log(roll); // → 24.01console.log(roll >= 0 && roll < 92.37); // → true (under 92.37 — the lower bound is in, the upper is out)1.7Win-Rule Reproduction & Client Seed Influence
Determinism covers the number; Step 6 covers what LIQD did with it. For every bet the recomputed roll is passed through the half-open rule LIQD’s verifier returns at the bounds (E13) — inBand = roll ≥ lower AND roll < upper, inverted for outside — and the result must equal the served win flag: 6,700 / 6,700 agree, across all four modes, including the two captured rolls that landed exactly on a lower bound (a roll of 0.00 under [0, 94.69) and a roll of 99.00 over [99, 100), both served as wins). To confirm the client seed is a genuine input to the HMAC-SHA256 computation, Step 14 recomputes all 500 Phase D bets under their auditor-chosen pfaudit- seeds (500 / 500 reproduce) and then probes sensitivity directly: appending one character to the recorded client seed and re-deriving changed the roll on 8 / 8 probed bets. Because every one of the 6,700 rolls requires its recorded seed to reproduce, and a chance collision of two different seeds on the same roll has probability 1 in 10,000, the seed is demonstrably load-bearing.
// ── Step 6: Win-rule reproduction ──────────────────────────────────────────── // The recomputed roll under the published band rule must reproduce the served win flag. let winChecked = 0, winFails = 0; for (const b of bets) { const ss = seedMap.get(b.hashedServerSeed); if (!ss) continue; const local = diceRoll(ss, b.clientSeed, b.nonce) / SCALE; if (diceWin(local, b.params) !== !!b.win) winFails++; winChecked++; } const s6 = step(6, 'Win-Rule Reproduction', winFails === 0 && winChecked === bets.length ? 'PASS' : 'FAIL', winFails === 0 ? `${winChecked}/${bets.length} bets: recomputed roll ∈/∉ band reproduces the served win flag (inverted honoured)` : `${winFails} win-flag mismatches`, );Technical Evidence & Verification5 sections
1.8Evidence Coverage Summary7 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Seed hash integrity (Step 1) | 134 / 134 revealed seeds hash-verified | Pass |
| Next-seed pre-commitment chain (Step 2) | 133 / 133 epoch transitions + the pre-capture link into epoch 0 | Pass |
| Hash consistency (Step 3) | 134 / 134 epochs (50 bets each) | Pass |
| Nonce audit (Step 4) | 0 gaps, 0 duplicates across 134 epochs; 0 capture flags | Pass |
| Roll recomputation (Step 5) | 6,700 / 6,700 — byte-identical on the two-decimal grid, with our own implementation | Pass |
| Win-rule reproduction (Step 6) | 6,700 / 6,700 served win flags reproduced under the half-open rule | Pass |
| Client seed influence (Step 14) | 500 / 500 Phase D bets recompute; 8 / 8 sensitivity probes change the roll | Pass |
1.9Code References7 source files
| File | Purpose |
|---|---|
| tests/verify.ts | 22-step verification pipeline (Steps 1–6 cover S1) |
| tests/steps/commitment.ts | Steps 1–4: Commit-reveal integrity checks and nonce audit |
| tests/steps/determinism.ts | Steps 5–6: Roll recomputation and win-rule reproduction |
| tests/steps/phase-d.ts | Step 14: Custom client-seed control and sensitivity probe |
| src/rng.ts | HMAC-SHA256 single draw (commitHash, generateProvablyFairNumber, diceRoll) — written from docs/ALGORITHM.md |
| src/loader.ts | Dataset loading + SHA-256 hash guard |
| capture/capture-dice.reference.mjs | Capture methodology record (Phases A–D), authentication removed |
1.10Datasets UsedSource artifacts
| Property | Value |
|---|---|
| Source | Live Dice game data from LIQD’s pre-launch environment (qa.liqd.com) — 6,700 real bets across 4 phases, USDC |
| Capture environment | Pre-launch host qa.liqd.com · one authenticated account · one session |
| Total Records | 6,700 bets across 134 epochs (50 bets each) |
| Captured | 27 August 2026 (18:52–20:30 UTC) |
| SHA-256 | ca1a181a9b4c89a1823e15fe71603b7a5e2267382c134873d80cba536d3aef34 |
Fields used: serverSeed, hashedServerSeed, nextHashedServerSeed, clientSeed, nonce, params (lower, upper, inverted), roll, win, multiplier, betAmount, winningAmount, stakeWallet, winningAmountWallet
src/loader.ts recomputes the SHA-256 above and aborts the suite before any step runs on a mismatch. The capture methodology ships as a reference record (capture/capture-dice.reference.mjs — authentication removed; it documents the capture, it does not replay it). Numeric response fields are stored as JavaScript numbers; the original decimal text is not part of the dataset. Every computational claim downstream of the dataset is reproducible from the file itself.
1.11Verified Invariants10 invariants
| Invariant | Result |
|---|---|
| SHA-256(utf8(serverSeed)) = hashedServerSeed for all 134 revealed seeds | Pass |
| Next-seed pre-commitment chain intact for all 133 transitions, plus the pre-capture link into epoch 0 | Pass |
| hashedServerSeed constant within epoch for all 134 epochs — no mid-epoch seed switching | Pass |
| Zero nonce gaps within any epoch (contiguous 0–49) | Pass |
| Zero nonce duplicates within any epoch | Pass |
| Every epoch contains exactly 50 bets (134 / 134), bound to the code constant, not the dataset header | Pass |
| Same inputs produce the same roll, byte-identical on the two-decimal grid, for all 6,700 bets | Pass |
| Recomputed roll reproduces the served win flag under the half-open rule for all 6,700 bets | Pass |
Every nonce window starts at the recorded nonceStart (0) and ends at nonceEnd (49) — 134 / 134 epochs | Pass |
| Custom client seeds honoured: 500 / 500 Phase D bets recompute; a changed seed changes the roll on 8 / 8 probes | Pass |
1.12Reproduction InstructionsRun locally
Clone the repository, install dependencies, and run the verification suite:
git clone https://github.com/ProvablyFair-org/liqd-dice.gitcd liqd-dice && git checkout v1.1.0npm cinpm run verify# Expected output: Steps 1–6 all PASSSteps 1–6 cover all Seed, Nonce & Determinism checks. Expected output:
[PASS] Step 1 — Seed Hash Integrity
[PASS] Step 2 — Next-Seed Pre-Commitment Chain
[PASS] Step 3 — Hash Consistency Within Epoch
[PASS] Step 4 — Nonce Audit
[PASS] Step 5 — Roll Recomputation & Draw Bias Bound
[PASS] Step 6 — Win-Rule ReproductionRNG & Entropy Model
Is the randomness genuinely random, or could it be rigged?
This section verifies that LIQD’s Dice random number generation produces cryptographically sound, unbiased outputs using only the disclosed inputs. The RNG makes one HMAC-SHA256 draw per bet — the first 4-byte chunk of the digest below the maxFair ceiling, reduced modulo 10,000 and placed on the two-decimal 0.00–99.99 grid, with bias-free rejection sampling. The implementation is our own, written from a specification without LIQD’s code; it reproduces all 6,700 live rolls, and LIQD’s own verifier serves only as a cross-check.
What we verified
- HMAC-SHA256 produces cryptographically sound, unpredictable output for each roll
- Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
- Rejection sampling removes modulo bias exactly — every one of the 10,000 grid points is equally likely
- The RNG is our own implementation, written from the specification — it reproduces all 6,700 captured rolls
- Draw uniformity, serial independence, nine effective-edge bands and all 98 over-mode targets pass their screens across 138M simulated rolls
- A changed client seed changes the roll — 8 / 8 sensitivity probes, 500 / 500 custom-seed bets
What this means for you
- Each roll is generated fairly and cannot be skewed
- All 10,000 outcomes from 0.00 to 99.99 are equally likely — no number is favoured
- No hidden randomness or server-side tricks influence the roll
- 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 roll
- Only disclosed inputs affect outcomes — no timestamps, no server-side state, no hidden entropy
- Rejection sampling removes modulo bias exactly — every one of the 10,000 grid points is equally likely
- The RNG is our own implementation, written from the specification — it reproduces all 6,700 captured rolls
- Draw uniformity, serial independence, nine effective-edge bands and all 98 over-mode targets pass their screens across 138M simulated rolls
- A changed client seed changes the roll — 8 / 8 sensitivity probes, 500 / 500 custom-seed bets
- Each roll is generated fairly and cannot be skewed
- All 10,000 outcomes from 0.00 to 99.99 are equally likely — no number is favoured
- No hidden randomness or server-side tricks influence the roll
- 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
Our own implementationPass
docs/ALGORITHM.md) by an implementer who never opened LIQD’s code; its 84 specification checks pass and it reproduces all 6,700 rolls byte-identicallyLIQD verifier cross-checkPass
Modulo biasPass
Key encoding verifiedPass
Serial independencePass
Client seed influencePass
The Dice RNG uses only the disclosed inputs, and our own implementation — written from the specification — reproduces every captured roll. The simulated rolls are consistent with a uniform draw across the 10,000-point grid, and the serial screens find no sign of dependence across 200,000 simulated rolls. The client seed is a genuine input — a one-character change alters the roll on every probe.
| Check | Result | Evidence |
|---|---|---|
| RNG derived only from disclosed inputs | Pass | HMAC-SHA256(serverSeed, clientSeed:nonce:0) — one draw per bet, no hidden entropy |
| Entropy purity | Pass | No timestamps, external APIs, Math.random, or server-side state |
| Our own implementation | Pass | Written from the specification (docs/ALGORITHM.md) by an implementer who never opened LIQD’s code; its 84 specification checks pass and it reproduces all 6,700 rolls byte-identically |
| LIQD verifier cross-check | Pass | LIQD’s verify endpoint, queried as-is, agrees on 8 recorded vectors and the 7 boundary probes (E13) — a cross-check, not the source |
| Modulo bias | Pass | Rejection ceiling 4,294,960,000 = 10,000 × 429,496 — worst-case bias 0, computed from the bound the draw itself applies; no captured draw needed a second chunk (Step 5) |
| Key encoding verified | Pass | Server seed hex-decoded to bytes for the HMAC key; the commitment hashes the seed text — both confirmed via 6,700-bet recomputation |
| Serial independence | Pass | Lag-1 z = 0.159 against the applied critical 2.9352; runs test p = 0.6910 — both clear at α/3 over 200,000 simulated rolls (Step 16) |
| Client seed influence | Pass | A changed client seed changes the roll on 8 / 8 probes; all 500 Phase D custom-seed bets recompute |
RNG derived only from disclosed inputsPass
Entropy purityPass
Our own implementationPass
docs/ALGORITHM.md) by an implementer who never opened LIQD’s code; its 84 specification checks pass and it reproduces all 6,700 rolls byte-identicallyLIQD verifier cross-checkPass
Modulo biasPass
Key encoding verifiedPass
Serial independencePass
Client seed influencePass
The Dice RNG uses only the disclosed inputs, and our own implementation — written from the specification — reproduces every captured roll. The simulated rolls are consistent with a uniform draw across the 10,000-point grid, and the serial screens find no sign of dependence across 200,000 simulated rolls. The client seed is a genuine input — a one-character change alters the roll on every probe.
How It Works — RNG & Entropy Model7 sections
2.1RNG Function Implementation
Each Dice bet makes exactly one draw. An HMAC-SHA256 digest is computed with the hex-decoded server seed as key and clientSeed:nonce:cursor as the message, where cursor is 0. The 32-byte digest is scanned in up to eight 4-byte big-endian chunks; the first chunk below the maxFair ceiling is reduced % 10000 to give an integer draw in [0, 10000), and dividing by 100 places it on the two-decimal roll scale 0.00 – 99.99. If all eight chunks were rejected — about 7 × 10⁻⁴⁷ per bet — the specification defines no further draw, so our implementation stops with an error; no captured draw needed even a second chunk. The band and stake never enter this function — they are applied to the roll afterwards.
| Component | Detail |
|---|---|
| Hash function | HMAC-SHA256 (32-byte digest) |
| Key | hex_decode(serverSeed) — 16 raw bytes (distinct from the commitment domain, which hashes the seed text) |
| Message | clientSeed:nonce:cursor — UTF-8, literal colons, no padding; cursor = 0 |
| Extraction | 4-byte big-endian chunks → readUInt32BE → uint32, up to 8 per digest |
| Rejection | chunk < maxFair where maxFair = floor(2³² / 10000) × 10000 = 4,294,960,000 |
| Reduction | uint32 % 10000 → integer draw in [0, 10000) |
| Scaling | draw / 100 → roll on the 0.00 – 99.99 grid |
| Exhaustion path | All 8 chunks rejected → no further draw defined; our implementation stops with an error (≈ 7 × 10⁻⁴⁷ per bet, never reached) |
| Output | One roll per bet — the band is applied to it afterwards |
// §2 Step 2: walk the 32-byte digest in eight 4-byte big-endian chunks; the// first chunk below LIMIT gives the draw as chunk mod range. If all eight are// rejected the spec has no settled continuation (O2), so we throw rather than// invent one.export function generateProvablyFairNumber( serverSeed: string, clientSeed: string, nonce: number, cursor: number, range: number,): number { const limit = maxFairFor(range); const digest = hmacDigest(serverSeed, clientSeed, nonce, cursor); for (let i = 0; i + 4 <= digest.length; i += 4) { const v = digest.readUInt32BE(i); if (v < limit) return v % range; } throw new Error("all 32-bit chunks of the digest were rejected (spec O2: continuation unspecified)");}docs/ALGORITHM.md) by an implementer who never opened LIQD’s code, its client bundle or any port of it, and the specification’s 84 vectors and property checks pass (tests/dice/rngSpecTests.ts). LIQD’s own verify endpoint is queried as-is and used only as a cross-check — it agrees on eight recorded vectors and the seven boundary probes (E13).2.2Entropy Sources
All randomness derives exclusively from the deterministic HMAC-SHA256 function combining three cleanly separated inputs — each visible in the fairness panel or the bet record:
| Source | Controlled By | Purpose |
|---|---|---|
| Server Seed | Casino | Base randomness — the HMAC key, committed via SHA-256 hash of the seed text before betting |
| Client Seed | Player | Player-contributed entropy — mixed into every HMAC message |
| Nonce | System | Uniqueness per bet (0–49 within each epoch, audited gap-free) |
| Cursor | System | Fixed at 0 — one draw per bet. An index, not an entropy source |
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, and no external beacon (no drand round, block hash, or VRF). Only: HMAC-SHA256(hexDecode(serverSeed), clientSeed:nonce:0). Because no external entropy is used, cherry-pick containment is structural rather than cryptographic — the commitment plus the player-controlled client seed carry the fairness; S4 covers exactly what that means and how it is checked.
2.3Modulo Bias Analysis
The draw is chunk % 10000. Modulo bias occurs because 2^32 is not evenly divisible by 10,000 — the 32-bit range holds 429,496 complete cycles of 10,000 plus a remainder of 7,296 values, so without a guard the residues 0–7,295 would each be reachable one extra time. The algorithm eliminates this bias entirely via rejection sampling: a ceiling maxFair = floor(2^32 / 10000) × 10000 ensures only unbiased chunks are used. Any chunk ≥ maxFair is discarded and the next 4-byte chunk of the same digest is tried:
range = 10,000
2^32 = 4,294,967,296
floor(2^32 / 10000) = 429,496
maxFair = 429,496 × 10,000 = 4,294,960,000
dead values = 2^32 − maxFair = 7,296
P(chunk rejected) = 7,296 / 2^32 ≈ 1.70e-6
P(all 8 chunks rejected) = (7,296 / 2^32)^8 ≈ 6.9e-47
draws in this capture = 6,700 (one per bet)
expected chunk rejections ≈ 6,700 × 1.70e-6 ≈ 0.011
observed = 0 (every captured roll came from the first chunk)Below maxFair, each of the 10,000 residues owns exactly 429,496 values — perfectly uniform. The 7,296 values at the top of the 32-bit space are discarded. Step 5 computes this bound from the same maxFairFor(10000) the draw applies (src/bias.ts), so the figures describe the code that produced the rolls, and it fails the audit if the accepted window is not a whole number of ranges. Our implementation’s rejection branch is exercised by a constructed unit vector (tests/dice/rngTests.ts: first chunk ≥ maxFair is skipped, second chunk taken); across the 6,700 captured draws every roll came from the first chunk, matching the ≈ 0.011 expectation.
2.4RNG Isolation
Each bet uses a unique nonce in the HMAC message (clientSeed:nonce:0), ensuring per-bet outputs are cryptographically independent. HMAC-SHA256 is a pseudorandom function — knowing one roll gives no usable information about any other roll under the same seed pair. There is no shared state between bets or between epochs, and there is no second draw inside a bet for the band to interact with: the roll is produced first, in isolation, and the band is applied to it afterwards.
Evidence: The diceRoll implementation in 1.6 confirms this — it is a pure function with no class state, no external calls, and no cross-bet memory. It takes exactly three arguments (serverSeed, clientSeed, nonce) — no wager, no band, no timestamp, no account identifier — and returns a number. Same inputs always produce the same output; if any hidden input influenced the roll, the reference implementation (which lacks that input) could not reproduce 6,700 / 6,700 rounds. The strongest cross-phase evidence: Phase C repeats the game at $10.00 (100× the $0.10 stake) and Phase D swaps in ten auditor-chosen client seeds — both recompute under the identical three-argument function.
2.5Monte Carlo Simulation (138M Rolls)
A 138,000,000-roll Monte Carlo simulation checks the algorithm’s distribution at scale. Its seed pairs are our own — derived from a pinned master seed as HMAC(SIM_MASTER_SEED, counter), independent of LIQD’s seeds and rotated every 50 rolls like real epochs — so the whole experiment replays byte for byte: npm run test:full regenerates the committed artifact exactly. Pass 1 has four parts: 2,000,000 draws binned into 100 cells for uniformity (with the first 200,000 rolls screened for serial structure), 98 over-mode targets at 1,000,000 bets each (over 2.00 to over 99.00, win chance 98% to 1%), nine named bands at 2,000,000 bets each across all four modes, and a 20,000,000-bet run on under 50 (1.98×) sampled at seven checkpoints. Step 16 scores the committed artifact deterministically — every derived field, standard error and win count is recomputed from the stored statistics and must match exactly.
| Metric | Value |
|---|---|
| Rolls | 138,000,000 — 2M uniformity draws + 98 targets × 1M bets + 9 bands × 2M bets + 20M under-50 bets |
| Seeds | Our own, from the pinned master seed — replayable byte for byte |
| Draw uniformity (χ², 100 bins, df = 99) | χ² = 108.885, p = 0.2335 — two-sided pass at α/3 (2,000,000 draws) |
| Serial screen (first 200,000 rolls) | lag-1 z = 0.159 < 2.9352; runs test p = 0.6910 ≥ 3.333e-3 |
| 98 over-mode targets (1M each) | 98 / 98 within 5·SE + 1e-4 of 0.99; mean 99.0203%, 0.98·SE from 99.0000%; largest deviation 3.08·SE (over 56.00) |
| Effective-edge bands (9 × 2M) | 9 / 9 within 5·SE + 1e-4 of 0.99; largest deviation 2.12·SE (over 90) |
| Under-50 run (20M) | 99.0476% vs the proven 99.0000% — 2.15·SE, inside the 5·SE tolerance |
export const SIM_UNIFORMITY_DRAWS = 2_000_000;export const SIM_SERIAL_DRAWS = 200_000;export const SIM_EDGE_BETS = 2_000_000;// …export const SIM_RTP_BETS = 20_000_000;/** The sample sizes the convergence series must contain, in order. */export const SIM_RTP_MARKS: readonly number[] = [1_000, 10_000, 100_000, 1_000_000, 5_000_000, 10_000_000, SIM_RTP_BETS];// …export const SIM_TARGET_BETS = 1_000_000;/** Per-target sample sizes at which the cross-target mean RTP is recorded, in order; the last is SIM_TARGET_BETS. */export const SIM_TARGET_MARKS: readonly number[] = [10_000, 50_000, 100_000, 500_000, SIM_TARGET_BETS];// …export const SIM_MASTER_SEED = 'a11d17e5c0ffee5ea1ed0c5b1b7ea5e5d1ce5eed5a1710add1ce5eed10addba5';// …export const SIM_TARGETS: readonly { label: string; params: DiceParams }[] = Array.from( { length: 98 }, (_, i) => ({ label: `over ${i + 2}.00`, params: { lower: i + 2, upper: 100, inverted: false } }),);All four families pass. The three statistical screens (uniformity, lag-1, runs) are each judged at α/3 = 3.333e-3 so the family has level α = 0.01; the uniformity screen is two-sided, rejecting a statistic too uniform to be a real 2,000,000-draw sample as well as one that is non-uniform. The 98 targets and the nine effective-edge rows must each sit within 5 × SE + 0.0001 of 0.99, and each row’s simulated RTP must be exactly the return that some integer win count produces under the producer’s own accumulation — a published number that no integer count can reach fails the step.
Methodology: the roll is a single uniform outcome per bet, so a 100-cell chi-squared against N/100 is the correct uniformity test. Every target’s client seed and first server seed are re-derived from the master seed at its fixed position in the seed stream, and the 98-target mean is recomputed at each of its five marks (10K, 50K, 100K, 500K and 1M bets per target). The under-50 series stores the integer win count, RTP and standard error at each checkpoint; Step 16 recomputes every standard error and confirms the win counts (493 / 5,046 / 50,371 / 499,779 / 2,501,269 / 5,002,354 / 10,004,806) are cumulatively consistent and reconstruct bit-for-bit to the published values.
2.6Serial Independence
Serial independence ensures consecutive rolls are not correlated — a high roll on one bet does not make the next more or less likely to be high. Two tests were applied to the first 200,000 rolls of the Pass 1 uniformity stream, each judged at the Bonferroni-corrected α/3 = 3.333e-3 (the third screen in the family is uniformity itself):
Lag-1 autocorrelation: Pearson correlation between consecutive rolls, r₁ = 0.000355, standardised as z = r₁ · √n = 0.159. The critical value is the two-sided normal quantile at α/3, 2.9352; the artifact stores the same value and Step 16 checks they agree. z clears it comfortably.
Wald-Wolfowitz runs test: Tests whether the sequence of above/below-50 outcomes has the expected number of runs. z = −0.398, two-sided p = 0.6910 — re-derived from the stored cell counts (99,912 runs, n₁ = 100,103 of 200,000) rather than read; clear at α/3.
export function lag1Autocorrelation(series: number[]): number { const n = series.length; let mean = 0; for (let i = 0; i < n; i++) mean += series[i]; mean /= n; let num = 0, den = 0; for (let i = 0; i < n - 1; i++) num += (series[i] - mean) * (series[i + 1] - mean); for (let i = 0; i < n; i++) den += (series[i] - mean) ** 2; return den === 0 ? 0 : num / den;}2.7Worked Example — Full RNG Trace
Real bet from dataset — bet kzLjSr11zq5GvEOHlj9n6 (Phase A, epoch 0), nonce 0, under 92.37. Verified from dice-master-6700bets.json. One HMAC digest, eight candidate chunks — the first is below the ceiling, so it is the draw:
serverSeed = 015307d5a3f62db476210c3797bac8b7
clientSeed = auditf2456fc140ac
nonce = 0
cursor = 0
message = auditf2456fc140ac:0:0
digest = 475461d14638cca3e385e940e763bf808fa6ef6c05f1a83c333dc25e586c5b6e
maxFair = 4,294,960,000| chunk | hex | uint32 | < maxFair? | draw (% 10000) |
|---|---|---|---|---|
| 0 | 475461d1 | 1,196,712,401 | yes — taken | 2401 → roll 24.01 |
| 1 | 4638cca3 | 1,178,127,523 | (not needed) | — |
| 2 | e385e940 | 3,817,204,032 | (not needed) | — |
| 7 | 586c5b6e | 1,483,496,302 | (not needed) | — |
1,196,712,401 % 10,000 = 2,401, and 2,401 / 100 = 24.01, exactly as the live game recorded. Against the band [0, 92.37) the roll wins — the featured bet's payout is traced in S3.8.Technical Evidence & Verification5 sections
2.8Evidence Coverage Summary10 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Our implementation (Step 5) | 6,700 / 6,700 bets — written from the specification; its 84 specification checks pass | Pass |
| LIQD verifier cross-check | 8 / 8 recorded vectors and 7 / 7 boundary probes (E13) agree | Pass |
| Key encoding (hex vs UTF-8) | Confirmed via recomputation — HMAC keys the decoded bytes, commitment hashes the text | Pass |
| Modulo bias analysis | Rejection ceiling 4,294,960,000; bias 0 from the draw’s own bound; 0 rejected chunks in 6,700 draws; branch unit-tested | Pass |
| External entropy non-participation (Step 5) | 6,700 / 6,700 reproduced from disclosed inputs alone | Pass |
| Draw uniformity (Step 16) | χ² = 108.885, df = 99, p = 0.2335 over 2,000,000 draws — two-sided pass at α/3 | Pass |
| Serial independence (Step 16) | lag-1 z = 0.159 < 2.9352; runs p = 0.6910 (200,000 rolls) | Pass |
| Effective-edge bands (Step 16) | 9 / 9 bands within 5·SE + 1e-4 of 0.99 at 2,000,000 bets each | Pass |
| Live-sample serial screen (Step 22, informational) | The 6,700 captured rolls in order: lag-1 z = 1.057, runs p = 0.2618 — no serial dependence | Pass |
| Client seed influence (Step 14) | 500 / 500 Phase D + 8 / 8 sensitivity probes | Pass |
2.9Code References10 source files
| File | Purpose |
|---|---|
| src/rng.ts | HMAC-SHA256 single draw (commitHash, generateProvablyFairNumber, diceRoll) — written from docs/ALGORITHM.md |
| docs/ALGORITHM.md | The specification the RNG was written from — inputs, commitment, derivation, win rule, settlement and their sources |
| src/bias.ts | The draw-bias bound, computed from the same maxFairFor the draw applies (Step 5) |
| src/simulate.ts | Monte Carlo simulation (138M rolls, two-pass), deterministic from the pinned master seed, and chart rendering |
| src/stats.ts | Chi-squared (exact regularized incomplete gamma), lag-1 autocorrelation, Wald-Wolfowitz runs test, AS 241 normal quantile, exact binomial tail |
| src/sim-checks.ts | Integer win-count recovery, convergence-series validation, the seven scored statistical screens and their nominal levels |
| tests/steps/determinism.ts | Steps 5–6: Roll recomputation, draw-bias bound and win-rule reproduction |
| tests/steps/simulation.ts | Steps 16–17: artifact binding and statistic reconciliation for Pass 1; Pass 2 re-derivation from the dataset’s own seeds |
| tests/dice/rngSpecTests.ts | The specification’s 84 vectors and property checks |
| tests/dice/rngTests.ts | 18 unit tests — eight recorded verifier vectors, the rejection branch, commitment convention, client-seed sensitivity, the boundary probes (E13), 59,998-band edge identity |
2.10Verified Invariants12 invariants
| Invariant | Result |
|---|---|
| HMAC-SHA256 output matches live game for all 6,700 bets | Pass |
| Key is hex-decoded (16 bytes, not UTF-8) — wrong encoding produces wrong rolls | Pass |
| Message format is clientSeed:nonce:cursor with cursor 0 — literal colons, no padding | Pass |
| Chunks scanned big-endian, 4 bytes, first accepted wins | Pass |
| Rejection sampling eliminates modulo bias — the draw’s own bound is 10,000 × 429,496, so every residue owns exactly 429,496 values | Pass |
| Zero rejected chunks across all 6,700 captured draws on a direct count (expected ≈ 0.011); rejection branch unit-tested | Pass |
| No external entropy sources required for roll computation | Pass |
| Draw distribution is consistent with uniform across the 10,000-point grid (χ² p = 0.2335, 2,000,000 draws, two-sided) | Pass |
| Lag-1 z < 2.9352 and runs p ≥ 3.333e-3 on 200,000 consecutive rolls | Pass |
| Nine effective-edge bands within 5·SE + 1e-4 of 0.99, each reconstructing bit-for-bit from an integer win count | Pass |
| 98 / 98 over-mode targets within 5·SE + 1e-4; the 98-target mean lands 0.98·SE from the 99.0000% identity; the 20M under-50 run lands 2.15·SE, inside the 5·SE tolerance | Pass |
| Client seed change produces a different roll on 8 / 8 probes; 500 / 500 custom-seed bets recompute | Pass |
2.11Datasets UsedSource artifacts
2.12Reproduction InstructionsRun locally
Clone the repository, install dependencies, and regenerate the simulation before verifying:
git clone https://github.com/ProvablyFair-org/liqd-dice.gitcd liqd-dice && git checkout v1.1.0npm cinpm run test:full # regenerates the 138M-roll simulation from its pinned master seed, then verifiescat outputs/simulation-results.jsonS2-related steps:
[PASS] Step 5 — Roll Recomputation & Draw Bias Bound
[PASS] Step 6 — Win-Rule Reproduction
[PASS] Step 14 — Custom Client-Seed Control
[PASS] Step 16 — Simulation — Pass 1 (RNG + Edge)
[PASS] Step 22 — Live-Sample Serial Independence (informational)
The regenerated outputs/simulation-results.json and outputs/rtp-convergence.html are byte-identical to the committed files (SHA-256 55873640… and 6b7b6bb7…).Verifier Parity
Does the live game actually follow its own rules?
This section validates that our implementation produces the exact same roll as the live game for every single bet — byte-identical on the two-decimal grid. It also re-derives every win flag from the recomputed roll and the half-open band rule, and confirms every credited amount matches the quotient multiplier 9900 / b through the two-stage 8-decimal settlement. Any mismatch would invalidate the fairness guarantee.
What we verified
- Every bet independently recomputed from seeds — the roll verified byte-identically, not just the payout
- Payout correctness: all 2,807 wins credit the two-stage settlement of bet × 9900 / b with zero tolerance; all 3,893 losses pay 0
- Win/loss label re-derived from the recomputed roll and band for every round — 0 mislabelled settlements
- Bet amount is not an input to the RNG — the roll depends only on seeds and nonce
- All four capture phases recomputed identically (random bands, the 49.5× / 99× tail, elevated stake, client seed variation)
What this means for you
- The verifier isn’t a simulation — it produces the exact same roll 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 which band you choose
- The game LIQD served in the capture follows the specified algorithm exactly
- Every bet independently recomputed from seeds — the roll verified byte-identically, not just the payout
- Payout correctness: all 2,807 wins credit the two-stage settlement of bet × 9900 / b with zero tolerance; all 3,893 losses pay 0
- Win/loss label re-derived from the recomputed roll and band for every round — 0 mislabelled settlements
- Bet amount is not an input to the RNG — the roll depends only on seeds and nonce
- All four capture phases recomputed identically (random bands, the 49.5× / 99× tail, elevated stake, client seed variation)
- The verifier isn’t a simulation — it produces the exact same roll 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 which band you choose
- The game LIQD served in the capture follows the specified algorithm exactly
Roll recomputationPass
Win-rule reproductionPass
inverted honouredMultiplier derivationPass
Credit arithmeticPass
Wallet-field reconciliationPass
Bet-size independencePass
Mode & phase completenessPass
All 6,700 bets matched our recomputation exactly — rolls, win flags, multipliers and credited amounts, across all four capture phases and all four modes. Payout math correct at both stakes, with the quotient multiplier reproduced bit-for-bit on every win.
| Check | Result | Evidence |
|---|---|---|
| Roll recomputation | Pass | 6,700 / 6,700 exact match — the roll verified byte-identically on the two-decimal grid for every bet |
| Win-rule reproduction | Pass | 6,700 / 6,700 served win flags reproduced by the half-open band rule on the recomputed roll, inverted honoured |
| Multiplier derivation | Pass | All 2,807 wins: served multiplier == 9900 / basisPoints as a binary64 quotient, bit-exact (max deviation 0) |
| Credit arithmetic | Pass | All 6,700 credits: winningAmount == round-half-even₈(stake × round₈(multiplier)) with zero tolerance; losses pay exactly 0 |
| Wallet-field reconciliation | Pass | stakeWallet / winningAmountWallet present and equal to the game ledger on 6,700 / 6,700 bets (Step 10) |
| Bet-size independence | Pass | Bet amount is absent from the RNG input — same roll derivation at $0.10 and $10. Tested in Phase C (200 / 200) |
| Mode & phase completeness | Pass | All four modes (under / over / inside / outside) and all four planned phases present at their planned counts |
Roll recomputationPass
Win-rule reproductionPass
inverted honouredMultiplier derivationPass
Credit arithmeticPass
Wallet-field reconciliationPass
Bet-size independencePass
Mode & phase completenessPass
All 6,700 bets matched our recomputation exactly — rolls, win flags, multipliers and credited amounts, across all four capture phases and all four modes. Payout math correct at both stakes, with the quotient multiplier reproduced bit-for-bit on every win.
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. Commit-reveal shows the seed didn’t change; parity shows the game the player experienced is the game the seed dictates. For every captured round the audit rebuilds the roll from raw inputs with our own code, re-derives the win flag from the band, and recomputes the credit from the quotient multiplier and the two-stage settlement — then compares all three against what LIQD actually served and credited. 100% parity is required: a single mismatch anywhere in 6,700 rounds would indicate a bug in the verification logic, manipulation in the live game, or an inconsistent RNG implementation.
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 band breadth across every mode, the high-multiplier tail, bet-size invariance, and client seed variation.
| Phase | Bets | Config | Bet Amount | Purpose |
|---|---|---|---|---|
| A — Band coverage | 5,000 | Random bands across all four modes (epochs 0–99) | $0.10 | Verify the roll, win rule and multiplier formula across 4,824 distinct bands |
| B — High-multiplier tail | 1,000 | 500 × over 98 (49.5×) + 500 × over 99 (99×) (epochs 100–119) | $0.10 | Maximum variance; the 1% band pays the largest multiplier in the capture |
| C — Stake equivalence | 200 | over 50 (1.98×) (epochs 120–123) | $10.00 | Confirm bet amount is not an RNG input (100× the $0.10 stake) |
| D — Client-seed variation | 500 | Random bands under 10 custom pfaudit- seeds (epochs 124–133) | $0.10 | Confirms seed control is player-side and the input is consumed |
3.3Roll Recomputation (Step 5)
For every bet, the audit independently computes the roll as diceRoll(serverSeed, clientSeed, nonce) / 100 with the epoch’s revealed server seed and compares it to the server-reported roll. The comparison is strict: the served value must be on the two-decimal grid, inside the roll domain, and equal to the recomputed value — no tolerance. The computation uses one HMAC-SHA256 draw with the hex-decoded server seed as key, rejection-sampled below 4,294,960,000 and reduced modulo 10,000.
let checked = 0, mismatches = 0; let sample = ''; for (const b of bets) { const ss = seedMap.get(b.hashedServerSeed); if (!ss) continue; const local = diceRoll(ss, b.clientSeed, b.nonce) / SCALE; const served = Number(b.roll); // Finiteness guard: a non-numeric served roll makes // `Math.abs(local - NaN) > 1e-9` false, silently passing; reject it explicitly. if (!servedRollMatches(served, local)) { mismatches++; if (!sample) sample = `epoch ${b.epoch} nonce ${b.nonce}: local ${local} vs served ${b.roll}`; } checked++; } const rollsOk = mismatches === 0 && checked === bets.length;roll in the dataset.3.4Multiplier Derivation + Credit Arithmetic (Steps 7–8)
For each of the 2,807 winning bets, Step 7 rebuilds the multiplier from the band alone: the winning width in basis points, b = round(100 × W), and the quotient 9900 / b. The served multiplier must equal that quotient bit-for-bit as a binary64 — not within a tolerance. It does on all 2,807 wins; the algebraically identical float form (100 / W) × 0.99 agrees bit-for-bit on only 1,719 of them, which is why the audit pins the integer-basis-point quotient as the served formula. b is our derivation — the field does not appear in the capture; the served value matching the quotient on every win is the evidence for the form. Step 8 then replays the money: winningAmount must equal ROUND_HALF_EVEN₈(stake × ROUND₈(multiplier)) — the multiplier rounded to 8 decimals, the product rounded half-even to 8 decimals in exact integer arithmetic on 1e-8 units — with zero tolerance, and a loss must credit exactly 0. All 6,700 reconcile. Against the raw un-rounded product (decimal stake × the exact rational 9900 / b, the operator’s own number system rather than binary64) the settlement rounds the player down on 1,341 wins and up on 1,316, lands exactly on the product on 150, with a maximum absolute adjustment of 5.50e-9 USDC and a net of +4.91e-8 USDC across the capture — no directional bias. 274 of the 2,807 wins settle on an exact 8-decimal tie, where the half-even rule decides the last unit.
| Metric | Count |
|---|---|
Wins where multiplier === 9900 / basisPoints bit-for-bit | 2,807 / 2,807 |
Wins where the float closed form (100 / W) × 0.99 also matches bit-for-bit | 1,719 / 2,807 (same rational, different binary64) |
Bets where winningAmount equals the two-stage settlement exactly | 6,700 / 6,700 |
| Wins rounded down / up / exactly on the raw product | 1,341 / 1,316 / 150 |
| Max |credit − stake × mult| · net across the capture | 5.50e-9 USDC · +4.91e-8 USDC |
| Wins settling on an exact 8-decimal tie (half-even decides) | 274 |
const numeratorIntegral = Number.isInteger(PAYOUT_NUMERATOR); let mChecked = 0, mFails = 0, maxDev = 0, closedFormAgrees = 0; for (const b of bets) { if (!b.win) continue; const bp = payoutBasisPoints(b.params); // integer basis points (auditor-derived) const served = PAYOUT_NUMERATOR / bp; // operator's exact multiplier const dev = Math.abs(Number(b.multiplier) - served); if (dev > maxDev) maxDev = dev; if (Number(b.multiplier) !== served) mFails++; // bit-exact pin, not tolerance // The float closed form is the SAME rational and a DIFFERENT binary64 on many bands. Counted, so // the report does not call the two forms "bit-exact" equal. if (Number(b.multiplier) === quotedMultiplier(b.params)) closedFormAgrees++; mChecked++; } /** The credit in exact integer 1e-8 units — the form with no rounding ambiguity at all. */export function settledCreditUnits(stake: number, multiplier: number): bigint { const stakeUnits = BigInt(Math.round(stake * 1e8)); const multUnits = BigInt(Math.round(multiplier * 1e8)); // stage 1: ROUND_8(multiplier) const product = stakeUnits * multUnits; // in 1e-16 units let q = product / UNITS_PER_WHOLE; const r = product % UNITS_PER_WHOLE; if (r > HALF_UNIT) q += 1n; else if (r === HALF_UNIT && (q % 2n) === 1n) q += 1n; // exact tie → round to even return q;}3.5Loss Settlement & Wallet Fields (Steps 9–10)
Step 9 checks every losing bet in isolation: winningAmount must be exactly 0 and multiplier exactly 0 — 3,893 / 3,893 conform, with no rounding band to hide a stray credit in. Step 10 reads the wallet-denominated settle-response fields: winningAmountWallet must equal the same two-stage settlement of stakeWallet × multiplier, and both wallet fields must equal the game ledger (winningAmount, betAmount) — 6,700 / 6,700, 0 missing, 0 divergent. Every quoted multiplier also sits within the audit odds bounds [1.0102, 9900].
| Scope | Coverage | Verified |
|---|---|---|
Losses — winningAmount == 0 and multiplier == 0 | 3,893 rounds | 3,893 / 3,893 |
Wallet credit — winningAmountWallet == settle(stakeWallet × multiplier) | 6,700 rounds | 6,700 / 6,700 (0 missing) |
| Wallet ledger == game ledger (stake and credit fields agree) | 6,700 rounds | 6,700 / 6,700 (0 divergent) |
| Quoted multipliers within [1.0102, 9900] | 6,700 rounds | 0 out of range |
3.6Settlement Determinism (Step 20)
Steps 7 and 8 check the served multiplier and the credit against it. Step 20 removes the served multiplier from the picture entirely: for every bet, the outcome and the credited amount are rebuilt from (roll, band, stake) alone — win flag from the half-open rule, multiplier as the integer-basis-point quotient 9900 / b, credit through the two-stage settlement — and must match the served settlement with zero tolerance. In a legitimate game nothing else can move the money: no hidden fee, no per-account adjustment, no stake-dependent formula. All 6,700 rounds conform, across all four modes, 2,807 wins and 3,893 losses.
/** The expected winning multiplier: the integer-basis-point quotient, not the float closed form. */ const expectedMultiplier = (p: typeof bets[number]['params']) => { const bps = payoutBasisPoints(p); return bps > 0 ? PAYOUT_NUMERATOR / bps : 0; }; for (const b of bets) { chk++; modesSeen.add(bandMode(b.params)); const win = diceWin(Number(b.roll), b.params); if (win) wins++; else losses++; const expected = win ? settledCredit(Number(b.betAmount), expectedMultiplier(b.params)) : 0; const credited = Number(b.winningAmount); // win flag must match the roll/band rule AND the credit must be the determined amount if (win !== !!b.win || credited !== expected) { bad++; if (fails.length < 3) fails.push(`epoch ${b.epoch} nonce ${b.nonce}: credited ${credited} vs determined ${expected}`); } } const coverOk = modesSeen.size === 4 && wins > 0 && losses > 0;3.7Phase C — Bet-Size Invariance (Step 18)
Phase C placed 200 bets at $10.00 on over 50 (1.98×) — 100× the $0.10 stake used in every other phase. All 200 rolls were recomputed byte-identically from revealed seeds (0 mismatches). The structural point: diceRoll takes no wager argument, so the roll cannot depend on stake, and the effective edge for the band is 1.0000% at every stake. The only place betAmount appears is the settlement multiplication, and the only stakes in the dataset are exactly $0.10 and $10.00.
3.8Worked Example — Full Parity Verification
Real bet from Phase A — bet kzLjSr11zq5GvEOHlj9n6 (epoch 0), nonce 0, under 92.37 at $0.10. Verified from dice-master-6700bets.json — the same bet LIQD's own verifier endpoint reproduced in the E13 settled-bet cross-check:
serverSeed = 015307d5a3f62db476210c3797bac8b7
clientSeed = auditf2456fc140ac
nonce = 0
band = [0, 92.37), not inverted (under 92.37)
betAmount = $0.10
recorded = roll 24.01 · win true · multiplier 1.071776550828191 · winningAmount 0.10717766
commitment = 918c2a302c7219c91cf31ce3619acb0a…| Step | Process | Output |
|---|---|---|
| 1 | Recompute the commitment — SHA-256(utf8(serverSeed)) | Matches the pre-published hash |
| 2 | Run diceRoll(serverSeed, clientSeed, 0) / 100 | 24.01 — equals the recorded roll |
| 3 | Apply the half-open band [0, 92.37) | In band — the won label is correct |
| 4 | Derive the multiplier 9900 / 9237 | 1.071776550828191 — the served field, bit-for-bit |
| 5 | Two-stage settlement of 0.10 × round8(m) | 0.107177655 is an exact tie → half-even → 0.10717766, the credited winningAmount, verbatim |
commitHash(serverSeed) → matches the published commitment ✓diceRoll(seed, client, 0) → 2401 → roll 24.01 (chunk 0x475461d1 = 1,196,712,401 % 10,000 = 2,401) ✓0 ≤ 24.01 < 92.37 → in band → won ✓b = round(100 × 92.37) = 9237 → multiplier = 9900 / 9237 = 1.071776550828191 (bit-exact) ✓round8(m) = 1.07177655 → 0.10 × 1.07177655 = 0.107177655 → half-even at the tie → 0.10717766 ✓Technical Evidence & Verification5 sections
3.9Evidence Coverage Summary12 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Roll recomputation (Step 5) | 6,700 / 6,700 rounds — byte-identical on the two-decimal grid | Pass |
| Win-rule reproduction (Step 6) | 6,700 / 6,700 served win flags under the half-open rule | Pass |
| Multiplier derivation (Step 7) | 2,807 / 2,807 wins — 9900 / b bit-exact | Pass |
| Credit arithmetic (Step 8) | 6,700 / 6,700 — two-stage 8-decimal settlement at zero tolerance | Pass |
| Loss settlement (Step 9) | 3,893 / 3,893 losses pay exactly zero | Pass |
| Wallet fields & odds bounds (Step 10) | 6,700 / 6,700 wallet fields reconcile; 0 multipliers out of [1.0102, 9900] | Pass |
| Dataset integrity & population (Step 11) | SHA-256 pin match; 6,700 bets · 134 seeds · 50 per epoch, bound to code constants | Pass |
| Phase & mode coverage (Step 12) | A 5,000 · B 1,000 · C 200 · D 500; all four modes; both stakes | Pass |
| Phase D client-seed variation (Step 14) | 500 bets, 10 custom client seeds — 500 / 500 match | Pass |
| Phase C bet-size invariance (Step 18) | 200 / 200 at $10.00 | Pass |
| Settlement determinism (Step 20) | 6,700 / 6,700 rebuilt from (roll, band, stake) alone | Pass |
| Multiplier monotonicity (Step 21) | 4,176 distinct win-chance points, 0 inversions, span 1.0105×–99× | Pass |
3.10Code References6 source files
| File | Purpose |
|---|---|
| tests/steps/determinism.ts | Steps 5–6: Roll recomputation and win-rule reproduction |
| tests/steps/payouts.ts | Steps 7–10: Multiplier derivation, credit arithmetic, loss settlement, wallet fields and odds bounds |
| tests/steps/dataset.ts | Steps 11–12: Dataset integrity and phase / mode coverage — population bound to src/config.ts |
| tests/steps/phase-d.ts | Step 14: Phase D custom client-seed variation and sensitivity |
| tests/steps/standardization.ts | Steps 18, 20, 21: Bet-size invariance, settlement determinism, multiplier monotonicity |
| src/config.ts | payoutBasisPoints, quotedMultiplier, diceWin, settledCreditUnits, creditResidualSign — the formulas every payout is replayed against |
3.11Datasets UsedSource artifacts
3.12Verified Invariants12 invariants
| Invariant | Result |
|---|---|
| Computed roll matches the served roll byte-identically for all 6,700 bets | Pass |
| Half-open rule on the recomputed roll reproduces the served win flag for all 6,700 bets | Pass |
| multiplier === 9900 / basisPoints (binary64 quotient) for all 2,807 wins | Pass |
| winningAmount == ROUND_HALF_EVEN₈(stake × ROUND₈(multiplier)) for all 6,700 bets, zero tolerance | Pass |
| winningAmount == 0 and multiplier == 0 for all 3,893 losses | Pass |
| Wallet-denominated settle fields equal the game ledger on all 6,700 bets | Pass |
| Credit is fully determined by (roll, band, stake) — no served multiplier needed — for all 6,700 bets | Pass |
| Phase C ($10.00) rolls derive from the identical three-argument function as $0.10 bets | Pass |
| No hidden inputs beyond (serverSeed, clientSeed, nonce) — stakes present are exactly {$0.10, $10.00} | Pass |
| All four modes present at volume; 5,304 distinct bands; 4,176 distinct win-chance values | Pass |
| Phase D: 500/500 rolls verified across 10 custom pfaudit client seeds | Pass |
| Multiplier strictly decreasing in win chance across 4,176 points, spanning 1.0105×–99× | Pass |
3.13Reproduction InstructionsRun locally
Clone the repository, install dependencies, and run the verification suite:
git clone https://github.com/ProvablyFair-org/liqd-dice.gitcd liqd-dice && git checkout v1.1.0npm cinpm run verify# Expected output: Steps 5, 6, 7, 8, 9, 10, 11, 12, 14, 18, 20, 21 all PASSS3-related steps:
[PASS] Step 5 — Roll Recomputation & Draw Bias Bound
[PASS] Step 6 — Win-Rule Reproduction
[PASS] Step 7 — Multiplier Derivation
[PASS] Step 8 — Credit Arithmetic
[PASS] Step 9 — Loss Settlement
[PASS] Step 10 — Wallet Credit & Odds Bounds
[PASS] Step 11 — Dataset Integrity
[PASS] Step 12 — Phase & Mode Coverage
[PASS] Step 14 — Custom Client-Seed Control
[PASS] Step 18 — Bet-Size Invariance
[PASS] Step 20 — Settlement Determinism
[PASS] Step 21 — Multiplier Monotonicity & RangeRTP & Payout Logic
Is the house edge what the casino claims?
This section verifies that the flat 1.00% house edge is exactly what’s advertised on every band in every mode. The key test is anti-circularity: we prove the RTP from first principles, not from any casino-supplied figure — a band of b basis points wins on exactly b of the 10,000 equally likely grid points and pays 9900 / b, so P(win) × multiplier = 0.99 cancels exactly. We check that identity on all 6,700 captured bets, hold LIQD’s published 99% to every one of the 9,800 settings under the RTP claim rule, compare it against 98 million simulated bets across 98 targets, and test whether the casino pre-selected seeds against the player.
What we verified
- House edge is exactly 1.00% — flat across every band, every mode and every bet size
- RTP proven from first principles: (b / 10,000) × (9900 / b) = 0.99 — as an integer identity on all 6,700 captured bets, not a float approximation
- LIQD’s published 99% RTP holds on all 9,800 settings — worst gap 0.000000 pp against the ±0.1 pp band of the RTP claim rule
- The quotient multiplier is served bit-exact on every one of the 2,807 captured wins, and the 8-decimal settlement rounding is directionless (1,341 down, 1,316 up, 150 exact)
- 98 targets × 1M simulated bets: all 98 within 5·SE, mean 99.0203% — 0.98·SE from 99.0000%; uniformity, serial and nine effective-edge bands also pass
- Cherry-pick test: the bands LIQD served on its 134 seeds show no return deficit (z = 0.752, p = 0.7738)
- Bet amount does not influence the roll — confirmed at $0.10 and $10
What this means for you
- The house edge on Dice is a flat 1.00%, the same whichever mode, band or bet size you choose
- The 99% LIQD advertises is what every setting actually returns
- The RTP proof is derived independently — it doesn’t rely on trusting the casino
- The casino’s seeds show no sign of being chosen against the player
- Your bet amount doesn’t affect the number rolled
- The multiplier on screen is exactly what you are paid — settlement rounds at the 8th decimal in both directions, never systematically against you
- House edge is exactly 1.00% — flat across every band, every mode and every bet size
- RTP proven from first principles: (b / 10,000) × (9900 / b) = 0.99 — as an integer identity on all 6,700 captured bets, not a float approximation
- LIQD’s published 99% RTP holds on all 9,800 settings — worst gap 0.000000 pp against the ±0.1 pp band of the RTP claim rule
- The quotient multiplier is served bit-exact on every one of the 2,807 captured wins, and the 8-decimal settlement rounding is directionless (1,341 down, 1,316 up, 150 exact)
- 98 targets × 1M simulated bets: all 98 within 5·SE, mean 99.0203% — 0.98·SE from 99.0000%; uniformity, serial and nine effective-edge bands also pass
- Cherry-pick test: the bands LIQD served on its 134 seeds show no return deficit (z = 0.752, p = 0.7738)
- Bet amount does not influence the roll — confirmed at $0.10 and $10
- The house edge on Dice is a flat 1.00%, the same whichever mode, band or bet size you choose
- The 99% LIQD advertises is what every setting actually returns
- The RTP proof is derived independently — it doesn’t rely on trusting the casino
- The casino’s seeds show no sign of being chosen against the player
- Your bet amount doesn’t affect the number rolled
- The multiplier on screen is exactly what you are paid — settlement rounds at the 8th decimal in both directions, never systematically against you
Anti-circularityPass
House edge auditPass
RTP claim (RTP-CLAIM 1.2.0)Pass
Simulated RTP (Pass 1)Pass
Cherry-pick test (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
The 99.0000% RTP is proven mathematically from grid arithmetic — under the half-open rule every band of b basis points wins on exactly b of 10,000 grid points and pays 9900 / b, so the product is 0.99 for all 5,304 captured bands and for every one of the 9,800 settings the game offers. LIQD’s published 99% therefore holds at every setting, with zero gap. This is a first-principles proof, not a statistical estimate; 98 million simulated bets across 98 targets are consistent with it, and the cherry-pick test finds no sign of seed selection. The house edge is flat at 1.00% — no mode or band carries a hidden penalty, and the only money-side approximation, the 8-decimal settlement rounding, is directionless.
| Check | Result | Evidence |
|---|---|---|
| Anti-circularity | Pass | P(win) × multiplier = (b / 10,000) × (9900 / b) = 0.99 for every band — proven by cancellation of the grid count against the quotient formula, no casino data used; closed-form win counts equal a literal 10,000-point tally on all 5,304 captured bands (Step 13) |
| House edge audit | Pass | Flat 1.00% house edge asserted in exact integers (100 · winCount · 9900 === 99 · 10,000 · b, no tolerance) on 6,700 / 6,700 bets, across under / over / inside / outside; the float form of the same quantity strays from 0.01 by at most 6.32e-15 (Step 15) |
| RTP claim (RTP-CLAIM 1.2.0) | Pass | LIQD publishes 99% RTP (Edge 1.00%) on the Dice game page; all 9,800 settings return exactly 99.000000% — worst gap 0.000000 pp against the ±0.1 pp band (Step 15) |
| Simulated RTP (Pass 1) | Pass | 98 / 98 targets (over 2.00 to over 99.00, 1M bets each) within 5·SE + 1e-4, mean 99.0203% at 0.98·SE from 99.0000%; draw uniformity p = 0.2335 (two-sided), serial screens clear, 9 / 9 effective-edge bands within 5·SE + 1e-4 (Step 16) |
| Cherry-pick test (Pass 2) | Pass | Served-band return over 134 seeds × 10,000 nonces: z = 0.752, one-sided p = 0.7738 against α = 0.01 — no return deficit (Step 17) |
| Bet-size invariance | Pass | Bet amount is not an input to the RNG — same roll derivation at $0.10 and $10. Tested in Phase C (200 / 200, Step 18) |
| Multiplier formula | Pass | 9900 / b served bit-exact on all 2,807 wins; strictly decreasing in win chance across 4,176 distinct points; span 1.0105×–99× within [1.0102, 9900] (Steps 7, 21) |
Anti-circularityPass
House edge auditPass
RTP claim (RTP-CLAIM 1.2.0)Pass
Simulated RTP (Pass 1)Pass
Cherry-pick test (Pass 2)Pass
Bet-size invariancePass
Multiplier formulaPass
The 99.0000% RTP is proven mathematically from grid arithmetic — under the half-open rule every band of b basis points wins on exactly b of 10,000 grid points and pays 9900 / b, so the product is 0.99 for all 5,304 captured bands and for every one of the 9,800 settings the game offers. LIQD’s published 99% therefore holds at every setting, with zero gap. This is a first-principles proof, not a statistical estimate; 98 million simulated bets across 98 targets are consistent with it, and the cherry-pick test finds no sign of seed selection. The house edge is flat at 1.00% — no mode or band carries a hidden penalty, and the only money-side approximation, the 8-decimal settlement rounding, is directionless.
How It Works — RTP & Payout Logic8 sections
4.1Anti-Circularity Proof (Steps 13, 15)
The anti-circularity proof establishes the 99.0000% RTP from first principles without using any casino-supplied probability data. Dice's proof is an algebraic identity rather than a summation — the win probability and the payout multiplier both contain the band's basis-point count b, which cancels exactly. Step 13 derives the win probability from grid arithmetic only, proves the check is falsifiable with a negative control (a deliberately corrupted roll must be rejected by the same predicate Step 5 uses), and anchors the closed-form win count with a counting leg — every one of the 10,000 grid points is passed through the win rule and tallied for each of the 5,304 distinct captured bands, a method in which a scaling or rounding slip is inexpressible. Step 15 then asserts the identity itself in exact integers on every captured bet:
| Component | Formula | Source |
|---|---|---|
| Win probability | b / 10,000 — b winning grid points under the half-open rule | Grid arithmetic — computed by the audit |
| Counting anchor | Literal tally: apply diceWin to every draw 0…9999 and count | 5,304 distinct captured bands — 0 mismatches against the closed form, no tolerance |
| Quoted multiplier | 9900 / b — (1 − 0.01) × 10,000 over the basis points | Closed form — validated bit-exact against every captured win (Step 7) |
| RTP | (b / 10,000) × (9900 / b) = 9900 / 10,000 = 0.99 | The product — b cancels |
| Integer form | 100 · winCount · 9900 === 99 · 10,000 · b (winCount = the literal tally, b = the priced basis points) | Asserted with === on 6,700 / 6,700 bets (Step 15) and over 59,998 bands in the unit suite |
| Band | Win chance (b / 10,000) | Multiplier (9900 / b) | P(win) × Multiplier | RTP |
|---|---|---|---|---|
| under 50 | 5000 / 10000 = 0.500000 | 1.98000000 | 0.990000 | 99.0000% |
| over 90 | 1000 / 10000 = 0.100000 | 9.90000000 | 0.990000 | 99.0000% |
| inside 25–75 | 5000 / 10000 = 0.500000 | 1.98000000 | 0.990000 | 99.0000% |
| outside 25–75 | 5000 / 10000 = 0.500000 | 1.98000000 | 0.990000 | 99.0000% |
| under 92.37 (featured bet) | 9237 / 10000 = 0.923700 | 1.07177655 | 0.990000 | 99.0000% |
| over 99 | 100 / 10000 = 0.010000 | 99.00000000 | 0.990000 | 99.0000% |
| under 0.01 | 1 / 10000 = 0.000100 | 9900.00000000 | 0.990000 | 99.0000% |
=== on 6,700 / 6,700 bets (Step 15). Theoretical RTP proof is non-circular and closed-form.Anti-Circularity Verification:
/** * Step 13: Anti-Circularity — the verifier's independence from operator data. * The theoretical quantities (win probability, multiplier, edge) are pure arithmetic over * the roll grid and band geometry — they never read an operator payout/odds field. To prove * the check is falsifiable (not a vacuous PASS), we run a NEGATIVE CONTROL: corrupt one * recomputed roll and confirm the determinism logic would reject it. */ /** * Literal tally over the 10,000-point grid: apply the win rule to every draw and count. * This is the genuinely arithmetic-error-immune anchor — it cannot share a scaling or * rounding mistake with the closed form, because it never scales a bound at all. */function enumCount(p: DiceParams): number { let c = 0; for (let d = 0; d < RANGE; d++) if (diceWin(d / SCALE, p)) c++; return c;}Why this proof is non-circular: P(win) = b / 10,000 comes from counting grid points, and 9900 / b is the quoted multiplier — LIQD’s 99% return divided by the win chance. They are reciprocals up to 0.99 — they cancel exactly, no summation, no approximation, no "converges to". The identity holds by inspection for every band with b ≥ 1, which is why the 99.0000% RTP is preserved on every mode and every width, from a 98% band to the single-grid-point 9900× tail. What the identity depends on is the half-open rule — with an inclusive upper bound a band would win on b + 1 points and the edge would shrink as the band narrows, reaching a player-favourable 198% at the 0.01% extreme. That rule is what LIQD’s own verifier returns at the bound (E13, S4.7) and what all 6,700 settled win flags reproduce. What the audit adds is evidence that LIQD’s implementation honours the algebra on every captured bet — the quotient really is what gets served and paid (Steps 7, 8, 20) — and that the probability side is derived without touching a single operator-supplied number.
4.2RTP Claim Rule (Step 15)
The RTP claim rule (RTP-CLAIM 1.2.0) is part of the audit method: the RTP a casino publishes must hold at every setting the game offers. A published figure counts wherever a player can find it — on the game page, the fairness page, the help page, the bet panel, the rules, or in the game settings the site loads. Each setting’s RTP is computed exactly, as a rational number, from the grid and the multiplier formula — never from the casino’s figure — and must sit within ±0.1 percentage points of the claim. A setting below that band on the house side is a miss: the verdict then reads RTP FIGURE OVERSTATED, even when every draw check passes. A setting above the claim never fails. The rule is scored on Step 15, the step that derives the effective edge.
| Item | Detail |
|---|---|
| Claim | 99% RTP — a flat 1.00% house edge |
| Where LIQD publishes it | The Dice game page, liqd.com/originals/dice: header "Edge: 1.00%", stats panel "RTP 99%" and "House Edge 1%", page text "every setting in Dice returns 99%" — read logged out on 1 October 2026 (evidence/rtp-page-labels-2026-10-01.json) |
| Settings tested | 9,800 — every legal band width, win chance 0.01% to 98.00% in basis points (multiplier 9900 / b from 1.0102× to 9900×) |
| Method | Exact rational: b / 10,000 winning draws × the quoted multiplier 9900 / b |
| Band | ±0.1 percentage points (house side fails, player side never fails) |
| True minimum · maximum | 99.000000% · 99.000000% |
| Worst gap | 0.000000 pp |
| Settings beyond the band | 0 on the house side · 0 on the player side |
| Scored on | Step 15 (profile check DICE-19) |
| Result | Pass — the claim holds at every setting |
| Setting (win chance) | Multiplier | Exact RTP | RTP | Gap | Result |
|---|---|---|---|---|---|
| 0.01% (b = 1) | 9900× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 1.00% (b = 100) | 99× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 2.00% (b = 200) | 49.5× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 10.00% (b = 1,000) | 9.9× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 50.00% (b = 5,000) | 1.98× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 92.37% (b = 9,237) | 1.0718× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| 98.00% (b = 9,800) | 1.0102× | 99/100 | 99.000000% | 0.000000 pp | Pass |
| All 9,800 settings | 9900 / b | 99/100 | 99.000000% | 0.000000 pp | Pass — 9,800 / 9,800 |
outputs/rtp-claim-table.json, pinned by SHA-256 and re-derived on every run.4.3House Edge & Settlement Rounding (Step 15)
Step 15 scores the effective edge on every one of the 6,700 captured bets — not a sample of configurations — computing 1 − P(win) × quotedMultiplier from first principles and asserting the underlying identity in exact integers: 100 · winCount · 9900 === 99 · 10,000 · basisPoints, with no tolerance. It holds on 6,700 / 6,700 bets, with 0 player-favourable bands, and by mode the edge is 1.000% flat (under 1,362 bets · outside 1,386 · inside 1,360 · over 2,592 — exactly 1.000% in each). The float form of the same quantity strays from 0.01 by at most 6.32e-15 across the capture and 5.06e-13 at the un-sampled 9900× extreme (upper 100 − lower 99.99 in binary64), which is exactly why the assertion is integral rather than a float comparison. The step also confirms the roll ceiling (max captured roll 99.99), that over 99 pays exactly 99× at a 1.0000% edge on all 500 Phase B bets, and that every captured multiplier sits within the audit bound of 9900×.
The only money-side approximation anywhere in the pipeline is settlement rounding at the 8th decimal. Across the 6,700 captured bets the RTP a player actually receives after that rounding spans 98.999994912% – 99.000005204% — below the nominal 99% on 2,742 bets, above it on 2,687, exactly on it on 1,271, and never at or above 100%. Across the 2,807 winning payments the rounding went against the player 1,341 times, for the player 1,316 times and landed exactly on the product 150 times (maximum 5.50e-9 USDC, net +4.91e-8 USDC) — directionless, and immaterial. These figures are computed by committed code (src/report-figures.ts) into outputs/report-figures.json and reconciled field-for-field by Step 19 on every run.
round8(m), then stake × round8(m) half-even. Player-visible in the 8th decimal, in both directions, maximum 5.50e-9 USDC per win; 274 wins sat on an exact tie where the half-even rule decided the last unit.4.4Simulation Pass 1 — Our Own Seeds (Step 16)
Section 4.1 proves RTP = 99.0000% mathematically. Does the algorithm deliver that at scale? To find out, we simulated 98 target bands at 1,000,000 bets each — every over-mode band from over 2.00 (98% win chance, 1.0102×) to over 99.00 (1%, 99×) — with our own implementation and our own seeds, derived from a pinned master seed, rotated every 50 rolls like real epochs, and replayable byte for byte. If the simulated payouts sit within tolerance of 99.0000%, the implementation matches the math. Pass 1 is scored as follows. The three statistical screens — uniformity, lag-1 and runs — are each judged at α/3, so together they hold a family-wise level of α = 0.01; the 98 targets, the nine effective-edge bands, the 98-target mean and the under-50 run’s final point must each sit within 5·SE + 1e-4 of 99.0000%:
98 over-mode targets: over 2.00 to over 99.00 at 1,000,000 bets each — 98,000,000 bets. 98 / 98 within 5·SE + 1e-4 of theory (68 within 1·SE, 95 within 2·SE, 97 within 3·SE; largest over 56.00 at 3.08·SE); 51 above theory, 46 below, 1 exactly on it. Their mean is 99.0203% at 1M bets per target, 0.98·SE from 99.0000% — the chart above. Every target’s seeds are re-derived from the master seed and every simulated RTP reconstructed bit-for-bit from an integer win count.
Draw uniformity: 2,000,000 draws over 100 bins (χ², df = 99, exact incomplete-gamma p). Two-sided at α/3 — χ² = 108.885, p = 0.2335; the lower tail would reject a statistic too uniform to be a real sample of that size.
Serial screen: Lag-1 autocorrelation and the Wald-Wolfowitz runs test on the first 200,000 rolls. z = 0.159 against the applied critical 2.9352; runs p = 0.6910 — both clear at α/3.
Effective-edge bands: Nine canonical bands at 2,000,000 bets each — the table below. 9 / 9 within 5·SE + 1e-4 of 0.99, band set identical to SIM_EDGE_BANDS, every derived field re-derived and every simulated RTP reconstructed bit-for-bit from an integer win count.
Under-50 run: 20,000,000 bets on under 50 (1.98×) sampled at seven pinned marks; final 99.0476% vs 99.00%, Δ = 2.15·SE. All seven standard errors recomputed exactly; the seven win counts (493 … 10,004,806) cumulatively consistent and reconstructing bit-for-bit to the published RTPs.
| Band | Theoretical RTP | Simulated RTP (2M) | Deviation |
|---|---|---|---|
| under 50 (1.98×) | 99.0000% | 98.998% | 0.03·SE |
| over 50 (1.98×) | 99.0000% | 98.930% | 1.00·SE |
| under 10 (9.9×) | 99.0000% | 98.896% | 0.50·SE |
| over 90 (9.9×) | 99.0000% | 99.446% | 2.12·SE |
| inside 25–75 (1.98×) | 99.0000% | 99.068% | 0.97·SE |
| outside 25–75 (1.98×) | 99.0000% | 99.113% | 1.61·SE |
| over 98 (49.5×) | 99.0000% | 98.597% | 0.82·SE |
| over 99 (99×) | 99.0000% | 98.951% | 0.07·SE |
| under 1 (99×) | 99.0000% | 99.480% | 0.69·SE |
const SIM_MASTER_KEY = Buffer.from(SIM_MASTER_SEED, 'hex');let simSeedCounter = 0;const randSeed = (): string => createHmac('sha256', SIM_MASTER_KEY).update(String(simSeedCounter++)).digest().subarray(0, 16).toString('hex');// … const targetRows = SIM_TARGETS.map(({ label, params: p }) => { const m = quotedMultiplier(p); const seedCounterFirst = simSeedCounter; // Exactly the band loop above: s = randSeed(), c = randSeed(), a fresh server seed every 50 bets. let wn = 0, wi = 0, k = 0; let s = randSeed(); const c = randSeed(); let firstServerSeed = ''; const marks: { n: number; wins: number; rtp: number; se: number }[] = []; for (let i = 0; i < SIM_TARGET_BETS; i++) { if (i % 50 === 0) { s = randSeed(); if (i === 0) firstServerSeed = s; } const roll = drawFrom(s, c, i % 50) / SCALE; if (diceWin(roll, p)) { wn += m; wi++; } if (k < SIM_TARGET_MARKS.length && i + 1 === SIM_TARGET_MARKS[k]) { const at = i + 1, r = wn / at; marks.push({ n: at, wins: wi, rtp: r, se: convergenceSE(at, r, wi, m) }); k++; } } // … const targetPoints = SIM_TARGET_MARKS.map((nAt, j) => ({ n: nAt, ...crossTargetPoint(targetRows.map((r) => r.marks[j])) }));What is a Monte Carlo simulation? Instead of proving fairness with algebra alone, we simulate millions of game rounds. Each round derives a roll from scratch using the same HMAC-SHA256 pipeline as the live game, applies a band, and records whether the bet won and what it paid. We then compare the aggregate return to the theoretical 99.0000%. If they match within tolerance, the math holds in practice — not just on paper.
How the artifact is scored, not believed: Step 16 does not read the simulation’s own pass flags. It recomputes the χ² p-value from the stored statistic, re-derives the lag-1 z from the correlation and the runs z from the stored cell counts, re-derives every target’s and effective-edge row’s multiplier, win probability, theoretical RTP and standard error from the band and the pinned depth, and checks the integer win count behind every published RTP — a published number that no integer win count could produce fails the step (a mutation that sets over 98 to 0.99000495, implying 40,000.2 wins, is caught exactly this way). Because the seeds come from a pinned master seed, npm run test:full regenerates the whole 138M-roll experiment and reproduces the committed artifact byte for byte.
4.5Cherry-Pick Test — Pass 2 (Step 17)
Could the casino have chosen server seeds that pay players less? Pass 2 takes every server seed LIQD actually used — all 134 revealed seeds — and replays each one for 10,000 nonces with the epoch’s recorded client seed. The load-bearing statistic is the served-band return: the payout-weighted return of the bands LIQD actually served in the captured window (nonces 0–49), standardised by its exact expectation and variance — each band’s exact lattice win probability times its multiplier. A seed set chosen against the player would show up as a return deficit in the window the player saw, so the test is one-sided at α = 0.01. Step 17 re-derives every per-epoch row from the revealed seeds and compares it with the artifact exactly. A supplementary screen checks each seed’s draws for unusual non-uniformity against an exact discrete null.
Primary defence — structural: The client seed is player-editable and rotatable after the server-seed commitment is published. An operator that cannot predict your seed cannot pre-evaluate candidates against it — the foreknowledge channel is removed by design, and Phase D exercised the rotation live.
Statistical check: The served-band return test above — a direct check on the money, backing the structural defence.
| Test | Detail |
|---|---|
| Seeds tested | 134 — every revealed server seed in the capture, keyed by its own epoch |
| Replay depth | 10,000 nonces per seed — the captured window is nonces 0–49, the rest is the reference |
| Statistic | Payout-weighted return of the served bands, standardised by its exact per-bet mean and variance; one-sided normal tail |
| Result | z = 0.752, one-sided p = 0.7738 against α = 0.01 — no return deficit (73 epochs below, 2 equal, 59 above expectation) |
| Detection power | A per-bet return deficit of 14.377 pp or more is detected at 80% power |
| Supplementary screen | 50-draw windows over 20 bins against an exact discrete null (832 values from 181,274 partitions): 10 flags vs 5.87 expected, P(X ≥ 10) = 0.0708 — consistent with chance; rejection would need 13 flags |
| Pooled real-roll uniformity | All 6,700 captured rolls, 100 bins: χ² = 92.21, df = 99, p = 0.6724 — recomputed from the dataset, informational |
for (let nn = 0; nn < SIM_PASS2_NONCES; nn++) { const band = served[nn % W2]; const mult = quotedMultiplier(band); const p = discreteWinProbability(band); // EXACT lattice win-probability const roll = diceRoll(serverSeed, clientSeed, nn) / SCALE; const won = diceWin(roll, band); const ret = won ? mult : 0; const eRet = p * mult, vRet = p * (1 - p) * mult * mult; if (nn < W2) { eR += ret; eE += eRet; eV += vRet; earlyCells.push({ band, won }); } else { rR += ret; rE += eRet; rV += vRet; } } // … const sbAggZ = sbTotalEarlyVar > 0 ? (sbTotalEarlyReturn - sbTotalEarlyExpected) / Math.sqrt(sbTotalEarlyVar) : 0; const sbAggP = normalLowerTailP(sbAggZ); // … const sbCherryClean = sbAggP >= SIM_PASS2_ALPHA; // HARD_FAIL on a cherry-pick signal4.6Bet-Size Invariance (Step 18)
Phase C placed 200 bets at $10.00 on over 50 — the 1.98× band, 100× the $0.10 stake used in every other phase. All 200 rolls were recomputed byte-identically from revealed seeds using the same single-draw HMAC-SHA256 path, and every $10.00 win credited exactly stake × 1.98. Bet amount is not an input to the RNG and does not affect the roll distribution or the edge.
| Metric | Phases A, B, D ($0.10) | Phase C ($10.00) |
|---|---|---|
| Rounds | 6,500 | 200 |
| Roll recomputation parity | 6,500 / 6,500 | 200 / 200 |
| Served multiplier = 9900 / b | 2,702 / 2,702 wins | 105 / 105 wins (1.98× on over 50) |
| Credit per win | stake × round8(9900 / b), half-even at 8 dp — every win reproduced (Steps 8, 20) | $19.80000000 — exactly $10.00 × 1.98 |
| Effective edge | 1.0000% on every captured bet | 1.0000% |
Wager argument in diceRoll | none — the function has no stake parameter | none — same function, same code path |
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, on the evidence behind the boundary rule, and on fields outside the scored surface. At the per-phase sample sizes in this audit, empirical RTP is not a meaningful measure of fairness — the identity in 4.1 is the authoritative evidence.
| Item | Value | Context |
|---|---|---|
| Live capture RTP | 103.56% | $2,744.36 returned on $2,650.00 wagered — variance illustration only; Phase B’s 23 tail wins alone returned $133.65 on $100 staked |
| Live win rate | 41.90% (2,807 / 6,700) | Mixed win chances across 5,304 bands — informational |
| Roll range | 0.00 – 99.99, mean 49.796 | Ceiling 99.99 confirmed on every bet; both extremes observed |
| Live-sample serial screen (Step 22) | lag-1 z = 1.057 · runs p = 0.2618 | The 6,700 captured rolls in capture order: 3,305 runs vs 3,350.9 expected — no serial dependence; reported, never scored |
| Boundary evidence (E13) | 7 verifier-endpoint probes, 31 August 2026 | Roll 3.61 at an unused nonce of the epoch 0 seed: roll == upper loses (non-inverted) and wins (inverted) — half-open confirmed; probes 1 and 5 are the two that discriminate between the candidate rules |
| Boundary hits in the capture | 2 lower-bound hits, 0 upper-bound hits | 4,106 bets had a reachable upper bound (expected hits 0.41); none landed on one, so the E13 verifier checks settle the upper-bound rule |
| Odds settings and recorded acceptance | minOdds 1.0102 · maxOdds 99 · maxBet 9,898 USDC | The settings field reads maxOdds 99, yet E14 records a $0.10 bet on [0, 0.01) accepted at a 9900× quote and settled on 1 September 2026 (a loss, roll 84.00); the audit uses the live-witnessed 9900× bound |
| Phase D exposure | 10 epochs, $50.00 staked, $48.93 paid | Expected $49.50; z = −0.14 |
| Settlement residual | max 5.50e-9 USDC · net +4.91e-8 USDC | Directionless at the 8th decimal — 1,341 wins down, 1,316 up, 150 exact; 274 exact ties |
| Capture-side self-check fields | localRoll, localWin, verified, modelMultiplier, multOk, walletOk, meta.edgeScan | Capture annotations, not audit findings — scored verification derives every result from the recorded inputs and ignores them |
4.8Worked Example — Payout Verification
Real bet from Phase B — bet RbGgV6toJS-1CgkkJI0vC (epoch 113), nonce 47, over 99 at $0.10. Verified from dice-master-6700bets.json — one of the two captured rolls that landed exactly on a band's lower bound, and one of the four 99× wins in the capture:
serverSeed = 36a8d90f5e7cd710ae29bfc89eb709c7
clientSeed = audit4195522a3970
nonce = 47
band = [99, 100), not inverted (over 99)
betAmount = $0.10
recorded = roll 99.00 · win true · multiplier 99 · winningAmount 9.9Step 1 — Roll computation: HMAC-SHA256(seed, "audit4195522a3970:47:0") → first chunk 0xbd93f34c = 3,180,589,900 < maxFair → % 10000 = 9900 → roll 99.00 — matches the record.
Step 2 — Win check: 99.00 ≥ 99 and 99.00 < 100 → in band → the round is legitimately won. The roll sits exactly on the lower bound, which the half-open rule includes — the served flag agrees.
Step 3 — Multiplier derivation: W = 100 − 99 = 1 → b = 100 → 9900 / 100 = 99 exactly; the served multiplier is 99.
Step 4 — Payout: round8(99) = 99 → 0.10 × 99 = 9.9 — no tie, no rounding → $9.90000000, the credited winningAmount, verbatim from the record.
| Step | Process | Output |
|---|---|---|
| 1 | Recompute the roll from seeds | 99.00 — byte-identical |
| 2 | Apply the half-open band [99, 100) | Lower bound is in — won confirmed |
| 3 | Quotient multiplier 9900 / 100 | 99 |
| 4 | Two-stage settlement of 0.10 × 99 | $9.90 credited |
| 5 | RTP identity at this band | (100 / 10,000) × 99 = 0.9900 — the flat edge, exactly |
winCount(over 99) = grid points 9900 … 9999 = 100 → P(win) = 100 / 10,000 = 0.01quotedMultiplier(over 99) = 9900 / 100 = 99RTP = P(win) × multiplier = 0.01 × 99 = 0.9900 → 99.0000% (b cancels; only the edge remains)Technical Evidence & Verification5 sections
4.9Evidence Coverage Summary9 checks
| Verification Area | Coverage | Result |
|---|---|---|
| Anti-circularity (Step 13) | 5,304 distinct bands — closed-form win count == literal 10,000-point tally, 0 mismatches; negative control rejects a corrupted roll | Pass |
| House edge audit (Step 15) | 6,700 / 6,700 bets — integer identity 100 · winCount · 9900 === 99 · 10,000 · b; 0 player-favourable bands; all four modes at 1.000% | Pass |
| RTP claim rule (Step 15) | 9,800 / 9,800 settings at exactly 99.000000%; worst gap 0.000000 pp against ±0.1 pp | Pass |
| Multiplier & credit reconciliation (Steps 7, 8) | 2,807 wins bit-exact 9900 / b; 6,700 credits at zero tolerance | Pass |
| Simulation Pass 1 (Step 16) | 138M rolls — 98 / 98 targets, mean 99.0203% at 0.98·SE; uniformity p = 0.2335, serial clear, 9 / 9 bands | Pass |
| Simulation Pass 2 (Step 17) | Served-band return over 134 seeds × 10,000 nonces: z = 0.752, p = 0.7738; supplementary screen 10 flags vs 5.87 (P = 0.0708) | Pass |
| Bet-size invariance (Step 18) | 200 / 200 Phase C at $10.00 | Pass |
| Artifact & figure integrity (Step 19) | Dataset, simulation and chart pins match; report figures reconcile field-for-field; 5,609 numeric values inside domains derived from src/config.ts | Pass |
| Multiplier monotonicity (Step 21) | 4,176 distinct win-chance points, 0 inversions, 1.0105×–99× within [1.0102, 9900] | Pass |
4.10Code References13 source files
| File | Purpose |
|---|---|
| tests/steps/anti-circularity.ts | Step 13: Negative control + literal grid enumeration anchor |
| tests/steps/boundary.ts | Step 15: Roll ceiling, over-99 multiplier, odds bounds and the exact integer edge identity on every bet |
| src/rtp-claim.ts | The RTP claim rule, RTP-CLAIM 1.2.0 — every package carries the same file byte for byte |
| src/rtp-settings.ts | The 9,800-setting exact RTP table and the claim it is held to (LIQD’s published 99%) |
| tests/steps/payouts.ts | Steps 7–10: Multiplier derivation, credit arithmetic, loss settlement, wallet fields |
| tests/steps/simulation.ts | Steps 16–17: Simulation integrity and the cherry-pick test (served-band return, exact-null screen) |
| tests/steps/standardization.ts | Steps 18–21: Bet-size invariance, artifact and value-domain integrity, settlement determinism, multiplier monotonicity |
| tests/steps/statistical.ts | Informational items — live RTP, per-mode edge, roll range, win rate, odds settings |
| src/simulate.ts | Monte Carlo simulation (138M rolls, two-pass), deterministic from the pinned master seed — Pass 1 screens, 98 targets, edge bands and the under-50 run, Pass 2 served-band return and exact-null screen |
| src/exact-chi2.ts | Exact discrete null for the Pass 2 supplementary screen — 181,274 partitions, 832 support values, achieved α and the derived flag rate |
| src/stats.ts | chiSquaredTest, lag1Autocorrelation, runsTest, inverseCriticalZ (AS 241), binomialTailP |
| src/config.ts | payoutBasisPoints, quotedMultiplier, discreteWinCount, diceWin, effectiveEdgeIsExact, theoreticalRTP, settledCreditUnits — closed-form formulas |
| src/report-figures.ts | Derived-figure artifact (outputs/report-figures.json) — the producing artifact for the settled-RTP span, residual counts, boundary hit counts, cherry-pick test figures and Phase D exposure, reconciled by Step 19 |
4.11Datasets UsedSource artifacts
4.12Verified Invariants14 invariants
| Invariant | Result |
|---|---|
| (b / 10,000) × (9900 / b) = 0.9900 for every band (non-circular) | Pass |
| 100 · winCount · 9900 === 99 · 10,000 · basisPoints as integers on all 6,700 bets and at the 9900× extreme | Pass |
| LIQD’s published 99% is met by all 9,800 settings — worst gap 0.000000 pp against ±0.1 pp | Pass |
| Closed-form win count equals a literal 10,000-point tally on all 5,304 distinct captured bands | Pass |
| Negative control: a corrupted roll is rejected by the Step 5 predicate (the check is falsifiable) | Pass |
| Flat 1.00% house edge — the quotient multiplier is served bit-exact on all 2,807 wins; losses pay exactly 0 (3,893 / 3,893) | Pass |
| Settled RTP after 8-decimal rounding spans 98.999994912%–99.000005204% across all 6,700 captured bets — never at or above 100% | Pass |
| Draw uniformity χ² p = 0.2335 over 2,000,000 draws, two-sided at α/3 | Pass |
| Lag-1 z = 0.159 < 2.9352 and runs p = 0.6910 ≥ α/3 on 200,000 rolls | Pass |
| 9 / 9 effective-edge bands within 5·SE + 1e-4 of 0.99, each reconstructing from an integer win count | Pass |
| 98 / 98 targets within 5·SE + 1e-4; 98-target mean 99.0203%, 0.98·SE from 99.0000% at 1,000,000 bets per target; 490 / 490 target marks and 5 / 5 mean points reconciled | Pass |
| Under-50 run 99.0476% within 2.15·SE of 99.0000% at 20,000,000 bets; 7 / 7 standard errors and win counts reconciled | Pass |
| No return deficit on the bands served across 134 casino seeds (Pass 2 — z = 0.752, p = 0.7738; 0 row mismatches) | Pass |
| Phase C ($10.00) rolls derive from the same three-argument function as $0.10 bets; bet amount absent from RNG input by construction | Pass |
4.13Reproduction InstructionsRun locally
Clone the repository, install dependencies, and regenerate the simulation before verifying:
git clone https://github.com/ProvablyFair-org/liqd-dice.gitcd liqd-dice && git checkout v1.1.0npm cinpm run test:full # regenerates the simulation from its pinned master seed, then verifieshead -40 outputs/rtp-claim-table.jsoncat outputs/simulation-results.json outputs/report-figures.jsonS4-related steps and the RTP claim rule, as printed:
[PASS] Step 7 — Multiplier Derivation
[PASS] Step 8 — Credit Arithmetic
[PASS] Step 13 — Anti-Circularity (Negative Control + Enumeration Anchor)
[PASS] Step 15 — Odds Boundary & Effective Edge
[PASS] Step 16 — Simulation — Pass 1 (RNG + Edge)
[PASS] Step 17 — Simulation — Pass 2 (Cherry-Pick)
[PASS] Step 18 — Bet-Size Invariance
[PASS] Step 19 — Artifact Hash + Value-Domain Integrity
[PASS] Step 21 — Multiplier Monotonicity & Range
rtp-claim: ENFORCED — PASS
rtp-claim: band ±0.10 pp · 9,800 settings · true min 99.000000% (win chance 0.01%) · true max 99.000000% (win chance 0.01%)
rtp-claim: house side beyond band: 0 · player-favourable beyond band: 0 · worst win chance 0.01% at 0.000000 pp
rtp-claim: outcome PASS · minimal fix: none — the claim holds for every setting it states a figure for
Final 98-target mean RTP in the committed artifact: 0.9902026676413088 against 0.9899999999999987 (the binary64 mean of the 98 theoretical RTPs; 0.99 exactly in rationals). The simulation is deterministic: npm run test:full regenerates outputs/simulation-results.json and outputs/rtp-convergence.html byte for byte, and the verdict is scored against the committed, SHA-256-pinned artifact. The chart above plots the committed 98-target mean series.Fairness Integrity Testing
Does the implementation maintain fairness under non-standard conditions?
Sections 1–4 establish that the game is mathematically fair; Section 5 tests whether the live implementation keeps its integrity under non-standard conditions. It covers 34 integrity tests run against LIQD’s running service across 12 categories: race conditions, input validation, idempotency, authorization, outcome and payout fields, nonce and seed handling, information disclosure, and the provably fair surface itself. 33 passed. One was remediated: under extreme bursts the server answered some requests with generic errors instead of structured overload responses (Low, robustness); LIQD fixed it and we re-verified it. No test altered, replayed or injected an outcome.
What we verified
- Race conditions — can parallel bets, a high-stake over-commit, extreme bursts, or a seed rotation racing a live bet change an outcome or double-settle?
- Nonce tampering — can the sequence be forced, replayed, or skipped?
- Seed handling — can unusual client seeds, or the active server seed, be exposed or overridden?
- Outcome replay — can an identical bet be settled twice, or a timed-out bet settle twice?
- Cross-player isolation — can one player act on another’s account, or after logging out?
- Payout tampering — can roll, multiplier, payout or win-region values be injected, or the payout cap bypassed?
- Parameter limits — can invalid bands, win chances, stakes or currencies be submitted?
What this means for you
- Across the 34 tests, no request path let anyone alter, replay or inject an outcome
- Once a bet is placed, its roll cannot be changed or replayed
- Each bet gets its own server-assigned nonce under a committed seed
- Your bets are bound to your session — no other account can act on them
- The one gap found — generic errors under extreme load instead of structured overload responses — was fixed by LIQD and re-verified; accepted nonces stayed unique throughout
- Race conditions — can parallel bets, a high-stake over-commit, extreme bursts, or a seed rotation racing a live bet change an outcome or double-settle?
- Nonce tampering — can the sequence be forced, replayed, or skipped?
- Seed handling — can unusual client seeds, or the active server seed, be exposed or overridden?
- Outcome replay — can an identical bet be settled twice, or a timed-out bet settle twice?
- Cross-player isolation — can one player act on another’s account, or after logging out?
- Payout tampering — can roll, multiplier, payout or win-region values be injected, or the payout cap bypassed?
- Parameter limits — can invalid bands, win chances, stakes or currencies be submitted?
- Across the 34 tests, no request path let anyone alter, replay or inject an outcome
- Once a bet is placed, its roll cannot be changed or replayed
- Each bet gets its own server-assigned nonce under a committed seed
- Your bets are bound to your session — no other account can act on them
- The one gap found — generic errors under extreme load instead of structured overload responses — was fixed by LIQD and re-verified; accepted nonces stayed unique throughout
Nonce integrityPass
Seed integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
Overload handlingRemediated
34 integrity tests: 33 pass and 1 remediated (overload handling — fixed by LIQD and re-verified; accepted nonces stayed unique throughout). No test altered, replayed or injected an outcome. Test procedures and payloads are withheld by policy — see 5.5.
Category Coverage
| Check | Result | Evidence |
|---|---|---|
| Nonce integrity | Pass | Server-assigned and sequential: injected reused, skipped, zero or negative and overflow-sized nonces are all refused (NONCE-001 … NONCE-004); the nonce advances by one per bet (PF-002) and restarts at 0 on rotation (ENTROPY-004) |
| Seed integrity | Pass | The active seed is never disclosed before rotation (DISCLOSE-001); 70 / 70 rotations racing live bets revealed the committed seed and never reused one (EXTRA-001); unusual and Unicode client seeds handled (SEED-001, SEED-002), and each bet uses the seed set just before it (SEED-003) |
| Outcome determinism | Pass | Live rounds recompute independently to the same roll and band result (EXTRA-002, PARAM-003); a 2,000-roll live sample passes the uniformity test (FAIR-001) |
| Round & player isolation | Pass | 13 / 13 foreign and malformed identifiers rejected; post-logout reuse rejected; parallel identical and high-stake bets settled once each and reconciled to the wallet |
| Payout integrity | Pass | Injected roll, payout, result, multiplier and win-region fields rejected; the 50,000 USDC potential-payout cap refuses over-limit bets; 200 consecutive rounds reconciled exactly |
| Overload handling | Remediated | Extreme bursts drew some generic errors instead of structured overload responses (Low); after LIQD’s fix, overload is answered with structured busy and rate-limit responses, and no refused request consumes a nonce (RACE-004) |
Nonce integrityPass
Seed integrityPass
Outcome determinismPass
Round & player isolationPass
Payout integrityPass
Overload handlingRemediated
34 integrity tests: 33 pass and 1 remediated (overload handling — fixed by LIQD and re-verified; accepted nonces stayed unique throughout). No test altered, replayed or injected an outcome. Test procedures and payloads are withheld by policy — see 5.5.
How It Works — Fairness Integrity Testing2 sections
5.1Framework Overview
Testing follows the ProvablyFair.org Fairness Integrity Framework — a structured methodology derived from real, historically observed failures in provably fair systems. Cryptographic verification (S1–S3) shows the game computed every captured roll from its disclosed inputs; fairness-integrity testing asks whether the surface around that computation holds up under non-standard use — seeds swapped, rounds replayed, parameters injected, settlements raced. These tests ran against LIQD’s running service, not against a dataset. This section covers 34 tests across 12 categories. Each card below gives the control assessed, the broad testing approach, the observed result, its impact and any limitation.
| Category | Tests | What It Catches |
|---|---|---|
| Race Conditions | 5 | Parallel bets racing placement, settlement or seed rotation, and how the service handles extreme bursts |
| Input Validation | 5 | Malformed, wrong-type or out-of-range bands, win chances, stakes, content types and currencies |
| Atomic Balance / Idempotency | 3 | Double debits, orphaned debits and unsafe retry behaviour after a timeout or disconnect |
| Broken Authorization | 2 | Acting on another account, or after the session has ended |
| Max-win Bypass | 4 | Client-supplied outcome, payout or win-region fields, and the stake and potential-payout limits |
| Nonce Manipulation | 4 | Reused, skipped, zero, negative or oversized nonces supplied by the client |
| RNG / PF Verification | 4 | Roll distribution and the multiplier curve, commitment and reveal, and independent recomputation of live rounds |
| RNG / Information Leak | 1 | Disclosure of the active server seed, or of hidden game state, before it is due |
| Entropy / PF Verification | 1 | Server-seed rotation behaviour: fresh seeds, a new commitment and the nonce reset |
| Precision / Payout | 1 | Settlement arithmetic and wallet reconciliation over many rounds |
| Event Replay | 1 | Duplicate or replayed requests settling more or less than once |
| Adversarial Seeds | 3 | Unusual client seeds: length, character sets, Unicode forms and rapid rotation |
5.2Severity Framework & Hard Fail Criteria
Two scales apply in this section. The audit framework decides whether a result can pass at all — any test that lets money settle against a tampered or unverifiable outcome is an automatic hard fail. The integrity matrix records each test’s status — Pass, Remediated, Warning or Fail — and each finding is graded Critical, High, Medium or Low. On this page: one Remediated test, graded Low — overload handling under extreme bursts, fixed by LIQD and re-verified.
| Status | Meaning | Action |
|---|---|---|
| Pass | The service handled the scenario correctly: the control held | None — recorded with its result |
| Remediated | A failed control that LIQD has since fixed, re-tested by ProvablyFair.org on the current build | Recorded with the fix and its re-test |
| Warning | A real defect, or a property not yet proven, without a direct effect on funds or fairness | Recorded with its impact; open until a re-test resolves it |
| Fail | A defect affecting funds, fairness or security | Remediation and a re-test required |
| Condition | Consequence |
|---|---|
| A substituted server seed is accepted against a published commitment | Hard fail |
| A revealed seed fails its commitment and play continues | Hard fail |
| Any settled round's roll cannot be reproduced from disclosed inputs | Hard fail |
| A credited payout deviates from the quotient multiplier and settlement rule | Hard fail |
| Nonce reuse or gaps that could conceal discarded outcomes | Hard fail |
34 tests · 33 pass · 1 remediated
Race Conditions5/5
LIQD-DICE-RACE-004LowRemediatedOverload handling under extreme bursts
- Control assessed
- Overload is answered with structured busy or rate-limit responses — never a generic server error — with no duplicate nonce and no internal detail in error bodies.
- Testing approach
- Bursts of parallel bets from one account at increasing volume; status codes, error bodies and nonces checked.
- Observed result
- Before the fix, the largest burst drew a handful of generic server errors, though accepted nonces stayed unique and no error body disclosed internal detail. After LIQD’s fix, re-tested on 23 September 2026: overload is answered with structured busy and rate-limit responses carrying retry guidance; accepted nonces are unique and consecutive, so no refused request consumed one.
- Impact
- Low · robustness — accepted nonces stayed unique and no error disclosed internal detail. Fixed by LIQD and re-verified.
- Limitations
- The test checked status codes, error bodies and nonces.
LIQD-DICE-EXTRA-001PassSeed rotation racing live bets
- Control assessed
- A rotation racing bets cannot reveal a seed early or inconsistently, or bring a revealed seed back into use.
- Testing approach
- Seventy rotations fired against bets in flight; each revealed seed checked against its pre-race commitment.
- Observed result
- All 70 rotations succeeded; each revealed seed matched its pre-race commitment, and none reappeared as a later active seed.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-RACE-001PassNonce uniqueness under parallel bets
- Control assessed
- Every accepted bet gets its own server-assigned nonce in sequence, and each roll is bound to that nonce, even under parallel load.
- Testing approach
- Bursts of parallel bets from one account; nonces and response order, and each roll’s binding to its nonce, compared.
- Observed result
- All 55 bets received unique nonces with no server error; in a follow-up burst of ten, nonces ascended in arrival order and every roll matched its nonce.
- Impact
- None — no nonce was shared or skipped.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-RACE-002PassIdentical bets in parallel, at scale
- Control assessed
- Each accepted bet settles exactly once, and the balance equals the sum of all settled rounds.
- Testing approach
- Fifteen identical bets sent in parallel; round IDs and balance reconciled.
- Observed result
- All 15 settled once, as distinct rounds; the balance change matched their summed outcome exactly.
- Impact
- None — no lost or duplicated settlement.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-RACE-003PassHigh-stake parallel over-commit
- Control assessed
- Parallel high-stake bets cannot overdraw the balance, settle twice or leave a phantom round.
- Testing approach
- Parallel bets whose combined stake exceeds the balance, with a control bet; wallet and bet history reconciled.
- Observed result
- All 12 settled rounds, the control included, reconciled to the wallet and the bet history: no over-commit, no negative balance, no payout mismatch, no lost or phantom round.
- Impact
- None — settlement stayed consistent under parallel placement.
- Limitations
- None specific to this test; the general scope is in 5.5.
Input Validation5/5
LIQD-DICE-PARAM-001PassBand limits and strict input types
- Control assessed
- Win chance stays within the offered 0.01%–98% envelope; invalid bands and wrong-type values are refused.
- Testing approach
- Valid bands at the limits, out-of-range bands and wrong-type values, each with a control.
- Observed result
- Valid bands settled; all five out-of-range cases and all three wrong-type cases were rejected. Boundary checks confirmed the 0.01%–98% envelope.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-PARAM-002PassStake precision, zero and invalid types
- Control assessed
- Invalid stakes are refused, and zero is accepted only as the explicit free-play value.
- Testing approach
- Minimum-stake and free-play controls, then sub-precision, null, negative, oversized and wrong-type stakes.
- Observed result
- Both controls settled; every invalid stake was rejected, and no non-zero stake was silently turned into zero.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-PARAM-003PassExtreme win chance and band edges
- Control assessed
- Out-of-range bounds are refused, and every band is half-open: the lower bound wins, the upper bound loses, and inverted bands take the complement.
- Testing approach
- Bounds just inside and outside the range, and boundary checks of the win rule.
- Observed result
- The out-of-range bound was rejected and the in-range one accepted; the accepted round verified. Boundary checks confirmed the half-open rule: a 50.00 roll loses on 0–50 and wins on 50–100.
- Impact
- None — the control held.
- Limitations
- The upper-bound rule is confirmed by LIQD’s verifier (Section 4); no captured live roll landed exactly on an upper bound.
LIQD-DICE-PARAM-004PassContent-type variations around validation
- Control assessed
- Changing how the request body is encoded cannot let an invalid band settle.
- Testing approach
- Invalid bands sent under each alternative content type.
- Observed result
- Both invalid bands were rejected under every content type; no malformed request produced a settled round.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-TOKEN-001PassCurrency identifier manipulation
- Control assessed
- A client-supplied currency identifier cannot redirect or alter the stake.
- Testing approach
- Bets carrying six altered currency identifiers, with a control bet.
- Observed result
- All six were rejected without a server error; only the control settled, in the expected wallet.
- Impact
- None — the stake stayed in the account’s own currency.
- Limitations
- None specific to this test; the general scope is in 5.5.
Atomic Balance / Idempotency3/3
LIQD-DICE-IDEMP-001PassSame-key retry after a timeout
- Control assessed
- A retry with the same key returns the original round — no second debit or credit, and no change of parameters.
- Testing approach
- Bets sent with very short client timeouts, then retried with the same key; one retry also changed the bet’s parameters.
- Observed result
- All 20 retries returned their original round; all 11 timed-out bets had settled once, server-side, and the wallet reconciled. The retry with changed parameters still returned the original round, unchanged.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-IDEMP-002PassOrphaned-debit check at scale
- Control assessed
- Every debit belongs to a settled round.
- Testing approach
- Twenty consecutive bets; the balance reconciled against their outcomes.
- Observed result
- All 20 settled, and the balance change matched their summed outcome exactly.
- Impact
- None — no unexplained debit.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-IDEMP-003PassDisconnect while a bet is processed
- Control assessed
- A client disconnect mid-bet leaves no missing or duplicated debit.
- Testing approach
- Bets abandoned by the client at different moments; rounds and balance reconciled.
- Observed result
- The abandoned bet still settled once, server-side; all four rounds were accounted for and the balance matched their net outcome.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
Broken Authorization2/2
LIQD-DICE-AUTH-001PassForeign and malformed user identifiers
- Control assessed
- A user identifier supplied in the request is refused; bets stay bound to the caller’s own session.
- Testing approach
- Bets sent with foreign and malformed user identifiers in the request, alongside control bets; the caller’s balance reconciled.
- Observed result
- All 13 injected identifiers were rejected without a server error; the two control bets account for the caller’s entire balance change.
- Impact
- None — no injected identifier was accepted.
- Limitations
- Covers the caller’s balance, from one authenticated account.
LIQD-DICE-AUTH-003PassSession reuse after logout
- Control assessed
- A session ends at logout: its cookie cannot place another bet.
- Testing approach
- A control bet, a logout, then a bet attempted with the old session.
- Observed result
- The control bet settled and the logout completed; the reused session was rejected and no bet was placed.
- Impact
- None — the session was invalidated server-side.
- Limitations
- None specific to this test; the general scope is in 5.5.
Max-win Bypass4/4
LIQD-DICE-INJECT-001PassSingle outcome field injected
- Control assessed
- No client-supplied roll, payout, result or multiplier value can control settlement.
- Testing approach
- Bets each carrying one injected outcome field, with two control bets.
- Observed result
- All four injected fields were rejected; the two controls account for the entire nonce advance and balance change.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-INJECT-002PassWin-region and nested-parameter injection
- Control assessed
- No injected field changes the winning region or a bet’s effective parameters.
- Testing approach
- Variants carrying alias and nested parameter fields, with two control bets.
- Observed result
- All six altered variants were rejected; both controls settled against their declared bands, with matching recorded parameters.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-MAXWIN-001PassPotential-payout cap at bet time
- Control assessed
- A bet whose potential payout exceeds the 50,000 USDC cap is refused when it is placed.
- Testing approach
- Stakes just inside and just outside the cap at the top multiplier, plus invalid stake values.
- Observed result
- The stake inside the cap settled and the one just over it was refused; invalid stakes were rejected.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-MAXWIN-002PassSeveral outcome fields injected at once
- Control assessed
- Outcome fields supplied by the client cannot steer settlement or consume a nonce.
- Testing approach
- One bet carrying several injected outcome fields together, with a control bet.
- Observed result
- The combined injection was rejected at validation; only the control consumed a nonce and moved the balance.
- Impact
- None — settlement stayed server-computed.
- Limitations
- None specific to this test; the general scope is in 5.5.
Nonce Manipulation4/4
LIQD-DICE-NONCE-001PassReused nonce
- Control assessed
- The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
- Testing approach
- Bets carrying a reused nonce.
- Observed result
- Every injected nonce was rejected without a server error; only the two controls consumed nonces or changed the balance.
- Impact
- None — the server kept its own sequence.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-NONCE-002PassSkipped-ahead nonce
- Control assessed
- The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
- Testing approach
- Bets carrying a skipped-ahead nonce.
- Observed result
- Every injected nonce was rejected without a server error; only the two controls consumed nonces or changed the balance.
- Impact
- None — the server kept its own sequence.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-NONCE-003PassZero and negative nonces
- Control assessed
- The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
- Testing approach
- Bets carrying zero and negative nonces.
- Observed result
- Every injected nonce was rejected without a server error; only the two controls consumed nonces or changed the balance.
- Impact
- None — the server kept its own sequence.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-NONCE-004PassOverflow-sized nonces
- Control assessed
- The client cannot choose the nonce: an injected value is refused, and the server keeps its own sequence.
- Testing approach
- Bets carrying overflow-sized nonces.
- Observed result
- Every injected nonce was rejected without a server error; only the two controls consumed nonces or changed the balance.
- Impact
- None — the server kept its own sequence.
- Limitations
- None specific to this test; the general scope is in 5.5.
RNG / PF Verification4/4
LIQD-DICE-EXTRA-002PassIndependent roll and outcome check
- Control assessed
- Live results can be reproduced independently from the revealed inputs.
- Testing approach
- Five live rounds recomputed with our own implementation after the reveal.
- Observed result
- All five rounds matched: the same roll and the same band result. The same derivation reproduces every captured roll and win flag in Sections 1–4.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-FAIR-001PassRoll distribution and multiplier curve
- Control assessed
- Rolls are uniform with no band asymmetry, and the multiplier is 99 / win chance, for 99% RTP before settlement rounding.
- Testing approach
- A 2,000-roll live sample tested for uniformity and band symmetry; multipliers compared with the formula.
- Observed result
- The sample passed the uniformity test and showed no band asymmetry; both 50% bands paid 1.98×.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-PF-001PassProvably-fair data and commit / reveal
- Control assessed
- Each bet carries every provably-fair field a player needs, and a revealed seed matches its earlier commitment.
- Testing approach
- Bets checked field by field, then the seed rotated and revealed.
- Observed result
- All required fields were present, and the revealed seed matched its commitment.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-PF-002PassNonce sequence within a seed period
- Control assessed
- The nonce advances by exactly one per bet while the commitment stays unchanged.
- Testing approach
- Ten consecutive bets on one server seed, then a reveal.
- Observed result
- The nonce advanced from 0 to 10 with no gap while the commitment stayed unchanged.
- Impact
- None — the sequence was complete and the commitment fixed.
- Limitations
- None specific to this test; the general scope is in 5.5.
RNG / Information Leak1/1
LIQD-DICE-DISCLOSE-001PassActive server-seed disclosure
- Control assessed
- The active server seed stays secret until rotation; only then is it revealed.
- Testing approach
- The responses around a bet checked for the active seed before rotation; a rotation then served as the positive control.
- Observed result
- No response checked exposed the active seed; after rotation, the revealed seed matched its commitment.
- Impact
- None — the seed stayed secret until it was due.
- Limitations
- Covers the seed period tested.
Entropy / PF Verification1/1
LIQD-DICE-ENTROPY-004PassNonce reset after seed rotation
- Control assessed
- Rotating the seed changes the commitment and restarts the nonce at 0.
- Testing approach
- Bets, a rotation, then further bets.
- Observed result
- The nonce reset from 2 to 0, the commitment changed, and both follow-up bets settled normally.
- Impact
- None — rotation behaved as specified.
- Limitations
- None specific to this test; the general scope is in 5.5.
Precision / Payout1/1
LIQD-DICE-PREC-001PassPayout consistency at scale
- Control assessed
- Every payout follows the game’s settlement rule, and the wallet reconciles over the run.
- Testing approach
- Two hundred consecutive rounds; each payout and the aggregate balance change checked.
- Observed result
- All 200 rounds settled with no payout mismatch; the balance change matched the summed outcome exactly.
- Impact
- None — settlement arithmetic held.
- Limitations
- Rounding versus flooring is settled by the captured credits checked in Sections 3–4, which match the rounding rule.
Event Replay1/1
LIQD-DICE-REPLAY-001PassDuplicate identical bets sent together
- Control assessed
- Two identical requests sent at the same moment settle as two distinct rounds — none lost, none settled twice.
- Testing approach
- A pair of identical bet requests sent in parallel; rounds and balance reconciled.
- Observed result
- Both requests settled as distinct rounds, and the balance moved by exactly their net outcome.
- Impact
- None — each request settled exactly once.
- Limitations
- None specific to this test; the general scope is in 5.5.
Adversarial Seeds3/3
LIQD-DICE-SEED-001PassClient-seed length and character sets
- Control assessed
- Client seeds have a consistent length limit, and no accepted seed disrupts the game.
- Testing approach
- Client seeds of increasing length and unusual character sets, each followed by a bet.
- Observed result
- Seeds up to 255 characters were accepted and every longer seed rejected; betting worked after every accepted change.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-SEED-002PassUnicode forms of one seed
- Control assessed
- Composed and decomposed Unicode forms of the same seed are both usable and handled consistently.
- Testing approach
- The same visible seed submitted in both forms, each followed by a bet.
- Observed result
- Both forms were accepted, each kept as entered, and the following bets settled.
- Impact
- None — the control held.
- Limitations
- None specific to this test; the general scope is in 5.5.
LIQD-DICE-SEED-003PassRapid seed rotation
- Control assessed
- Each bet uses the seed set by the rotation immediately before it.
- Testing approach
- Back-to-back rotations, each followed by a bet.
- Observed result
- Every bet used the seed set just before it; no stale or mixed-up seed appeared.
- Impact
- None — each bet bound to the current seed.
- Limitations
- None specific to this test; the general scope is in 5.5.
Technical Evidence & Verification4 sections
5.3Coverage Summary12 categories
Coverage by category — 34 tests: 33 pass, 1 remediated.
| Category | Tests | Result |
|---|---|---|
| Race Conditions | 5 | 4 pass · 1 remediated (RACE-004) |
| Input Validation | 5 | All pass |
| Atomic Balance / Idempotency | 3 | All pass |
| Broken Authorization | 2 | All pass |
| Max-win Bypass | 4 | All pass |
| Nonce Manipulation | 4 | All pass |
| RNG / PF Verification | 4 | All pass |
| RNG / Information Leak | 1 | All pass |
| Entropy / PF Verification | 1 | All pass |
| Precision / Payout | 1 | All pass |
| Event Replay | 1 | All pass |
| Adversarial Seeds | 3 | All pass |
5.4Findings & Remediation1 record
1 finding from 1 test, remediated and re-verified. Each test’s card in the matrix above gives its control, approach, result, impact and limitations.
| Tests | Finding | Status |
|---|---|---|
| RACE-004 | Extreme bursts drew some generic server errors instead of structured overload responses. Fixed by LIQD and re-verified. | Remediated · Low |
5.5Scope & LimitationsScope note
5.6Evidence & Disclosure
Live-surface record: Section 5 tests LIQD’s running service, so unlike Sections 1–4 it does not reproduce from the audit repository. The published record is the test cards above.
Retained under the engagement record: Per-test procedures, request and response captures, re-test captures, run timestamps and build identifiers — available to LIQD and to regulators on request.
Integrity tests ....................... 34Categories ............................ 12Pass .................................. 33Remediated ............................ 1Warning (open) ........................ 0Findings .............................. 1 · 1 remediated, 0 openProcedures and payloads ............... withheld (5.5)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 the revealed seed matches the hash you saw before betting and your computed roll matches the game result, the bet was provably fair. This section walks you through the process — with LIQD’s own panel, with the independent ProvablyFair.org verifier, or with a few lines of code you run yourself.
Key principles
- Every Dice outcome can be independently reproduced
- No hidden variables — no private backend data
- If the revealed seed matches the hash you saw before betting and your computed roll matches the game, the bet was provably fair
- Most players can verify directly through LIQD's Provably Fair panel
What you need
- Server Seed — revealed after seed rotation (casino entropy)
- Client Seed — your player-controlled seed
- Nonce — the bet number in sequence (ensures uniqueness)
- Band — the mode and limits you played (from / to, under · over · inside · outside)
- Every Dice outcome can be independently reproduced
- No hidden variables — no private backend data
- If the revealed seed matches the hash you saw before betting and your computed roll matches the game, the bet was provably fair
- Most players can verify directly through LIQD's Provably Fair panel
- Server Seed — revealed after seed rotation (casino entropy)
- Client Seed — your player-controlled seed
- Nonce — the bet number in sequence (ensures uniqueness)
- Band — the mode and limits you played (from / to, under · over · inside · outside)
Player can access all required inputsPass
Client seed origin transparentPass
Verification process documentedPass
MANIFEST.md, Verify a bet)Casino UI exposes verification panelPass
Independent verifier availablePass
Manual recomputation possiblePass
No hidden or proprietary stepsPass
Public algorithm documentationInfo
docs/ALGORITHM.md), and publishing LIQD’s own — including the half-open band rule — is our first recommendation to the operatorOnly disclosed inputs are used. Identical inputs always produce identical output.
- 1
Place a Dice Bet
Pick a mode and set your band on the slider, then roll a number from 0.00 to 99.99. The number is computed from the seeds before your band is applied — the mode, the limits and the stake decide the odds and the price, never what is rolled.
- 2
Open the Provably Fair Panel and Rotate the Seed
Open the Provably Fair panel on the game page. The Seeds tab shows the active client seed, the hashed server seed (the commitment) and your bet count under the pair. Rotating ends the epoch and reveals the outgoing server seed in plaintext. First check the commitment: the SHA-256 of the revealed seed’s hex text must equal the hash you saw before betting — across this audit’s 134 epochs it always did.
- 3
Enter the Seeds in the Verify Tab
The Verify tab recomputes any roll from a server seed, client seed, nonce and band. Enter the revealed server seed, your client seed, the bet’s nonce and the band you played — the mode, From and To.
- 4
Recompute & Confirm
Press Verify — or enter the same inputs in the ProvablyFair.org verifier. If the commitment check in step 2 passed, the recomputed roll matches your bet and the half-open band gives the same win or loss, the bet was provably fair. The ProvablyFair.org verifier checks the commitment, roll, win and multiplier; it takes no stake, so check the credit with the scripts under Manual Verification.
Verify a real bet, step by stepVerify a real bet · Swipe for all 4 steps →
Verify with the audit code
Independent verification using the implementation behind this report.
Open independent verifierBuilt-in verification
The operator’s own verification page. This tool is controlled by LIQD.com.
Open LIQD verifierVisual Walkthroughstep-by-step
Pick a mode with the game-mode switch, set your band on the slider and roll. The platform computes the roll from (serverSeed, clientSeed, nonce) before the band is applied — the bet below rolls under 49.50, a 49.50% win chance at 2.00×.

LIQD Dice on liqd.com — a $1.00 roll under 49.50 (2.00×, 49.50% win chance) that came up 13.17, a win paying $2.00; the 58.61 before it was the pair’s first roll (nonce 0).
The panel has two tabs, Seeds and Verify. The Seeds tab holds your active pair and its commitment, with the next server-seed hash already published underneath it. Rotation ends the epoch: the outgoing server seed is revealed for verification, and the pre-committed next pair is promoted to active. That pre-commitment is the chain the audit’s Step 2 checks across all 133 links, plus the pre-capture link into epoch 0.
Active Server Seed (Hashed) — the commitment for the current epoch; the plaintext is revealed when you rotate and must hash to this value.
Active Client Seed — your player-controlled entropy input, editable via the Next Client Seed field before rotation.
Total bets made with pair — the bet counter under the active pair, which locates each round's nonce within the epoch.

The Seeds tab before rotating — Active Server Seed (Hashed) bf7c353e…14d717 after 2 bets, and the next server seed’s hash already published. The seed revealed by the rotation, c18398a8…8ec7ab, hashes to bf7c353e…14d717.
The Verify tab takes the four inputs — client seed, server seed, nonce, and the band (mode plus from / to) — and recomputes a roll on demand. Enter the server seed revealed in step 2, your client seed, the bet’s nonce and the band you played. Everything this audit checks cryptographically, the panel lets you check by hand.

The Verify tab filled in for that roll — client seed, the server seed revealed in step 2, nonce 1 and the Under band from 0.00 to 49.50.
Press Verify. The tab recomputes the roll from the three seed inputs and applies the half-open rule to your band — so you can check both the number rolled and the win or loss it produced. A match shows the roll is the one the committed seed produces. The scripts in the Manual Verification section below do the same with nothing but Node.js or Python, pre-loaded with the featured bet from this report — and they also check the credit.

The result: “Rolled 13.17: win at 2.00x”. Our scripts in 6.8–6.9 reproduce it exactly: roll 13.17, a win under 49.50, $2.00 credited on the $1.00 stake.
Manual Verification (Advanced)6 sections
6.5Why Manual Verification Matters
Hosted verifiers — including ours — are conveniences. The guarantee only becomes unconditional when you can reproduce a round with tools nobody else controls. The algorithm below is small enough to audit by eye and run anywhere: if the revealed seed matches the hash you saw before betting and your recomputation matches the round you played, no one in the chain — casino, auditor, or tool host — changed it after you bet.
6.6How the Algorithm Works (Plain English)
Four moving parts, in the order they act:
- Commit — before you play, LIQD publishes
SHA-256of the server seed (hashed as the 32-character hex string). The seed itself stays secret until rotation. - Draw — one call to
HMAC-SHA256(key = decoded seed, message = clientSeed:nonce:0); the first 4-byte chunk below 4,294,960,000 is taken modulo 10,000 and divided by 100 — that is the roll, 0.00 to 99.99. - Judge — the roll wins if it lies in
[lower, upper)— the lower bound counts, the upper does not — or, foroutside, if it does not. - Pay — a win credits
stake × 9900 / bwherebis the winning width in basis points, rounded in two stages at the 8th decimal (multiplier first, then the product half-even).
Note the two hashing domains: the commitment hashes the hex string as UTF-8 text, while the HMAC key is the 16 decoded bytes. Mixing them up is the most common reason a hand-rolled verifier disagrees with the game — both scripts below get it right, and both print the featured bet’s expected output in their comments so you can self-check the tool before trusting it. The other classic slip is the boundary: use < on the upper bound, not <=, or a roll exactly on it will be judged a win the game never pays.
6.7Casino Verifier vs ProvablyFair.org Verifier4 records
Both should agree — the point of having an independent one is what happens if they ever don't:
| LIQD Built-in (in-game panel) | ProvablyFair.org | |
|---|---|---|
| Who controls the code | The casino | ProvablyFair.org — open source |
| What it proves alone | Internal consistency of LIQD’s own math | Independent recomputation from raw inputs |
| Checked against this capture | Indirectly — its verify endpoint agrees with our implementation on eight recorded vectors and the E13 boundary probes | Yes — its dice module, run from source, reproduces all 6,700 captured draws (HMAC-SHA256 draw, half-open band, 9900 / b) |
| If they disagree | Suspect the casino’s tool or a tampered round | The repo + scripts are the tiebreaker anyone can run |
6.8JavaScript Verification Script
Complete, dependency-free, and pre-loaded with the featured bet as its default inputs — run it as-is to see a full match, then swap in your own round:
// verify-dice.js — standalone LIQD Dice verifier (Node.js, no dependencies)// Usage: node verify-dice.js <serverSeed> <clientSeed> <nonce> <lower> <upper> <inverted> [stake]const { createHash, createHmac } = require('node:crypto'); const argv = process.argv.slice(2);const [serverSeed, clientSeed, nonceArg, lowerArg, upperArg, invArg, stakeArg] = argv.length ? argv : ['015307d5a3f62db476210c3797bac8b7', 'auditf2456fc140ac', '0', '0', '92.37', 'false', '0.10'];const nonce = Number(nonceArg), lower = Number(lowerArg), upper = Number(upperArg);const inverted = invArg === 'true', stake = Number(stakeArg ?? '0.10'); // 1) Commitment: SHA-256 of the hex STRING (UTF-8) — what the panel showed before you playedconsole.log('commitment =', createHash('sha256').update(serverSeed, 'utf8').digest('hex')); // 2) One bias-free draw — HMAC keyed on the DECODED bytes, message clientSeed:nonce:0function draw() { const digest = createHmac('sha256', Buffer.from(serverSeed, 'hex')) .update(`${clientSeed}:${nonce}:0`).digest(); const maxFair = Math.floor(0x1_0000_0000 / 10000) * 10000; // 4,294,960,000 for (let o = 0; o + 4 <= digest.length; o += 4) { const chunk = digest.readUInt32BE(o); if (chunk < maxFair) return chunk % 10000; } throw new Error('all eight chunks rejected (not specified; about 7e-47 per bet)');}const roll = draw() / 100;console.log('roll =', roll.toFixed(2)); // 3) Half-open win rule: the lower bound is in, the upper bound is out; outside = exact complementconst inBand = roll >= lower && roll < upper;const win = inverted ? !inBand : inBand;console.log('win =', win); // 4) Quotient multiplier 9900 / b (b = winning width in basis points), then the two-stage 8-dp settlementconst width = inverted ? 100 - (upper - lower) : upper - lower;const b = Math.round(width * 100);const multiplier = 9900 / b;const multUnits = BigInt(Math.round(multiplier * 1e8)); // stage 1: round8(multiplier)const product = BigInt(Math.round(stake * 1e8)) * multUnits; // 1e-16 unitslet credit = product / 100000000n; const r = product % 100000000n;if (r > 50000000n || (r === 50000000n && credit % 2n === 1n)) credit += 1n; // stage 2: half-evenconsole.log('multiplier =', multiplier, '| credit =', win ? (Number(credit) / 1e8).toFixed(8) : '0.00000000');// Expected for the defaults (bet kzLjSr11zq5GvEOHlj9n6, epoch 0 nonce 0, under 92.37 at $0.10):// commitment = 918c2a302c7219c91cf31ce3619acb0ac820b9b7760e8ea3889787bfcb5d14d8// roll = 24.01// win = true// multiplier = 1.071776550828191 | credit = 0.107177666.9Python Verification Script
The same verifier in Python 3, standard library only — the settlement is done in exact rational arithmetic, so it doubles as an independent check on the JavaScript version:
# verify-dice.py — standalone LIQD Dice verifier (Python 3, stdlib only)import hashlib, hmac, struct, sysfrom fractions import Fraction args = sys.argv[1:] or ['015307d5a3f62db476210c3797bac8b7', 'auditf2456fc140ac', '0', '0', '92.37', 'false', '0.10']server_seed, client_seed, nonce = args[0], args[1], int(args[2])lower, upper, inverted = Fraction(args[3]), Fraction(args[4]), args[5] == 'true'stake = Fraction(args[6] if len(args) > 6 else '0.10') # 1) Commitment — SHA-256 of the hex string as UTF-8 textprint('commitment =', hashlib.sha256(server_seed.encode('utf-8')).hexdigest()) # 2) One bias-free draw — HMAC-SHA256 keyed on the 16 decoded bytes, message clientSeed:nonce:0def draw(): digest = hmac.new(bytes.fromhex(server_seed), f'{client_seed}:{nonce}:0'.encode(), hashlib.sha256).digest() for chunk in struct.unpack('>8I', digest): if chunk < 4294960000: # maxFair = floor(2**32 / 10000) * 10000 return chunk % 10000 raise ValueError('all eight chunks rejected (not specified; about 7e-47 per bet)') roll = Fraction(draw(), 100)print('roll = %.2f' % roll) # 3) Half-open win rule — lower bound in, upper bound out; outside = exact complementin_band = lower <= roll < upperwin = (not in_band) if inverted else in_bandprint('win =', win) # 4) Quotient multiplier 9900 / b, then the two-stage 8-dp settlement in exact rational arithmeticwidth = 100 - (upper - lower) if inverted else upper - lowerb = int(width * 100) # basis points (exact for 2-dp bounds)multiplier = Fraction(9900, b)mult_units = (2 * multiplier * 10**8 + 1) // 2 # stage 1: round half-up to 8 dpproduct = stake * mult_units # in 1e-8 units of creditq, r = divmod(product.numerator, product.denominator)if 2 * r > product.denominator or (2 * r == product.denominator and q % 2 == 1): q += 1 # stage 2: round half-evencredit = Fraction(q, 10**8) if win else Fraction(0)print('multiplier = %r | credit = %.8f' % (float(multiplier), float(credit)))# Expected for the defaults (bet kzLjSr11zq5GvEOHlj9n6, epoch 0 nonce 0, under 92.37 at $0.10):# commitment = 918c2a302c7219c91cf31ce3619acb0ac820b9b7760e8ea3889787bfcb5d14d8# roll = 24.01# win = True# multiplier = 1.071776550828191 | credit = 0.107177666.10Evidence Screenshots & Records6 records
The walkthrough’s screenshots are page captures from liqd.com, outside the package’s evidence set.
| File | Shows |
|---|---|
| E10-dice-game-ui.png | The Dice interface — 0–100 slider, game-mode switches, multiplier and win-chance fields, bet controls |
| E11-provably-fair-seeds-panel.png | The Seeds tab — active client seed, hashed server seed, bet counter, and the pre-committed next pair |
| E12-provably-fair-verify-tab.png | The Verify tab — client seed / server seed / nonce / band inputs and the VERIFY action (default form values, not a captured bet) |
| E13-verify-endpoint-boundary-probes.json | Seven recorded probes of LIQD’s verifier endpoint — five with the roll exactly on a band bound, two just past a bound as controls — plus a settled-bet cross-check — the evidence for the half-open rule (S4.7) |
| E14-maxodds-live-record.json | The settings pull, UI clamp observation and one losing 9900× bet at the odds ceiling (S4.7) — a session record |
| rtp-page-labels-2026-10-01.json | The RTP and house-edge figures on LIQD’s Dice game page, read logged out on 1 October 2026 — the source of the RTP claim (S4.2) |
Verify a real bet, step by stepVerify a real bet · Swipe for all 4 steps →
How it works
- 1
Place a Dice Bet
Pick a mode and set your band on the slider, then roll a number from 0.00 to 99.99. The number is computed from the seeds before your band is applied — the mode, the limits and the stake decide the odds and the price, never what is rolled.
- 2
Open the Provably Fair Panel and Rotate the Seed
Open the Provably Fair panel on the game page. The Seeds tab shows the active client seed, the hashed server seed (the commitment) and your bet count under the pair. Rotating ends the epoch and reveals the outgoing server seed in plaintext. First check the commitment: the SHA-256 of the revealed seed’s hex text must equal the hash you saw before betting — across this audit’s 134 epochs it always did.
- 3
Enter the Seeds in the Verify Tab
The Verify tab recomputes any roll from a server seed, client seed, nonce and band. Enter the revealed server seed, your client seed, the bet’s nonce and the band you played — the mode, From and To.
- 4
Recompute & Confirm
Press Verify — or enter the same inputs in the ProvablyFair.org verifier. If the commitment check in step 2 passed, the recomputed roll matches your bet and the half-open band gives the same win or loss, the bet was provably fair. The ProvablyFair.org verifier checks the commitment, roll, win and multiplier; it takes no stake, so check the credit with the scripts under Manual Verification.
Visual Walkthroughstep-by-step
Pick a mode with the game-mode switch, set your band on the slider and roll. The platform computes the roll from (serverSeed, clientSeed, nonce) before the band is applied — the bet below rolls under 49.50, a 49.50% win chance at 2.00×.

LIQD Dice on liqd.com — a $1.00 roll under 49.50 (2.00×, 49.50% win chance) that came up 13.17, a win paying $2.00; the 58.61 before it was the pair’s first roll (nonce 0).
The panel has two tabs, Seeds and Verify. The Seeds tab holds your active pair and its commitment, with the next server-seed hash already published underneath it. Rotation ends the epoch: the outgoing server seed is revealed for verification, and the pre-committed next pair is promoted to active. That pre-commitment is the chain the audit’s Step 2 checks across all 133 links, plus the pre-capture link into epoch 0.
Active Server Seed (Hashed) — the commitment for the current epoch; the plaintext is revealed when you rotate and must hash to this value.
Active Client Seed — your player-controlled entropy input, editable via the Next Client Seed field before rotation.
Total bets made with pair — the bet counter under the active pair, which locates each round's nonce within the epoch.

The Seeds tab before rotating — Active Server Seed (Hashed) bf7c353e…14d717 after 2 bets, and the next server seed’s hash already published. The seed revealed by the rotation, c18398a8…8ec7ab, hashes to bf7c353e…14d717.
The Verify tab takes the four inputs — client seed, server seed, nonce, and the band (mode plus from / to) — and recomputes a roll on demand. Enter the server seed revealed in step 2, your client seed, the bet’s nonce and the band you played. Everything this audit checks cryptographically, the panel lets you check by hand.

The Verify tab filled in for that roll — client seed, the server seed revealed in step 2, nonce 1 and the Under band from 0.00 to 49.50.
Press Verify. The tab recomputes the roll from the three seed inputs and applies the half-open rule to your band — so you can check both the number rolled and the win or loss it produced. A match shows the roll is the one the committed seed produces. The scripts in the Manual Verification section below do the same with nothing but Node.js or Python, pre-loaded with the featured bet from this report — and they also check the credit.

The result: “Rolled 13.17: win at 2.00x”. Our scripts in 6.8–6.9 reproduce it exactly: roll 13.17, a win under 49.50, $2.00 credited on the $1.00 stake.
Technical evidence
6.5Why Manual Verification Matters
Hosted verifiers — including ours — are conveniences. The guarantee only becomes unconditional when you can reproduce a round with tools nobody else controls. The algorithm below is small enough to audit by eye and run anywhere: if the revealed seed matches the hash you saw before betting and your recomputation matches the round you played, no one in the chain — casino, auditor, or tool host — changed it after you bet.
6.6How the Algorithm Works (Plain English)
Four moving parts, in the order they act:
- Commit — before you play, LIQD publishes
SHA-256of the server seed (hashed as the 32-character hex string). The seed itself stays secret until rotation. - Draw — one call to
HMAC-SHA256(key = decoded seed, message = clientSeed:nonce:0); the first 4-byte chunk below 4,294,960,000 is taken modulo 10,000 and divided by 100 — that is the roll, 0.00 to 99.99. - Judge — the roll wins if it lies in
[lower, upper)— the lower bound counts, the upper does not — or, foroutside, if it does not. - Pay — a win credits
stake × 9900 / bwherebis the winning width in basis points, rounded in two stages at the 8th decimal (multiplier first, then the product half-even).
Note the two hashing domains: the commitment hashes the hex string as UTF-8 text, while the HMAC key is the 16 decoded bytes. Mixing them up is the most common reason a hand-rolled verifier disagrees with the game — both scripts below get it right, and both print the featured bet’s expected output in their comments so you can self-check the tool before trusting it. The other classic slip is the boundary: use < on the upper bound, not <=, or a roll exactly on it will be judged a win the game never pays.
6.7Casino Verifier vs ProvablyFair.org Verifier4 records
Both should agree — the point of having an independent one is what happens if they ever don't:
| LIQD Built-in (in-game panel) | ProvablyFair.org | |
|---|---|---|
| Who controls the code | The casino | ProvablyFair.org — open source |
| What it proves alone | Internal consistency of LIQD’s own math | Independent recomputation from raw inputs |
| Checked against this capture | Indirectly — its verify endpoint agrees with our implementation on eight recorded vectors and the E13 boundary probes | Yes — its dice module, run from source, reproduces all 6,700 captured draws (HMAC-SHA256 draw, half-open band, 9900 / b) |
| If they disagree | Suspect the casino’s tool or a tampered round | The repo + scripts are the tiebreaker anyone can run |
6.8JavaScript Verification Script
Complete, dependency-free, and pre-loaded with the featured bet as its default inputs — run it as-is to see a full match, then swap in your own round:
// verify-dice.js — standalone LIQD Dice verifier (Node.js, no dependencies)// Usage: node verify-dice.js <serverSeed> <clientSeed> <nonce> <lower> <upper> <inverted> [stake]const { createHash, createHmac } = require('node:crypto'); const argv = process.argv.slice(2);const [serverSeed, clientSeed, nonceArg, lowerArg, upperArg, invArg, stakeArg] = argv.length ? argv : ['015307d5a3f62db476210c3797bac8b7', 'auditf2456fc140ac', '0', '0', '92.37', 'false', '0.10'];const nonce = Number(nonceArg), lower = Number(lowerArg), upper = Number(upperArg);const inverted = invArg === 'true', stake = Number(stakeArg ?? '0.10'); // 1) Commitment: SHA-256 of the hex STRING (UTF-8) — what the panel showed before you playedconsole.log('commitment =', createHash('sha256').update(serverSeed, 'utf8').digest('hex')); // 2) One bias-free draw — HMAC keyed on the DECODED bytes, message clientSeed:nonce:0function draw() { const digest = createHmac('sha256', Buffer.from(serverSeed, 'hex')) .update(`${clientSeed}:${nonce}:0`).digest(); const maxFair = Math.floor(0x1_0000_0000 / 10000) * 10000; // 4,294,960,000 for (let o = 0; o + 4 <= digest.length; o += 4) { const chunk = digest.readUInt32BE(o); if (chunk < maxFair) return chunk % 10000; } throw new Error('all eight chunks rejected (not specified; about 7e-47 per bet)');}const roll = draw() / 100;console.log('roll =', roll.toFixed(2)); // 3) Half-open win rule: the lower bound is in, the upper bound is out; outside = exact complementconst inBand = roll >= lower && roll < upper;const win = inverted ? !inBand : inBand;console.log('win =', win); // 4) Quotient multiplier 9900 / b (b = winning width in basis points), then the two-stage 8-dp settlementconst width = inverted ? 100 - (upper - lower) : upper - lower;const b = Math.round(width * 100);const multiplier = 9900 / b;const multUnits = BigInt(Math.round(multiplier * 1e8)); // stage 1: round8(multiplier)const product = BigInt(Math.round(stake * 1e8)) * multUnits; // 1e-16 unitslet credit = product / 100000000n; const r = product % 100000000n;if (r > 50000000n || (r === 50000000n && credit % 2n === 1n)) credit += 1n; // stage 2: half-evenconsole.log('multiplier =', multiplier, '| credit =', win ? (Number(credit) / 1e8).toFixed(8) : '0.00000000');// Expected for the defaults (bet kzLjSr11zq5GvEOHlj9n6, epoch 0 nonce 0, under 92.37 at $0.10):// commitment = 918c2a302c7219c91cf31ce3619acb0ac820b9b7760e8ea3889787bfcb5d14d8// roll = 24.01// win = true// multiplier = 1.071776550828191 | credit = 0.107177666.9Python Verification Script
The same verifier in Python 3, standard library only — the settlement is done in exact rational arithmetic, so it doubles as an independent check on the JavaScript version:
# verify-dice.py — standalone LIQD Dice verifier (Python 3, stdlib only)import hashlib, hmac, struct, sysfrom fractions import Fraction args = sys.argv[1:] or ['015307d5a3f62db476210c3797bac8b7', 'auditf2456fc140ac', '0', '0', '92.37', 'false', '0.10']server_seed, client_seed, nonce = args[0], args[1], int(args[2])lower, upper, inverted = Fraction(args[3]), Fraction(args[4]), args[5] == 'true'stake = Fraction(args[6] if len(args) > 6 else '0.10') # 1) Commitment — SHA-256 of the hex string as UTF-8 textprint('commitment =', hashlib.sha256(server_seed.encode('utf-8')).hexdigest()) # 2) One bias-free draw — HMAC-SHA256 keyed on the 16 decoded bytes, message clientSeed:nonce:0def draw(): digest = hmac.new(bytes.fromhex(server_seed), f'{client_seed}:{nonce}:0'.encode(), hashlib.sha256).digest() for chunk in struct.unpack('>8I', digest): if chunk < 4294960000: # maxFair = floor(2**32 / 10000) * 10000 return chunk % 10000 raise ValueError('all eight chunks rejected (not specified; about 7e-47 per bet)') roll = Fraction(draw(), 100)print('roll = %.2f' % roll) # 3) Half-open win rule — lower bound in, upper bound out; outside = exact complementin_band = lower <= roll < upperwin = (not in_band) if inverted else in_bandprint('win =', win) # 4) Quotient multiplier 9900 / b, then the two-stage 8-dp settlement in exact rational arithmeticwidth = 100 - (upper - lower) if inverted else upper - lowerb = int(width * 100) # basis points (exact for 2-dp bounds)multiplier = Fraction(9900, b)mult_units = (2 * multiplier * 10**8 + 1) // 2 # stage 1: round half-up to 8 dpproduct = stake * mult_units # in 1e-8 units of creditq, r = divmod(product.numerator, product.denominator)if 2 * r > product.denominator or (2 * r == product.denominator and q % 2 == 1): q += 1 # stage 2: round half-evencredit = Fraction(q, 10**8) if win else Fraction(0)print('multiplier = %r | credit = %.8f' % (float(multiplier), float(credit)))# Expected for the defaults (bet kzLjSr11zq5GvEOHlj9n6, epoch 0 nonce 0, under 92.37 at $0.10):# commitment = 918c2a302c7219c91cf31ce3619acb0ac820b9b7760e8ea3889787bfcb5d14d8# roll = 24.01# win = True# multiplier = 1.071776550828191 | credit = 0.107177666.10Evidence Screenshots & Records6 records
The walkthrough’s screenshots are page captures from liqd.com, outside the package’s evidence set.
| File | Shows |
|---|---|
| E10-dice-game-ui.png | The Dice interface — 0–100 slider, game-mode switches, multiplier and win-chance fields, bet controls |
| E11-provably-fair-seeds-panel.png | The Seeds tab — active client seed, hashed server seed, bet counter, and the pre-committed next pair |
| E12-provably-fair-verify-tab.png | The Verify tab — client seed / server seed / nonce / band inputs and the VERIFY action (default form values, not a captured bet) |
| E13-verify-endpoint-boundary-probes.json | Seven recorded probes of LIQD’s verifier endpoint — five with the roll exactly on a band bound, two just past a bound as controls — plus a settled-bet cross-check — the evidence for the half-open rule (S4.7) |
| E14-maxodds-live-record.json | The settings pull, UI clamp observation and one losing 9900× bet at the odds ceiling (S4.7) — a session record |
| rtp-page-labels-2026-10-01.json | The RTP and house-edge figures on LIQD’s Dice game page, read logged out on 1 October 2026 — the source of the RTP claim (S4.2) |
Only disclosed inputs are used. Identical inputs always produce identical output.
Reproducibility & Artifacts
Can anyone independently reproduce every scored check in this audit?
This section consolidates the open-source repository, datasets, output artifacts, and reproducibility posture of the audit. Every scored step, every statistic and every pass/fail result of the audit package 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/liqd-dice
- Release: v1.1.0 · Framework 1.1
- Game: Dice (liqd-dice)
- Public Verifier: provablyfair.org/verify-bets/liqd
Prerequisites
- Node.js 22.x (validated on 22.23.1; other majors are refused) — no dependency install for
node test.js && node verify.js - Git
- TypeScript toolchain via
npm ci(TypeScript 5.9.3 · ts-node 10.9.2 · Mocha 11.7.6) — fornpm testand the source commands - Python 3 — only for the optional standalone checker in S6
- GitHub: ProvablyFair-org/liqd-dice
- Release: v1.1.0 · Framework 1.1
- Game: Dice (liqd-dice)
- Public Verifier: provablyfair.org/verify-bets/liqd
- Node.js 22.x (validated on 22.23.1; other majors are refused) — no dependency install for
node test.js && node verify.js - Git
- TypeScript toolchain via
npm ci(TypeScript 5.9.3 · ts-node 10.9.2 · Mocha 11.7.6) — fornpm testand the source commands - Python 3 — only for the optional standalone checker in S6
All audit-package results reproduce from release v1.1.0, the pinned dataset and the commands above. The dataset hash is checked before any step runs, so you are provably testing the same 6,700 bets; the simulation, chart and RTP claim table are pinned by SHA-256, and the derived figures are recomputed and compared on every run. The simulation is deterministic — npm run test:full regenerates it from its pinned master seed byte for byte.
Commands to Reproduce
The 6,700-bet dataset and the committed results ship inside the repo — no separate download, and the dataset’s SHA-256 is checked before anything runs. npm test runs the 123 unit tests, the 22-step verification and the release-manifest check; node test.js && node verify.js runs the same unit suite and verification with nothing but Node.js 22. Node.js 22.x is required; other majors are refused before any step runs.
Every run writes only to outputs/run/ and compares its results field by field with the committed results (outputs/run/diff.json) — that comparison is the reproduction gate. Exit codes: 0 Full Pass · 2 Conditional Pass (an operator-origin flag) · 5 RTP FIGURE OVERSTATED (the RTP claim rule finds a published figure overstated) · 1 any hard fail, or a reproduction beyond tolerance. Output as run on Node 22.22.0; the informational context the suite prints before the summary is left out here.
Regenerates Pass 1 and Pass 2 from the pinned SIM_MASTER_SEED — 2,000,000 uniformity draws (serial screens on the first 200,000), a 20,000,000-bet run on under 50, nine effective-edge bands at 2,000,000 bets each, 98 over-mode targets at 1,000,000 bets each, and the served-band replay of the 134 dataset seeds — then verifies. The rewritten outputs/simulation-results.json and outputs/rtp-convergence.html are byte-identical to the committed files. About five minutes.
Audit Reproducibility Pinning
- Release Tag
- v1.1.0 — github.com/ProvablyFair-org/liqd-dice/tree/v1.1.0
- Framework
- ProvablyFair.org Audit Framework 1.1 · Dice profile 1.0.2 · RTP-CLAIM 1.2.0
- Release Signature
- Release commit and tag signed with the ProvablyFair.org release key (SSH,
SHA256:gEujiJm2pRyq7lQsfYQfQ1r97H/J7GdaKHbOW8GtikY), recorded inRELEASE-MANIFEST.json - Node Version
- 22.x required (validated on 22.23.1; other majors refused)
- Primary Dataset
- data/dice-master-6700bets.json (6,700 bets, 134 seed epochs)
- Primary Dataset Hash (SHA-256)
- ca1a181a9b4c89a1823e15fe71603b7a5e2267382c134873d80cba536d3aef34
- Simulation Artifact Hash (SHA-256)
- 55873640715d1f9e38d86e10227791337c5664dca094068b2d8d148fdf9ccd97
- Convergence Chart Hash (SHA-256)
- 6b7b6bb7c474a49b48effbd31dd69ca952e4af5ee451902dbaff4a745743e02c
- RTP Claim Table Hash (SHA-256)
- 075811c734cfbf4bdf1b374d8aac719f823caad23a86a3e30b73cb02b056ee06
- Derived Figures
- outputs/report-figures.json — checked against a fresh recomputation from the pinned dataset on every run (Step 19), not against a fixed hash
- Reproduction Gate
- Every run writes only to outputs/run/ and is compared field by field with the committed results (outputs/run/diff.json) — integers exactly, p-values within 1e-14 + 1e-12 relative; anything beyond tolerance exits 1
- Exit Codes
- 0 Full Pass · 2 Conditional Pass · 5 RTP FIGURE OVERSTATED · 1 hard fail or reproduction beyond tolerance
- Mutation Battery
- 59 entries — 49 mutations, 8 forgery scripts, 2 declared survivors: 56 killed, 1 informational signal, 2 survivors as declared, 0 not as declared
- Runtime
npm test≈ 11 s ·node verify.js≈ 5 s ·npm run test:full≈ 5 min ·npm run mutate≈ 8 min (Node 22.22.0)- Capture Environment
- Pre-launch host qa.liqd.com · one authenticated account
- Audit Date
- August–September 2026 (dataset captured 27 August 2026; verifier boundary probes 31 August and odds-acceptance probe 1 September 2026)
- Audit ID
- PF-2026-LQ04
Output Artifacts5 files committed
| File | Contents | Size |
|---|---|---|
| verification-results.json | Steps 1–22 results — per-step pass states with evidence detail strings, profile coverage (DICE-01 … DICE-30), dataset + simulation + chart hashes, info items, verdict | ~45 KB |
| report-figures.json | Derived figures — settled-RTP span, settlement residual counts, boundary hit counts, cherry-pick test figures, Phase D exposure, live aggregates — re-derived field-for-field by Step 19 | ~1.5 KB |
| simulation-results.json | Pass 1 summaries (uniformity χ², serial screens, nine effective-edge rows, the seven-point under-50 series, 98 target rows with their marks and the 98-target mean series) + Pass 2 (served-band return per epoch, exact-null rows for 134 epochs) | ~245 KB |
| rtp-claim-table.json | The RTP claim rule’s table — LIQD’s published 99% against all 9,800 settings, each with exact RTP, gap and outcome | ~1.3 MB |
| rtp-convergence.html | Convergence chart rendered from the saved series (self-contained SVG/HTML — the same data as the chart on this page) | ~2.3 KB |
Repository Structure
liqd-dice/ ├── src/ │ ├── rng.ts → commitHash · HMAC draw (generateProvablyFairNumber) · diceRoll — written from docs/ALGORITHM.md │ ├── bias.ts → the draw-bias bound, from the same maxFairFor the draw applies │ ├── config.ts → game model (range 10000 · half-open win rule · 9900/b quotient · two-stage settlement) + population constants + artifact pins │ ├── rtp-claim.ts · rtp-claim-switch.ts → the RTP claim rule (RTP-CLAIM 1.2.0) and its enforcement switch (on) │ ├── rtp-settings.ts → the 9,800-setting exact RTP table and LIQD’s published claim │ ├── profile-map.ts → which Dice profile check (DICE-01 … DICE-30) each step implements │ ├── loader.ts → dataset loader + SHA-256 guard (aborts on mismatch) │ ├── simulate.ts → Pass 1 (uniformity · serial · 9 edge bands · 20M under-50 run · 98 over-mode targets) + Pass 2, from the pinned master seed → outputs/ │ ├── chart.ts → renders outputs/rtp-convergence.html from the saved series │ ├── exact-chi2.ts → exact discrete null for the Pass 2 screen (181,274 partitions · 832 support values) │ ├── sim-checks.ts → integer win-count recovery, convergence validation, the seven scored screens and their nominal levels │ ├── stats.ts → chi-squared (exact incomplete gamma), lag-1, runs test, AS 241 quantile, exact binomial tail │ ├── report-figures.ts → outputs/report-figures.json — every quoted derived figure, reconciled by Step 19 │ ├── domains.ts → value domains for every numeric field of the committed artifacts (Step 19) │ ├── diff.ts → field-level diff of a run vs the committed artifacts (outputs/run/diff.json) │ ├── runtime.ts → Node 22.x guard (validated on 22.23.1) │ └── types.ts → dataset record types (liqd-dice-capture-v1) ├── docs/ │ ├── ALGORITHM.md → the specification the RNG is written from │ └── INTERFACE.md → the module interface the RNG implements ├── tests/ │ ├── verify.ts → 22-step verification runner (results under outputs/run/; PF_EMIT=1 to regenerate the committed record) │ ├── dice/ → 123 unit tests (rng 18 · rngSpec 1 [84 checks] · settlement 21 · simChecks 38 · stats 12 · exactNull 9 · clientSeed 8 · artifact 5 · runtime 4 · captureRetry 4 · commitment 3) │ ├── mutate.ts · mutations.json · structural-forgeries.json · mutation-survivors.json → 49 mutations + 8 forgeries, 2 declared survivors │ ├── forgeries/ → the eight structural forgeries as executable scripts │ └── steps/ │ ├── commitment.ts → Steps 1–4 (seed hashes, chain + pre-capture link, epoch consistency, nonce windows) │ ├── determinism.ts → Steps 5–6 (roll recompute + draw-bias bound, win-rule reproduction) │ ├── payouts.ts → Steps 7–10 (quotient multiplier, two-stage credit, losses, wallet fields + odds bounds) │ ├── dataset.ts → Steps 11–12 (dataset identity + population, phase / mode coverage) │ ├── anti-circularity.ts → Step 13 (negative control + 10,000-point enumeration anchor) │ ├── phase-d.ts → Step 14 (custom client seeds + sensitivity) │ ├── boundary.ts → Step 15 (roll ceiling, odds bounds, exact integer edge identity) │ ├── simulation.ts → Steps 16–17 (Pass 1 reconciliation, Pass 2 re-derivation from the dataset’s seeds) │ ├── standardization.ts → Steps 18–21 (bet size, artifact + value-domain integrity, settlement determinism, monotonicity) │ ├── live-fit.ts → Step 22 (live-sample serial independence, informational) │ ├── statistical.ts → informational items (live RTP, per-mode edge, roll range, win rate, odds settings) │ └── context.ts → shared context + step helper ├── scripts/ → release manifest + release bar, standalone-bundle builder and its byte-identity check ├── capture/ │ ├── capture-dice.reference.mjs → capture methodology record, authentication removed (documents, does not replay) │ ├── client-seed.reference.mjs → client-seed chooser (commitment first, then ≥128 bits of fresh randomness) │ └── bet-operation.reference.mjs · rng.reference.mjs → stable request identities for retries · the capture’s RNG module ├── data/ │ └── dice-master-6700bets.json → 6,700 bets · 134 epochs · SHA-256 pinned ├── outputs/ │ ├── verification-results.json → 22 steps + profile coverage + info items + dataset/simulation/chart hashes + verdict │ ├── report-figures.json → derived figures (settled RTP, residuals, boundary hits, cherry-pick figures, Phase D, live aggregates) │ ├── simulation-results.json → Pass 1 summaries + Pass 2 served-band and exact-null rows │ ├── rtp-claim-table.json → the RTP claim rule over all 9,800 settings │ └── rtp-convergence.html → self-contained convergence chart (same data as the chart on this page) ├── evidence/ → E10–E12 UI screenshots · E13 verifier boundary probes · E14 maxOdds live record · RTP page labels ├── .github/workflows/reproduce.yml → second-machine reproduction on a clean runner ├── test.js · verify.js → dependency-free bundles (Node 22 only) ├── README.md · MANIFEST.md → the report (findings, evidence register, worked examples, limitations L1–L19) ├── RELEASE-MANIFEST.json → release identity: tag, framework, artifact hashes, verdict, signing key ├── package.json → npm test = mocha && verify && release-manifest --check · test:standalone = node test.js && node verify.js └── package-lock.json · tsconfig.json · .mocharc.yml · .nvmrc · .gitattributes · .gitignore · LICENSE
Step-to-Section Cross-Reference22 verification steps mapped
| Step | Test Name | Report Section |
|---|---|---|
| 1 | Seed Hash Integrity | S1 |
| 2 | Next-Seed Pre-Commitment Chain | S1 |
| 3 | Hash Consistency Within Epoch | S1 |
| 4 | Nonce Audit | S1 |
| 5 | Roll Recomputation & Draw Bias Bound | S1, S2, S3 |
| 6 | Win-Rule Reproduction | S1, S3 |
| 7 | Multiplier Derivation | S3, S4 |
| 8 | Credit Arithmetic | S3, S4 |
| 9 | Loss Settlement | S3 |
| 10 | Wallet Credit & Odds Bounds | S3 |
| 11 | Dataset Integrity | S3 |
| 12 | Phase & Mode Coverage | S3 |
| 13 | Anti-Circularity (Negative Control + Enumeration Anchor) | S4 |
| 14 | Custom Client-Seed Control | S1, S2, S3 |
| 15 | Odds Boundary & Effective Edge | S4 (also scores the RTP claim rule) |
| 16 | Simulation — Pass 1 (RNG + Edge) | S2, S4 |
| 17 | Simulation — Pass 2 (Cherry-Pick) | S4 |
| 18 | Bet-Size Invariance | S3, S4 |
| 19 | Artifact Hash + Value-Domain Integrity | S7 |
| 20 | Settlement Determinism | S3 |
| 21 | Multiplier Monotonicity & Range | S3, S4 |
| 22 | Live-Sample Serial Independence (informational) | S2, S4 |
Game Profile CoverageDice profile 1.0.2 · 30 checks
Every check in the ProvablyFair.org Dice profile, version 1.0.2, is covered by a scored step, by a package check, or marked n/a with a reason — 22 by scored steps, 6 by package checks, 2 n/a. No check has partial coverage. The map ships in src/profile-map.ts and is emitted into outputs/verification-results.json on every run.
| Check | What it requires | Covered by | Coverage |
|---|---|---|---|
| DICE-01 | Every revealed seed hashes to its commitment | Step 1 | Full |
| DICE-02 | Next-seed pre-commitment chain, including the pre-capture link | Step 2 | Full |
| DICE-03 | One commitment per epoch | Step 3 | Full |
| DICE-04 | Nonce contiguity, no reuse, no withholding; capture-retry gaps flagged, operator gaps failed | Step 4 | Full |
| DICE-05 | Audited population bound to code constants, not the dataset header | Steps 11, 12 | Full |
| DICE-06 | Client-seed control and sensitivity | Step 14 | Full |
| DICE-07 | Every served roll recomputes byte-identically | Step 5 | Full |
| DICE-08 | Draw-bias bound computed from the draw’s own rejection window | Step 5 | Full |
| DICE-09 | Known-input unit vectors reproduce captured bets | Package check: tests/dice/rngTests.ts | Full |
| DICE-10 | Win flag under the half-open rule, roll ceiling 99.99 | Steps 6, 15 | Full |
| DICE-11 | Settlement arithmetic — winning credits, losses at 0, credit determined by (roll, band, stake) | Steps 8, 9, 20 | Full |
| DICE-12 | Bet-size invariance | Step 18 | Full |
| DICE-13 | Served multiplier equals 9900 / basisPoints | Step 7 | Full |
| DICE-14 | Odds bounds and strict multiplier monotonicity | Steps 10, 21 | Full |
| DICE-15 | All four modes, both stakes and the planned phases present | Step 12 | Full |
| DICE-16 | Wallet balance ledger reconciliation | — | N/A — the capture carries no running balance field, so there is no balance delta to reconcile; the settle-response wallet fields are reconciled in Step 10 |
| DICE-17 | Boundary rule at the band edges | Step 15 | Full |
| DICE-18 | Anti-circularity: negative control and literal grid tally | Step 13 | Full |
| DICE-19 | Effective edge against the claimed edge; the RTP claim rule | Step 15 | Full |
| DICE-20 | Simulation Pass 1 — uniformity, serial, edge bands, convergence; replayable | Step 16 | Full |
| DICE-21 | Cherry-pick test — served-band return | Step 17 | Full |
| DICE-22 | Live-sample uniformity and serial independence (informational) | Steps 17, 22 | Full |
| DICE-23 | Framework fairness-integrity rows S5-1 … S5-8, kept as a package checklist (Section 5 reports the live integrity tests) | Package check: MANIFEST.md S5 matrix | Full |
| DICE-24 | Operator verifier source hash-matched to the derivation | — | N/A — LIQD publishes no verifier source code to hash-match; its verify endpoint is used as a cross-check (E13) |
| DICE-25 | Player verification path, in LIQD’s field names | Package check: MANIFEST.md Verify a bet + E12 | Full |
| DICE-26 | Dataset and simulation artifacts pinned by SHA-256 | Steps 11, 19 | Full |
| DICE-27 | Artifacts of record: no run metadata, every numeric value inside a declared domain | Step 19 | Full |
| DICE-28 | Release gate: fresh clone, pinned runtime, audit ID | Package check: package.json, .nvmrc, src/config.ts | Full |
| DICE-29 | Mutation battery with declared survivors | Package check: tests/mutations.json and companions | Full |
| DICE-30 | Limitations register and numbered evidence | Package check: MANIFEST.md | Full |
reproduce-audit.sh
All audit-package results reproduce from release v1.1.0, the pinned dataset and the commands above. The dataset hash is checked before any step runs, so you are provably testing the same 6,700 bets; the simulation, chart and RTP claim table are pinned by SHA-256, and the derived figures are recomputed and compared on every run. The simulation is deterministic — npm run test:full regenerates it from its pinned master seed byte for byte.