VRF and verifiable randomness for smart contracts
What is verifiable randomness?
Verifiable randomness is a random value a smart contract receives with a signature or proof anyone can check, so whoever runs a draw can show they didn’t pick the result; a verifiable random function (VRF) is the best-known way to produce one.
Prize-linked savings apps use it to draw winners, token sale platforms to allocate oversubscribed sales by lottery, and DAOs to pick jurors or committee members, because a contract has no source of chance that block producers can’t predict or nudge, and entrants depend on draws nobody could rig.
The setup here takes each value from drand, a public randomness beacon signed jointly by independent organizations, and stores it with its signature: anyone can check it afterwards, but the contract trusts the wallet that delivers it, so a swapped value can be caught, not prevented.
With Goldsky, a Compose task answers each request your contract emits: it fetches the drand round and writes it back from the wallet your contract names as its only fulfiller.
Who uses verifiable randomness?
Prize-linked savings apps, token sale and launch platforms that allocate by lottery, DAOs and dispute protocols that pick jurors or committee members, and the chains and oracle networks that offer randomness to apps built on them; raffle and giveaway apps, games and NFT projects use it too.
How verifiable randomness reaches a smart contract
The diagram follows one randomness request, from the contract call that makes it to the value anyone can check against drand, and shows who does each step. Your setup may differ.
Create the fulfiller wallet
Handled by Goldsky ComposeYour contract will take answers from one address only, so that address has to exist before the contract does.
Whoever can make this wallet sign can write any value the contract accepts, so it should do nothing else, and changes to the code that uses it need the same review as changes to the contract.
Compose creates a named smart wallet the first time a task calls
Create a Compose wallet (opens in a new tab)evm.wallet({ name })and returns the same wallet on every later call, so you pass its address to the contract when you deploy it.Deploy the request contract
The contract is where requests are recorded and where each answer is stored for everyone to see.
It records each request, emits an event with the request’s ID, and accepts one answer per request, only from the fulfiller wallet, storing the value with its round and signature. It also needs a way to cancel a request left open too long, because drand can miss a round and a task can fail.
Fix the round that answers each request
If whoever answers can choose the round, they can wait for a value they like, and every later check would still pass.
drand publishes rounds at fixed times from a known start, so at request time the contract can record a round due a little after the request’s block time, then reject an answer for any other round. Leave a margin of more than one round, so drand can’t already have published it when the request lands.
A user requests randomness
Each draw, allocation or selection starts with a call to your contract.
The user or your app calls the request function, for example when a prize period ends or a sale’s entry window closes, and the contract emits a
RandomnessRequestedevent with the request’s ID. Lock the entries, stakes and anything else the draw decides in the same call, because drand publishes each round to everyone before your contract stores it.Pick up each request
Handled by Goldsky ComposeA contract can’t call out to drand itself, so something offchain has to see the request and answer it.
Answering a request from a block the chain later drops in a reorg (the chain replacing its newest blocks) ties a value to a request that no longer exists. Waiting until the request’s block is a few blocks deep avoids that, at the cost of a short delay.
With Goldsky, an
Trigger a task from contract events (opens in a new tab)onchain_eventtrigger runs the Compose task for eachRandomnessRequestedlog your contract emits. Settingconfirmations, withparallel_processingon, holds each request until its block is deep enough; requests don’t depend on each other, so losing block order costs nothing.Fetch the round from drand
Handled by Goldsky ComposeThe random value comes from drand, so each request needs its round’s value and signature.
Fetch the round the contract fixed for the request, never whichever round is newest when the task runs. drand’s value is the hash of the round’s signature, so the response holds everything needed to check it later; if the round isn’t out yet, the task has to wait and try again.
The task calls drand’s HTTP API with
Call an API from a task (opens in a new tab)ctx.fetch, which retries failed reads and records each URL and response on the run. Goldsky’s guide fetches drand’s latest round; request the round number your contract fixed instead.Store the value onchain
Handled by Goldsky ComposeThe value is only usable once it’s stored in the contract that asked for it.
The answer carries the request’s ID, the value, the round and the signature, and the contract rejects it unless it comes from the fulfiller wallet, names the fixed round and finds the request still open. An answer that a reorg removes has to be sent again, or the request stays open.
The task calls
Call your contract from a task (opens in a new tab)fulfillRandomnesson a typed class Compose generates from your contract’s ABI, signed by the fulfiller wallet, andonReorgwithreplaysends the answer again if a reorg removes it after it confirmed.Trace every answer
Handled by Goldsky ComposeWhen someone disputes a draw, you need to show what was fetched and what was sent.
Each answer should trace back to the drand response and the transaction that stored it. A failed run can also leave a request open, and an open request holds up the draw waiting on it.
Goldsky logs every context call a Compose task makes, so each run keeps the drand response beside the transaction that stored it. An event whose run fails every retry isn’t delivered again, so a scheduled task that finds open requests and answers them closes the gap.
How retries and logging work (opens in a new tab)Draw with the stored value
The stored value is what decides the winner, the allocation or the selection.
Derive each outcome from it, for example by hashing it with the request’s ID and an entry’s number, so one request can settle many outcomes. Use a value only for the request it answered: anyone who has seen it can work out what a reused value decides next.
Check the value against drand
The contract doesn’t check drand’s signature, so a check afterwards is what shows a value wasn’t swapped.
With a drand client library, anyone can confirm that the stored signature is valid under drand’s public key, that the value is the hash of that signature, and that the round is the one the request fixed. A failed check proves a swap but doesn’t undo the draw, so contracts that pay out significant value should verify the signature onchain before using it.
What you control
- Your team
- Deploy the request contract
- Fix the round that answers each request
- Check the value against drand
Questions and answers
What is a verifiable random function?
A verifiable random function (VRF) turns an input and a secret key into a random output plus a proof, which anyone holding the matching public key can check. Each input has only one valid output, so the key holder can’t swap in a result they prefer without the proof failing. Onchain, a VRF service answers each request with its own proof. drand works differently: it’s a beacon that publishes one signed value per round for everyone, rather than a proof for each request.
How is drand randomness different from a VRF that verifies onchain?
A VRF that verifies onchain rejects a forged value inside the contract, while a contract that stores drand randomness without checking its signature trusts the wallet that delivers it. drand also signs something different: one public value per round, shared by everyone, rather than a proof tied to each request. Anyone can check a stored round against drand’s public key afterwards, so a swapped value can be caught but not stopped. Checking the signature inside the contract closes that gap, at a higher gas cost on every answer.
Security and deployment
- SOC 2 Type IIIndependently audited security controls.
- Dedicated infrastructureSingle-tenant options for production workloads.
- Data residencyCloud, on-premises or co-located.
More use cases that run protocol automation
- Run your own oracle price feedPush prices and other data to contracts on many chains.
- Smart contract automation and keepersRun recurring or event-driven contract calls a protocol needs to keep working.
- Onchain circuit breakersPause a token, market or payout when a rule is breached.
- Cross-chain automationAct on one chain when an event happens on another.
- Onchain interest rate, funding rate and volatility feedsPublish interest rates, funding rates and volatility benchmarks to contracts.
Use cases built the same way
- Tokenized stock mint and redeem against ordersTrigger mints and redemptions when a customer buys or sells, and record every step for the books.
- Authorize and capture stablecoin paymentsHold a buyer’s stablecoins in an escrow contract at checkout, capture them to the merchant as orders ship, void what can’t be filled and refund after capture.
- Stablecoin payroll and contractor payoutsPay contractors and staff in stablecoins and confirm each payout settled.
- Stablecoin payouts for marketplaces and creatorsPay sellers, gig workers and creators in stablecoins from a marketplace balance.
Deliver verifiable randomness to a smart contract with Goldsky
We’ll map it to your systems and show you what Goldsky runs, so your team builds the product, not the pipelines underneath it.