RAIN RNG — TWO-PARTY COMMIT-REVEAL RANDOMNESS
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
①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, fixessessionSeed = 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?
| PROPERTY | VRF / ORACLE (CHAINLINK VRF, PYTH ENTROPY, DRAND) | RAIN RNG (TWO-PARTY COMMIT-REVEAL) |
|---|---|---|
| Trust assumption | The 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 / cost | One 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. |
| Throughput | Bounded by block time and callback gas. | Bounded by the house node's disk fsync — thousands of rounds per session, 4,096 per chain. |
| Censorship | Provider can delay or refuse a callback. | A stalling house is a liveness fault: dual-signed BET state → forceClose → stake voided + compensation from HouseBond. |
| Verifiability | Verify 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
| ATTACK | WHY IT FAILS | ENFORCED BY |
|---|---|---|
House picks houseSeed after seeing playerSeed | houseSeedCommit 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 bet | The 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 hRev | Must 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 reveals | Client recomputes r locally and refuses to countersign; if a bad state is ever signed, proveBadTransition slashes the signer. | Engine mirror + proveBadTransition |
Player grinds pRev | Player'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 / chains | EIP-712 domain binds chainId + manager; channelId and k are inside the hash. | EIP-712 domain, fuzz-tested |
| Collusion of both parties | Out 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
| Contract | ChannelManagerAA 0x60743006c2a5Dd5b9907CA37e7381854ff860959 |
| Transaction | 0x1c62012f3e449da8a6c4b2b6d9a367b343bea8e17c566cebe279b0b55930ba9e · block 504537592 · 2026-09-12 22:31:14 UTC |
| Event | SessionSeedCommitReveal(bytes32 channelId, bytes32 houseSeed, bytes32 playerSeed, bytes32 sessionSeed) · topic0 0x2518e577bfda1c28f5ad7946566f7be396d227547c56a4a0d55b23092abdf611 |
channelId | 0xc6fcfe25563701b9e9ec1167f6ac54ad826e9c07aa0d461b39bb358be4854f0d |
houseSeed | 0xa52aa98608487152568524baf8212b65a852159cc1de7ede77de43ac3b6daa13 |
playerSeed | 0x8c3d4d13e80e659be1e9617c93ec46d49b992540088c0d6f0da1b12288301c30 |
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
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.