Skip to main content

How rng.dev Works

Overview

rng.dev generates verifiable random numbers by combining entropy from 8 independent blockchain sources. Every second, fresh data from fast-finality chains is mixed with stable base entropy from slower chains to produce cryptographically secure randomness.

The security guarantee: Compromising this beacon would require controlling multiple major blockchains simultaneously — an economic cost exceeding $80 billion.


The Generation Process

Every second, rng.dev:

  1. Snapshots blockchain data at the end of each round (T+1000ms)
  2. Combines block hashes and transaction IDs using SHA3-256 sequential mixing
  3. Derives a fair die value (1-6) using rejection sampling
  4. Publishes the result via API and WebSocket
┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│ Aptos │ │ Arbitrum │ │ Base │ │ Bitcoin │
│ (~900ms) │ │ (~250ms) │ │ (~2s) │ │ (6 conf) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │ │
└────────────────┴────────────────┴────────────────┘

┌──────────────────────────┼──────────────────────────┐
│ │ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐ ┌────────┴────────┐
│ Cardano │ │ Ethereum │ │ Solana │ │ Sui │
│ (finalized) │ │ (finalized) │ │ (~400ms) │ │ (~400ms) │
└──────┬──────┘ └──────┴──────┘ └──────┴──────┘ └────────┬────────┘
│ │ │ │
└────────────────┴────────────────┴───────────────────┘

┌─────────────────┐
│ SHA3-256 │
│ Sequential │
│ Mixing │
└────────┬────────┘

┌─────────────────┐
│ 256-bit Hash │
│ + Die Value │
└─────────────────┘

Blockchain Sources

We use 8 blockchains with different consensus mechanisms to ensure diversity:

ChainFinalityBlock TimeRole
Aptos~900ms~160msFresh each round
Arbitrum~250ms~250msFresh each round (L2)
Base~2s~2sFresh each round (L2)
Bitcoin~60 min (6 conf)~10 minStable base entropy
Cardano~20 min~20 secStable base entropy
Ethereum~15 min~12 secStable base entropy
Solana~400ms~400msFresh each round
Sui~400ms~380msFresh each round

Fast vs Slow Chains

  • Fast-finality chains (Aptos, Arbitrum, Base, Solana, Sui) provide fresh entropy every second
  • Slow-finality chains (Bitcoin, Cardano, Ethereum) provide stable base entropy that changes with each new block

This combination ensures:

  • Fresh randomness every second from fast chains
  • Deep economic security from proof-of-work (Bitcoin) and established proof-of-stake chains
  • L2 chains (Arbitrum, Base) add diversity with different sequencer trust models

Why Multiple Sources?

Using multiple independent blockchains provides:

  1. Resilience — If one chain is unavailable, others continue
  2. Security — Manipulating multiple chains simultaneously is economically impractical
  3. Diversity — Different consensus mechanisms reduce correlated failures

What We Combine

Each round combines block hashes and transaction IDs from all 8 chains:

output_hash = SHA3(
aptos_block:aptos_hash:aptos_tx |
arbitrum_block:arbitrum_hash:arbitrum_tx |
base_block:base_hash:base_tx |
bitcoin_height:bitcoin_hash:bitcoin_tx |
cardano_slot:cardano_hash:cardano_tx |
ethereum_number:ethereum_hash:ethereum_tx |
solana_slot:solana_hash:solana_tx |
sui_checkpoint:sui_digest:sui_tx
)

Why Transaction IDs?

Block hashes alone could theoretically be influenced by block producers. Transaction IDs add entropy from a different trust source:

PropertyBlock HashesTransaction IDs
OriginBlock producerExternal users
GrindingProducer can iterate nonceDetermined by TX content
PredictabilityProducer knows firstDepends on mempool state

Transaction IDs come from users submitting transactions, not from block producers. This provides entropy with a different trust assumption than block hashes alone.

Transaction Selection Rules

ChainSelectionRationale
Aptos1st transaction (index 0)Standard selection
Arbitrum1st transaction (index 0)Standard selection (L2)
Base1st transaction (index 0)Standard selection (L2)
Bitcoin2nd transaction (index 1)Skip coinbase (miner-controlled)
Cardano1st transaction (index 0)Standard selection
Ethereum1st transaction (index 0)No coinbase equivalent
Solana1st transaction (index 0)Standard selection
Sui1st transaction (index 0)Standard selection

Sequential Mixing Algorithm

We combine sources using SHA3-256 sequential mixing:

def combine_sources(sources: dict[str, str]) -> str:
"""
Combine multiple entropy sources into single hash.
Sources are processed in alphabetical order for determinism.
"""
state = ""

for name in sorted(sources.keys()): # Alphabetical order
if sources[name] is not None:
if state:
combined = f"{state}|{sources[name]}"
else:
combined = sources[name]
state = sha3_256(combined.encode()).hexdigest()

return state

Why Sequential Mixing?

  • Order-dependent: Each hash depends on all previous inputs
  • Deterministic: Same inputs always produce same output
  • Verifiable: Anyone can recompute with the same inputs

Input Canonicalization

Each blockchain input follows a strict format:

ChainFormatExample
Aptos{block_height}:{block_hash}:{txid}12345678:0xabc...:0xdef...
Arbitrum{block_number}:{block_hash}:{txid}442950038:0x9a8...:0xa4b...
Base{block_number}:{block_hash}:{txid}43507585:0x6f0...:0x63b...
Bitcoin{block_height}:{block_hash}:{txid}831245:00000...3f:abc...
Cardano{slot}:{block_hash}:{txid}123456789:abc...:def...
Ethereum{block_number}:{block_hash}:{txid}19234567:0x8a3f...:0x...
Solana{slot}:{blockhash}:{txid}245678901:5eykt...:3abc...
Sui{checkpoint}:{digest}:{txid}12345678:abc...:def...

Concatenation: Sources joined with pipe | separator in alphabetical order.


Die Value Derivation

The 256-bit hash is the primary output, but we also derive a die value (1-6) as a visual representation. The animated die on our homepage provides an intuitive display of ever-changing randomness.

We convert the hash to a fair die value using rejection sampling:

def derive_die_value(hash_hex: str) -> int:
"""
Convert hash to die value 1-6 using rejection sampling.
Avoids modulo bias by rejecting values >= 252.
"""
for i in range(0, len(hash_hex), 2):
byte_val = int(hash_hex[i:i+2], 16)
if byte_val < 252: # 252 = 6 * 42, evenly divisible
return (byte_val % 6) + 1

raise ValueError("Rejection sampling exhausted")

Why Rejection Sampling?

Simple modulo (hash % 6) creates bias because 256 is not evenly divisible by 6. Values 1-4 would appear slightly more often than 5-6.

Rejection sampling ensures perfect uniformity by only accepting values that map evenly to 1-6.


Partial Availability

If some sources are unavailable, we continue with available ones:

AvailableStatusBehavior
8/8completeNormal operation
6-7/8partialGenerate with warning
3-5/8degradedGenerate with prominent warning
0-2/8failedDo not generate

The API response always indicates which sources were included.


Verification

Any round can be independently verified:

  1. Fetch the round data including all source inputs
  2. Recompute the hash using the same algorithm
  3. Compare with the stored hash
# Example verification
import hashlib

def verify_round(inputs: dict, expected_hash: str) -> bool:
state = ""
for name in sorted(inputs.keys()):
if inputs[name]:
combined = f"{state}|{inputs[name]}" if state else inputs[name]
state = hashlib.sha3_256(combined.encode()).hexdigest()

return state == expected_hash

See the Verification Guide for complete examples.


Statistical Analysis

We continuously run statistical tests to verify randomness quality:

Die value tests (details):

  • Chi-squared test — Distribution uniformity
  • Runs test — Sequence randomness
  • Serial correlation — Independence between rounds

Beacon comparisons (details):

  • K-S test — Distribution comparison against drand and NIST
  • Chi-squared — Byte frequency comparison
  • Runs test — Sequential pattern comparison
  • Serial correlation — Dependency comparison

View live statistics on the homepage.


Next Steps