Quantum RNG — Consume the Beacon mixed with QRNG
Consume the chain randomness beacon (commit-reveal and VDF, optionally mixed with QRNG) from a contract safely.
5 min read 1,097 words
View docs/tutorials/QUANTUM_RNG.md on GitHub
Source: docs/tutorials/QUANTUM_RNG.md — this page mirrors the repository documentation.
Naming note. Older repository documents call the signature scheme “Dilithium3”; that is the lineage name for ML-DSA-65 (FIPS 204, scheme id
0x1003), the only signature scheme mainnet accepts for new transactions. Where SPHINCS+ is mentioned as a backup scheme, note that on mainnet it is legacy and consensus-stranded: it cannot sign transactions.
This tutorial shows how to consume the chain randomness beacon that’s constructed each round as:
commit–reveal → VDF → (optional) QRNG mix → BeaconOut
Contracts can safely derive deterministic randomness from this beacon and use it for lotteries, leader election, or fair sampling—without relying on any off-chain oracle. The optional QRNG component (if enabled by the network) is already mixed inside the beacon, so your contract just reads the final, canonical bytes.
Determinism rule: contracts never read external entropy directly. All mixing happens in the beacon finalize step (see
randomness/beacon/finalize.py).
0) Prereqs
- Devnet node + miner running (see Dev Quickstart).
- Randomness subsystem enabled (defaults are fine).
- Python SDK installed (
sdk/python). - VM CLI available (
vm_py/cli).
1) Contract: quantum_rng_consumer.py
This small contract:
- Reads the current beacon output.
- Exposes a helper to draw N random bytes derived from the beacon with domain separation (context bytes).
- Stores the last draw so you can inspect it later.
The VM’s syscall surface provides:
syscalls.random(n: int) -> bytes: deterministically derived bytes that include the beacon (when available) and a per-call PRF (seecapabilities/host/random.pyandrandomness/adapters/execution.py).syscalls.rand_beacon() -> bytes(optional/feature-gated): returns the raw current beacon output (or empty if not yet finalized for the round).
# quantum_rng_consumer.py — deterministic beacon/QRNG consumer for the Python-VM
# Exposes:
# - beacon() -> bytes # current beacon bytes (may be empty early in round)
# - draw(n: u64, ctx: bytes) -> bytes # derive n bytes with domain-separated context
# - last_draw() -> bytes # persisted last random draw
# - last_ctx() -> bytes # persisted context of last draw
from stdlib import storage, events, abi, hash, syscalls
def _k_last_draw() -> bytes: return b"rng:last_draw"
def _k_last_ctx() -> bytes: return b"rng:last_ctx"
def beacon() -> bytes:
"""
Read the canonical beacon bytes for the *current* round, if the finalize
step has occurred. If called too early it may return b"".
"""
try:
b = syscalls.rand_beacon() # may not exist on all networks; returns bytes or b""
return b or b""
except Exception:
# Fallback: not all networks expose raw-beacon; return empty to signal "not ready".
return b""
def draw(n: int, ctx: bytes) -> bytes:
"""
Derive n random bytes for the given context. Uses syscalls.random(n) which
is a deterministic PRF seeded from the beacon (if available) + chain state.
We additionally domain-separate with caller-provided context.
"""
abi.require(n > 0 and n <= 4096, b"n out of bounds")
abi.require(len(ctx) > 0 and len(ctx) <= 64, b"bad context length")
base = syscalls.random(n) # deterministic, beacon-seeded when available
bcn = beacon() # may be empty if round not finalized yet
# Domain separation: H("DRW" | ctx | base | beacon)
out = hash.keccak256(b"DRW" + ctx + base + bcn)
storage.set(_k_last_draw(), out)
storage.set(_k_last_ctx(), ctx)
events.emit(b"RandomDraw", {b"ctx": ctx, b"n": n, b"hasBeacon": 1 if len(bcn) > 0 else 0})
return out
def last_draw() -> bytes:
return storage.get(_k_last_draw()) or b""
def last_ctx() -> bytes:
return storage.get(_k_last_ctx()) or b""
Notes
- syscalls.random(n) is the portable way to get randomness in contracts. It is deterministic and includes the beacon (and QRNG mix, if enabled) once the round has finalized.
- syscalls.rand_beacon() is optional and may be feature-gated. The contract safely handles its absence by returning b"".
2) Compile the Contract
python -m vm_py.cli.compile quantum_rng_consumer.py --out /tmp/quantum_rng_consumer.ir
python -m vm_py.cli.inspect_ir /tmp/quantum_rng_consumer.ir
3) Deploy & Try It (Python SDK)
The script below deploys the contract, then:
- Calls beacon() to inspect whether the current round has finalized.
- Calls draw(32, b”lottery-epoch-1”) to get 32 bytes for a lottery context.
# deploy_and_draw_rng.py
import time
from omni_sdk.config import Config
from omni_sdk.rpc.http import HttpRpc
from omni_sdk.wallet.mnemonic import new_mnemonic
from omni_sdk.wallet.signer import Dilithium3Signer
from omni_sdk.address import address_from_pubkey
from omni_sdk.tx.build import build_deploy_tx, build_call_tx
from omni_sdk.tx.send import send_and_await_receipt
from omni_sdk.contracts.client import ContractClient
RPC_URL = "http://127.0.0.1:8545"
CHAIN_ID = 1
cfg = Config(rpc_url=RPC_URL, chain_id=CHAIN_ID)
rpc = HttpRpc(cfg)
mn = new_mnemonic()
signer = Dilithium3Signer.from_mnemonic(mn)
addr = address_from_pubkey("dilithium3", signer.public_key())
ir = open("/tmp/quantum_rng_consumer.ir","rb").read()
abi = {
"functions": [
{"name":"beacon","inputs":[],"returns":"bytes"},
{"name":"draw","inputs":[{"name":"n","type":"u64"},{"name":"ctx","type":"bytes"}],"returns":"bytes","mutates":True},
{"name":"last_draw","inputs":[],"returns":"bytes"},
{"name":"last_ctx","inputs":[],"returns":"bytes"}
],
"events":[
{"name":"RandomDraw","inputs":[{"name":"ctx","type":"bytes"},{"name":"n","type":"u64"},{"name":"hasBeacon","type":"u64"}]}
]
}
# 1) Deploy
tx0 = build_deploy_tx(from_address=addr, manifest={"abi": abi}, code=ir,
gas_price=1, gas_limit=700_000, nonce=0)
rcpt0 = send_and_await_receipt(rpc, signer.sign_tx(tx0, CHAIN_ID), 30)
assert rcpt0["status"] == "SUCCESS", rcpt0
contract = rcpt0["contractAddress"]
print("Quantum RNG Consumer deployed at:", contract)
client = ContractClient(rpc=rpc, address=contract, abi=abi)
# 2) Check beacon bytes (may be empty early in a round)
b = client.call("beacon", [])
print("beacon len:", len(b) if isinstance(b, (bytes, bytearray)) else 0)
# 3) Draw 32 bytes for your lottery epoch
n = 32
ctx = b"lottery-epoch-1"
tx1 = build_call_tx(from_address=addr, to_address=contract, abi=abi,
function="draw", args=[n, ctx],
gas_price=1, gas_limit=220_000, nonce=1)
rcpt1 = send_and_await_receipt(rpc, signer.sign_tx(tx1, CHAIN_ID), 30)
print("draw status:", rcpt1["status"])
draw = client.call("last_draw", [])
print("random bytes (hex):", draw.hex() if isinstance(draw, (bytes, bytearray)) else draw)
Tip: If you want draws tied to round boundaries, call draw only after you observe a new round (or new block after beacon finalize). Derive contexts
with the round number in them (e.g., b"lottery-epoch-%d" % round_id).
4) Best Practices
- Context domain separation: Always include a unique context (e.g., contract address, purpose, round/epoch) so outputs are unlinkable across use-cases.
- Round awareness: If your logic requires the next round’s randomness, trigger consumption after the finalize event height. (Your app/SDK can watch rand.getRound via RPC or listen to events if you publish them on finalize.)
- No external entropy: Never feed external random bytes into a contract. All entropy must come from the beacon (already mixed with QRNG if enabled).
- Auditability: For user-facing draws/lotteries, emit events containing the context and output hash so results are easy to verify off-chain.
5) Troubleshooting
- Empty beacon bytes: You’re calling beacon() before the round finalized. Wait a block or two (depending on your randomness/config.py windows).
- Non-reproducible draws in tests: Ensure tests mine at deterministic intervals and pin chain parameters; use the same context and block height.
6) Next Steps
- Build a verifiable on-chain raffle using draw + merkle claims.
- Use beacon-derived randomness to shuffle committee membership.
- In a contract suite, derive per-game seeds (ctx = game_id || round_id) to make simulated games fair and reproducible.
See also:
- docs/randomness/OVERVIEW.md
- docs/randomness/BEACON_API.md
- randomness/specs/LIGHT_CLIENT.md
Happy mixing! 🎲
This page mirrors a file in the animicaorg/all repository. If the repository and this page ever disagree, the repository is authoritative. For long-form explainers written for newcomers, see Learn.