v2.8 · RNG PRE-SUBMISSION PACKAGE v1.2
POWERED BY RAIN RNG

RAIN RNG — TWO-PARTY COMMIT-REVEAL RANDOMNESS

Provably fair by construction. Oracle-free since v2.6.0. Running live on Arbitrum One at maycasino.xyz and playmarkets.bet.
Measured latency (real network, Virginia → Oregon, 1,000 spins/run, 0 errors): one round trip on the critical path with pipelining off — HTTPS p95 101.8 ms, WS p95 100.2 ms; with pipelining on and a 60 ms animation gap → 49.0 / 39.7 ms; with a 1 s reel → 1.6 / 1.5 ms (the reveal is already there). With no animation at all, pipelining changes nothing — stated as the boundary, not hidden.
USE THE RNGPRE-SUBMISSION PACKAGE v1.2 →⬇ RNG WHITEPAPER v1.2 (PDF)

MAINNET · ARBITRUM ONEPLAY-MONEY DEPLOYMENTSPRE-AUDITMIT + ATTRIBUTION

Honest scope, first. Everything on this page is enforced by Sourcify-verified contracts on Arbitrum One and can be checked by anyone with a keccak256 tool. What it is not: externally audited (yet), or running with real money (both deployments settle in play-money tokens). The Outperform markets on playmarkets.bet do not use RAIN RNG for settlement — they use a house-signed price oracle, disclosed on the front page. Read the gaps before the claims.

1HOW IT WORKS — COMMIT → OPEN → REVEAL

Two parties, two secrets each round, one formula. No third party anywhere in the path.

①COMMIT (BEFORE PLAY)

The house generates houseSeed and sends houseSeedCommit = keccak256(abi.encode(houseSeed)) before it sees anything from the player. The player picks playerSeed and signs the EIP-712 OpenChannel struct that binds both playerSeed and houseSeedCommit. Each side also commits the root h₀ of a 4,096-deep hash chain (hᵢ = keccak256(hᵢ₊₁)), generated locally from a WebCrypto secret.

②OPEN (ONE TRANSACTION)

The ChannelManager verifies keccak256(abi.encode(houseSeed)) == houseSeedCommit, stores playerChainRoot / houseChainRoot, fixes
sessionSeed = keccak256(abi.encode(houseSeed, playerSeed, channelId))
and emits SessionSeedCommitReveal(channelId, houseSeed, playerSeed, sessionSeed). The channel is Active in the same block.

③REVEAL (EVERY ROUND k)

The player signs the state that commits the bet and attaches pRev_k (the next preimage of their chain). The house persists it, then answers hRev_k plus a countersignature of the settled state. Both clients check keccak256(rev_k) == rev_{k-1} against the committed chain — a reveal that doesn't hash to the anchor is rejected before any result is shown.

THE FORMULA

r        = keccak256(abi.encode(pRev_k, hRev_k, sessionSeed, channelId, k))     // uint256
roulette = r % 37            blackjack card = r % 52            dice = r % 100 + 1
slot (Engine V2): seed = keccak256(abi.encode(pRev, hRev, sessionSeed, channelId, k, keccak256(gameId)))
                  → KeccakRng(seed) drives the published engine build; keccak256(engine ‖ config) == engineHash on-chain

Properties that follow directly: (a) the outcome is fixed only when both reveals exist, and the bet is dual-signed before either does — decision before randomness; (b) neither party can choose its reveal (chains are committed in order); (c) if either party's chain is uniformly random, r is uniformly random — unbiased if either party is honest; (d) every input is either on-chain or dual-signed, so any round is recomputable by anyone, forever.

2WHY NOT A VRF OR AN ORACLE?

PROPERTYVRF / ORACLE (CHAINLINK VRF, PYTH ENTROPY, DRAND)RAIN RNG (TWO-PARTY COMMIT-REVEAL)
Trust assumptionThe provider's key(s) and liveness; a committee or a company.Either the player or the house is honest — the player can always be that party.
Latency / costOne on-chain request + callback per random value (seconds, gas, provider fee).One RTT over WebSocket per round (~100–480 ms measured), zero gas, zero fee.
ThroughputBounded by block time and callback gas.Bounded by the house node's disk fsync — thousands of rounds per session, 4,096 per chain.
CensorshipProvider can delay or refuse a callback.A stalling house is a liveness fault: dual-signed BET state → forceClose → stake voided + compensation from HouseBond.
VerifiabilityVerify the provider's proof against its public key.Verify with keccak256 alone — no keys, no provider, no client software.
Fallback—Deployment #1 historically ran a Pyth Entropy session seed (ChannelManager V3); v2.6.0 removed it. Outperform markets' price oracle is a separate, disclosed exception.

The research that led here — Stake, iGaming labs, Pyth Entropy, drand, FunFair-style channels — is on the Randomness Research page, sources labelled honestly.

3THREAT MODEL

ATTACKWHY IT FAILSENFORCED BY
House picks houseSeed after seeing playerSeedhouseSeedCommit is inside the player's signed OpenChannel; the contract checks the preimage at open.ChannelManager open (V4 / AA)
House sees the result, then rejects the betThe bet is dual-signed with grossAtRisk = stake before hRev exists. Refusing to reveal = liveness fault → forceClose, stake voided, max(2× gross, floor) compensation from HouseBond.forceClose / RevealDemanded / HouseBond
House reveals a chosen hRevMust satisfy keccak256(hRev_k) == hRev_{k-1}, chain fixed at open. One preimage per position; no choice.Client check + IRulesVerifier
House reports a wrong outcome for correct revealsClient recomputes r locally and refuses to countersign; if a bad state is ever signed, proveBadTransition slashes the signer.Engine mirror + proveBadTransition
Player grinds pRevPlayer's chain is also committed at open; the next preimage is fixed. Player stalling forfeits after the grace timer.Chain roots + house-node grace
Replay across channels / chainsEIP-712 domain binds chainId + manager; channelId and k are inside the hash.EIP-712 domain, fuzz-tested
Collusion of both partiesOut of scope by definition — there is no third party to defraud in a two-party game; the vault is the house's own capital.—

Residual, disclosed: the house node is foundation-run (no HA / watchtower in v1), ChannelManagerAA is owner-upgradable pre-audit (setVerifier), and there is no external audit yet. All listed in the accepted risks.

4VERIFY ANY ROUND — WALKTHROUGH WITH A LIVE EXAMPLE FROM ARBISCAN

A real session open on deployment #2, read from Arbitrum One on 2026-09-13. Recompute it yourself.
ContractChannelManagerAA 0x60743006c2a5Dd5b9907CA37e7381854ff860959
Transaction0x1c62012f3e449da8a6c4b2b6d9a367b343bea8e17c566cebe279b0b55930ba9e · block 504537592 · 2026-09-12 22:31:14 UTC
EventSessionSeedCommitReveal(bytes32 channelId, bytes32 houseSeed, bytes32 playerSeed, bytes32 sessionSeed) · topic0 0x2518e577bfda1c28f5ad7946566f7be396d227547c56a4a0d55b23092abdf611
channelId0xc6fcfe25563701b9e9ec1167f6ac54ad826e9c07aa0d461b39bb358be4854f0d
houseSeed0xa52aa98608487152568524baf8212b65a852159cc1de7ede77de43ac3b6daa13
playerSeed0x8c3d4d13e80e659be1e9617c93ec46d49b992540088c0d6f0da1b12288301c30
sessionSeed (emitted)0x0ae271400c5eb64c6e8226f6cddbb0736b483120e4e85db1c7fc5b10588bffbd

STEP 1 — RECOMPUTE THE SESSION SEED

import { keccak256, AbiCoder } from "ethers";
const abi = AbiCoder.defaultAbiCoder();
keccak256(abi.encode(["bytes32","bytes32","bytes32"],
  ["0xa52aa98608487152568524baf8212b65a852159cc1de7ede77de43ac3b6daa13",
   "0x8c3d4d13e80e659be1e9617c93ec46d49b992540088c0d6f0da1b12288301c30",
   "0xc6fcfe25563701b9e9ec1167f6ac54ad826e9c07aa0d461b39bb358be4854f0d"]))
// → 0x0ae271400c5eb64c6e8226f6cddbb0736b483120e4e85db1c7fc5b10588bffbd   ✅ equals the emitted sessionSeed
keccak256(abi.encode(["bytes32"], ["0xa52aa98608487152568524baf8212b65a852159cc1de7ede77de43ac3b6daa13"]))
// → 0x5e935d70097cca9bed5d4a890846a31b8fdd5697d249fbfb92db2084326d62de   = the houseSeedCommit the player signed at open

STEP 2 — RECOMPUTE A ROUND

Open the fairness panel of any settled round in the game (playmarkets.bet shows channelId, sessionSeed, k, pRev, hRev and the formula on every result; maycasino.xyz/fairness.html has a recompute widget). Then:

r = keccak256(abi.encode(["bytes32","bytes32","bytes32","bytes32","uint256"], [pRev, hRev, sessionSeed, channelId, k]))
// dice: BigInt(r) % 100n + 1n     roulette: % 37n     blackjack card: % 52n
// chain check: keccak256(abi.encode(["bytes32"],[hRev_k])) == hRev_{k-1}   (or houseChainRoot when k == 1)

If your number matches the panel, the house told the truth. If it doesn't, the client would already have refused to countersign — and you hold a dual-signed state you can take to proveBadTransition.

Engine V2 slots: run the published engine build with KeccakRng(seed, gameId) and compare winFp + result-tree hash; confirm keccak256(engine ‖ config) == engineHash registered in EngineV2RulesVerifier 0x5d6048AB261e6151AB44fE6053e7AEb35Cb5C1B1. The repo ships a checker (tools/engine-v2-audit.mjs).

5USE THE RNG — BADGE, LICENCE, CODE

▸“POWERED BY RAIN RNG” BADGE

POWERED BY RAIN RNG

Show it on any product whose outcomes are decided by RAIN RNG and verifiable by the formula above. The badge is a promise to your users that every round is recomputable from on-chain + dual-signed inputs, with no oracle in the path. Don't show it on outcomes that use a different randomness or price source (we don't on Outperform).

▸LICENCE — MIT + ATTRIBUTION

@rain/rng-core and @rain/rng-session are released under the MIT licence with one added condition: a visible “Powered by RAIN RNG” attribution (the badge or the text, linking to rainriskmarkets.com/docs/rng.html) wherever RAIN RNG decides an outcome shown to end users. Fork it, embed it, sell on top of it — just say where the randomness comes from.

FIVE LINES

import { outcome, verifyRound } from '@rain/rng-core';

const r   = outcome({ pRev, hRev, sessionSeed, channelId, k });   // uint256 — same keccak as the verifiers
const ok  = verifyRound({ pRev, hRev, sessionSeed, channelId, k, hAnchor, claimed: r }); // true ⇔ VERIFIED
console.log(r % 37n, ok);                                        // e.g. roulette number, and whether the house told the truth

Status: publishing to npm pending; source shared on request. The same primitives run today inside the two live house nodes and the browser clients. Full SDK layering →

6WHO INTEGRATES — THREE PERSONAS

01A CASINO THAT WANTS ONLY THE RNG

You keep your games, your wallet, your house. Take @rain/rng-core + @rain/rng-session: hash chains, commit-reveal, the formula, and a verifier your players can run. Optionally anchor sessionSeed on-chain with @rain/channels for a public fairness proof. Show the badge.

02AI-AGENT BUILDERS

Agents negotiating, betting or sampling against a counterparty need randomness neither side can steer. RAIN RNG gives two machines a fair coin over one WebSocket RTT with no oracle dependency and a dispute path on Arbitrum if one of them stalls. @rain/rng-session is plain TypeScript; no browser required.

03GAME STUDIOS

Write the game once as a deterministic engine seeded by r (Engine V2 pattern), register keccak256(engine ‖ config) as the on-chain engineHash, and ship it into any RAIN channel — 8 slot titles already run this way on playmarkets.bet. @rain/games has the IRulesVerifier mirror scaffolding.

Honest scope: third-party games and bonded external operators are the designed path, not yet a permissionless one — the mux and verifier set are wired by protocol governance pre-audit. Talk to us before building for production.