Usage and Best Practices for Randomness Beacon
Introduction
The Algorand randomness beacon allows smart contracts to access random values. This article explains how to securely use this randomness beacon. It complements the article Randomness on Algorand which presents a higher level understanding of how the beacon works.
Warning
IMPORTANTLY: Not following these best practices can lead to complete loss of funds.
Overview of the randomness beacon
The randomness beacon is composed of an external service (oracle) and an associated randomness beacon smart contract. On TestNet, the randomness beacon smart contract has the following app ID: 600011887. On MainNet, the randomness beacon smart contract has the following app ID: 1615566206.
The service generates VRF proofs (see Randomness on Algorand) for every round that is a multiple of 8 and submits them to the randomness beacon smart contract, which verifies the correctness of those proofs and stores their outputs in its global storage. The smart contract can store up to 189 VRF outputs, meaning it stores values for 189 * 8 = 1512 rounds in the past.
The randomness beacon smart contract uses those VRF outputs to generate pseudo-random values to other smart contracts.
Usage
In order for smart contracts to access randomness, they need to query the randomness beacon smart contracts via the following ABI methods:
get(uint64,byte[])byte[]
:- Input:
round uint64
: the round from which we take the VRF outputuser_data byte[]
: optional user data to be hashed with the VRF output
- Output:
get
returns a 32-byte pseudo-random value derived from the stored VRF output corresponding to a given round, the round, and the user data. In case the VRF output is not available for the round, it returns an empty byte array.
- Input:
must_get(uint64,byte[])byte[]
:
must_get
logic is the same as get, but panics in case there’s no value to return.
Best practices
In short, a smart contract using the randomness beacon must:
- Commit to the round on which they will access the randomness
- Ensure a gap (in rounds) between the last input to the smart contracts and the committed round (e.g., stop the lottery registration a few rounds before the committed round to prevent manipulation of the result)
- Read the random value only after it is available (which can take up to 11 rounds in normal operation but much more in case of degraded service. See below for more details)
Read the random value before it becomes unavailable (1512 rounds after it) - Handle gracefully cases where the randomness beacon does not provide values for a certain number of rounds
- Handle gracefully cases where the randomness beacon is discontinued, which means updatability of the smart contract or at least of the app ID of the randomness beacon smart contract.
Smart contracts not following the above best practices may forever stall (and lose funds) or may be subject to attacks on their randomness. Details are provided in the next subsections.
Committing to a future round
A user or smart contract using the above methods must commit publicly to several rounds in the future in which they intend to use the methods. The more rounds the better. Commitment can be implicit or explicit: smart contracts using randomness may hard code the rounds they will be using, “log” them, or just define them implicitly.
In addition, there should be sufficient rounds between the last input on which the randomness may be applied and the round chosen for the randomness. Again, the more rounds, the better.
For example, a lottery smart contract that executes a lottery every 10,000 rounds can systematically use randomness on rounds that are multiple of 10,000 (this is an implicit commitment). In addition, the lottery smart contract will require that all lottery bets are entered 200 rounds (for a high-stake lottery) or 1 round (for a low-stake lottery) before the committed round.
The actual call to the randomness beacon smart contract can only be made after the committed round has passed and the service has submitted the associated VRF proofs.
In addition, it must be done no longer than 1512 rounds after.
Rationale
The pseudo-random values for rounds in the past are already known and not random anymore! If there is no commitment to the round to be chosen, the user can just wait until finding a round for which the pseudo-random value is good for them (e.g., make them win the lottery).
Furthermore, it is important that the block seed used as input to the VRF in the randomness beacon smart contract is unpredictable to the user. The further in the future the committed round is, the harder it is to predict the block seed, even if the user has significant control over the Algorand network.
Timeliness of access to the random value
Any smart contract using the randomness beacon needs to get the random value for the round they are committed to at the right time (by making a get
or must_get
call to the randomness beacon smart contract). In most cases, this requires using an external service to send at the right time the right transaction (that then makes a get
or must_get
call to the randomness beacon smart contract).
Waiting until availability of random values
Random values for a given round are only available after the round and after the service submits the VRF proof. This normally happens at most 3 rounds after the lowest multiple-of-8 round larger or equal to the committed round. For example, for committed round = 1, the next multiple of 8 round is 8, the VRF proof will usually be submitted by round 11, and hence it will be possible to query the random value at round 12.
Warning
However, it is possible that in some cases the VRF service is late and there is a need to wait for more rounds. Smart contracts need to be aware that they may need to wait more than expected to receive random values and services calling those smart contracts should handle the situation gracefully.
Accessing the random value on time
Random values can only be retrieved when the associated VRF output is stored in the randomness beacon smart contract. Concretely, this means the get
or must_get
call to the randomness beacon smart contract must be performed before 1512 rounds after the committed rounds.
In case this is not possible, intermediary/proxy smart contracts and associated services can be designed to store more rounds of random values. The smart contract developer is responsible for running those services
Updatability and resilience against downtime and discontinuation
The randomness beacon may face downtime or full discontinuation. Details about potential risks are provided in Appendix.
From the smart contract’s developer perspective, this means that the smart contract must be resilient to: * The random value for a given round being never available. * The randomness beacon smart contract stopping working completely.
A non-resilient smart contract can lead to complete loss of funds. For example, a non-resilient lottery requiring the random value of round 30,000 to distribute funds to the winner would forever lose the funds if the randomness beacon is discontinued.
The simplest solution to the problem is to allow updatability of the smart contract.
Another option is to use an updateable proxy smart contract between the actual smart contract and the randomness beacon smart contract. This proxy smart contract may just call the randomness beacon smart contract. But due to its updatability, it can be changed to a different randomness beacon smart contract in case the original one gets deprecated.
Yet another solution is to allow participants to withdraw their funds in case there is no way to get the random value for the committed round, or after a fixed amount of blocks have passed. This way the risk of getting funds locked in the smart contract is reduced.
In addition, it’s important that anyone can trigger the smart contract call to get randomness and not only the owner. This way, the owner can’t block distribution of assets in case the outcome is not in their favor.
Appendix: Details about downtime and discontinuation risks
Due to the fact that the randomness smart contract is dependent on an external service to submit VRF proofs, some risks are involved:
- Service is down for a few rounds: if the service is down for a few rounds, a delay can occur in submitting values which means a user must wait longer to get randomness.
- Service is down for more than 1000 rounds: since the randomness smart contract verifies every VRF proof submitted (with the help of
vrf_verify
opcode, explained in this article) and since VRF proofs are generated from block seeds, only proofs that were generated from block seeds of the last 1000 rounds can be verified. In case the service is down for more than 1000 rounds, some rounds won’t have corresponding random values to be used on-chain. Hence every smart contract that is calling the randomness smart contract must be designed in such a way that funds won’t be lost in case it’s impossible to get randomness for the committed round, for example by having one or more backup rounds or by allowing the community to vote on changing the round. - VRF private key is destroyed or compromised, or more generally, the VRF service is discontinuing operations: the randomness smart contract is immutable, meaning it only verifies VRF proofs that were generated with the same private key. In case this key can no longer be used, the randomness smart contract becomes unusable (whether it cannot accept any more VRF proofs or the private key holder cannot be trusted) and a new smart contract must be deployed with a new private key. This means that a user must be resilient to a case where the randomness smart contract is switched, for example by calling a mutable proxy smart contract.