Verifiable randomness on gno.land with drand
A blockchain cannot roll a die. Everything a contract can read, the block height, the block time, the previous hashes, is known or chosen by someone before the transaction runs. A validator can nudge the time, a user can pick the block. For a raffle with real money, that is not good enough.
gno-drand fixes that for every gno.land realm, using drand, the public randomness beacon run by the League of Entropy. I announced it on X:
albttx
@albttx
Verifiable randomness on @_gnoland 🎲
github.com/albttx/gno-drandHow it works 🧵
Oct 3, 2026 · View on X
The idea comes from Nois, a Cosmos blockchain that delivered drand randomness to other chains over IBC. I ran a validator on it with nysa.network, and saw the model work in production: a request pinned to a future beacon, a relayer anyone can run, a signature checked on-chain. gno-drand keeps that model and drops the second chain.
How it works
- A realm calls
drand.Request(). The request is pinned to the first drand round published at least 5 seconds after the current block time. That round does not exist yet, so nobody, the caller or the validators, can know the outcome. - Anyone relays that round's beacon once drand publishes it.
- The chain verifies the BLS signature of the beacon before storing it. Only rounds someone asked for are stored.
- Each request gets its own value:
sha256(beacon || requester || ":" || id). Two requests on the same round get independent results.
A relayer can deliver the real beacon, or nothing. It cannot lie, so it does not need to be trusted. Relayers only matter for liveness, and anyone can run one.
Using it from a realm
Two transactions. The first freezes the inputs and asks for randomness, the second uses it:
import "gno.land/r/albttx/drand/v0"
// tx 1: freeze your inputs (bets, tickets), then request.
func Close(cur realm) {
reqID = drand.Request(cross(cur))
}
// tx 2: a few seconds later, once a relayer has delivered the round.
func Draw(cur realm) {
if _, ready := drand.Status(reqID); !ready {
panic("not yet")
}
winner = players[drand.Rand(reqID).IntN(len(players))]
}drand.Rand(id) returns a standard PRNG seeded with the beacon, so IntN, Shuffle and Perm work as usual. There is one rule: record everything the randomness decides before calling Request. Games, raffles, lotteries, fair draws.
The trick: a curve gno.land already has
drand runs several networks. Most sign on BLS12-381, a curve gno.land has no native support for. Verifying those signatures would have needed a new native function in the VM and a chain upgrade.
drand's evmnet network signs on BN254 instead, one beacon every 3 seconds, and gno.land already ships BN254 as native functions, with the same layout as Ethereum's precompiles. The whole verification is one native pairing check:
e(sig, G2) == e(H(m), pk)
Only one piece was missing: the hash-to-curve, which turns the round number into a point H(m) on the curve (RFC 9380, SVDW map, keccak256). That part is written in Gno.
Making field math cheap in an interpreter
Hash-to-curve needs a lot of 256-bit modular arithmetic, and a 256x256 multiplication interpreted by the VM is expensive. gno.land has one more native that helps: modexp, modular exponentiation.
Squaring, inversion and square root are all exponentiations, so they go straight to the native. Multiplication is not, but it can be rewritten as two squarings with the quarter-squares identity:
a * b = ((a + b) / 2)^2 - ((a - b) / 2)^2
The Gno code only does additions, subtractions and halvings on four 64-bit limbs. Everything heavy runs natively. One verification costs about 12 million gas, roughly 0.012 GNOT at mainnet prices. The relayer pays it once per round, however many requests share that round.
Design choices
- No admin. The realm has no owner, no pause, no parameter a key can change. A change in behaviour means a new version.
- Permissionless relaying.
Submitaccepts any beacon from anyone, and panics on a bad signature. A resubmit is a no-op. - Relayers check before paying. The Go relayer verifies each beacon locally before sending it, and batches up to 10 per transaction. A bad drand mirror never costs gas.
- Single chain. Nois needed its own chain and IBC to serve others. Here requests, verification and consumers all live on gno.land.
Limits
- BN254 security. About 100 bits, against about 128 for BLS12-381. It is the trade-off Ethereum made for its precompiles, and it is fine for games, raffles and lotteries.
- drand's trust model. The randomness is unbiasable unless a threshold of the League of Entropy nodes collude.
- Liveness. If no relayer runs, requests wait. Nothing wrong can be delivered.
- Block time. The 5-second safety gap covers normal drift. A proposer setting the block time far behind real time could pin a request to a round already published.
What's next
The repository is the prototype. The code is being upstreamed into the official gno repository in gnolang/gno#6268, under gno.land/p/drand/v0 and gno.land/r/drand/v0. That version is cheaper still: about 9.6 million gas to verify, 17.9 million for a full Submit.