Architecture
DICE has three moving pieces: a fleet of ESP32-S3 devices that produce entropy, an off-chain coordinator that shuttles messages, and a Solana program that settles each round atomically.
Three layers
Every round touches all three layers, in order: hardware generates entropy, protocol aggregates it safely, program verifies and finalises it. You only interact with layer three — but knowing what lives where explains why the guarantees work.
Hardware
ESP32-S3 fleet
RNG sampling, ECDSA signing, CBOR over USB serial.
Coordinator
Rust daemon
Watches chain, requests commits, collects reveals, posts TXs.
Program
Solana · Anchor
Verifies signatures, aggregates, pays operators, CPIs into you.
The hardware layer
Every DICE node is an ESP32-S3 running our firmware. The chip has a hardware true-RNG peripheral that samples from analog noise in the radio front-end. It also has a small persistent flash region that stores one thing: the device's ECDSA private key, written at provisioning and never exported.
What a node exposes
- Device ID — deterministic:
SHA-256(device_pubkey). The program verifies this on every reveal so a device can't pretend to be another. - Round API — over USB serial, CBOR-framed. The coordinator sends a round nonce; the device returns
commit, then laterreveal + signature. - Signed payloads — every message crossing the USB boundary is signed. The private key never leaves the chip.
ℹ Why ESP32-S3 specifically
Low unit cost, deterministic behaviour, a RTC that survives power cycles, and a hardware RNG that independent audits have blessed for cryptographic use. We use a subset of the chip — no Wi-Fi, no Bluetooth — so the attack surface is small.
The commit-reveal protocol
A naive oracle picks entropy after seeing the request, which lets it bias the output. DICE picks entropy before anyone sees it, using a two-phase protocol that's been well-understood since Rabin.
Phase 1 · Commit
Each selected node generates a local random entropy_i ∈ {0,1}^256 plus a round nonce, then computes commit_i = SHA-256(entropy_i ‖ nonce). The coordinator posts all commit_i values to chain in a single submit_round transaction. At this point the entropy is locked — any change would break the pre-image.
Phase 2 · Reveal
After commits land, each node releases reveal_i = entropy_i along with its ECDSA signature over the reveal. The coordinator submits all reveals in one submit_reveal transaction. The program verifies that SHA-256(reveal_i ‖ nonce) == commit_i and that the signature matches the device's registered pubkey.
Phase 3 · Aggregate
The final randomness is randomness = SHA-256(reveal_1 ‖ reveal_2 ‖ … ‖ reveal_n). One honest reveal makes the output uncontrollable by everyone else. The program stores this 32-byte value on the channel and CPIs your callback with it.
✓ Why SHA-256, not XOR?
XOR is algebraically fragile: a last-mover with knowledge of all other shares can grind to a target output. SHA-256 breaks that — the adversary would need to find pre-images.
The on-chain program
The DICE program (FMwPuCjkfZXN2MuNJQiUzZC3hnxHcD8mrTuntsqA84XD) is an Anchor program with four families of instructions:
- Registration — device enrolment, channel creation, coordinator binding.
- Rounds —
request_randomness_auto,submit_round,submit_reveal, finalize. - Callback —
deliver_callbackCPIs into your program with theDiceResultpayload. - Payouts —
claim_rewards_v2credits node vaults from the round escrow; 70/20/10 split.
Every state transition is an on-chain event, so a full history of commits, reveals, and signatures is publicly reproducible. See Verifying randomness for the recipe.
A round, end to end
Here's the wall-clock view — numbers taken from a 50-round devnet bench on real hardware, v7.7 streaming:
| t | Layer | Event |
|---|---|---|
| 0 ms | Your program | request_randomness_auto lands. Round is open. |
| ~400 ms | Coordinator → devices | 4 nodes selected, commit request broadcast. |
| ~1.5 s | Chain | submit_round lands with all 4 commits. |
| ~3.0 s | Chain | submit_reveal lands with signatures. |
| ~4.0 s | Chain | deliver_callback CPIs your dice_callback. Round finalized. Operators paid. |
From the caller's perspective: submit TX, wait 4 seconds, read your settled game state. Streaming mode: avg 4.05 s, p50 3.99 s, p95 4.62 s over 50 rounds, 98% pass rate.