Randomness on Moonbeam¶
Introduction¶
Randomness is necessary for a variety of blockchain applications to create unbiased, unpredictable, and unique outcomes. However, obtaining a reliable source of randomness is a challenge. Computers are deterministic, meaning given the same input, the same output will always be produced. Therefore, random values generated by computers are referred to as pseudo-random as they appear to be statistically random, but given the same input, the output can easily be repeated.
Moonbeam utilizes verifiable random functions (VRF) to generate randomness that can be verified on-chain. A VRF is a cryptographic function that takes some input and produces random values, along with a proof of authenticity that these random values were generated by the submitter. The proof can be verified by anyone to ensure the random values generated were calculated correctly.
There are two available sources of randomness that provide random inputs based on block producers' VRF keys and past randomness results: local VRF and BABE epoch randomness. Local VRF is determined directly within Moonbeam using the collator of the block's VRF key and the last block's VRF output. On the other hand, BABE epoch randomness is based on all the VRF produced by the relay chain validators during a complete epoch.
You can interact with and request on-chain randomness using the Randomness Precompile contract, a Solidity interface that enables smart contract developers to access the randomness functionality through the Ethereum API. For more information, please check out the Interacting with the Randomness Precompile guide. You can also take a look at the Randomness Pallet, which can be used to obtain current randomness requests and results.
General Definitions¶
- Epoch - a time duration in the BABE protocol that is broken into smaller time slots. Slots are discrete units of time six seconds in length. On Polkadot, one epoch lasts approximately 2,400 slots or 4 hours. On Kusama, one epoch lasts approximately 600 slots or 1 hour.
- Deposit - an amount of funds required to request random words. There is one deposit per request. Once the request has been fulfilled, the deposit will be returned to the account that requested the randomness
- Block expiration delay - the number of blocks that must pass before a local VRF request expires and can be purged
- Epoch expiration delay - the number of epochs that must pass before a BABE request expires and can be purged
- Minimum block delay - the minimum number of blocks before a request can be fulfilled for local VRF requests
- Maximum block delay - the maximum number of blocks before a request can be fulfilled for local VRF requests
- Maximum random words - the maximum number of random words being requested
- Epoch fulfillment delay - the delay in epochs before a request can be fulfilled for a BABE request
Quick Reference¶
Variable | Value |
---|---|
Deposit | 100 GLMR |
Block expiration delay | 10000 blocks |
Epoch expiration delay | 10000 epochs |
Minimum block delay | 2 blocks |
Maximum block delay | 2000 blocks |
Maximum random words | 100 words |
Epoch fulfillment delay | 2 epochs (following the current one) |
Variable | Value |
---|---|
Deposit | 1 MOVR |
Block expiration delay | 10000 blocks |
Epoch expiration delay | 10000 epochs |
Minimum block delay | 2 blocks |
Maximum block delay | 2000 blocks |
Maximum random words | 100 words |
Epoch fulfillment delay | 2 epochs (following the current one) |
Variable | Value |
---|---|
Deposit | 1 DEV |
Block expiration delay | 10000 blocks |
Epoch expiration delay | 10000 epochs |
Minimum block delay | 2 blocks |
Maximum block delay | 2000 blocks |
Maximum random words | 100 words |
Epoch fulfillment delay | 2 epochs (following the current one) |
Local VRF¶
Local VRF randomness is generated on a block-by-block basis at the beginning of the block using the previous block's VRF output along with the public key of the current block author's VRF key. The generated randomness result is stored and used to fulfill all randomness requests for the current block.
You can request local VRF randomness using the requestLocalVRFRandomWords
method of the Randomness Precompile.
If your contract could have concurrent requests open, you can use the requestId
returned from the requestLocalVRFRandomWords
method to track which response is associated with which randomness request.
BABE Epoch Randomness¶
BABE epoch randomness is based on a hash of the VRF values from the blocks produced in the relay chain epoch before last. On Polkadot, an epoch lasts for roughly 4 hours, and on Kusama, an epoch lasts for roughly 1 hour. The hashing is completed on the relay chain, and as such, it is not possible for a collator on Moonbeam to influence the randomness value unless they are also a validator on the relay chain and were responsible for producing the last output included in an epoch.
The randomness is constant during a full epoch, so if a collator skips block production, the next collator can fulfill it with the same random value.
You can request BABE epoch randomness using the requestRelayBabeEpochRandomWords
method of the Randomness Precompile. In order to generate unique randomness, a different salt must be provided to the requestRelayBabeEpochRandomWords
function.
At the beginning of each relay chain epoch change, the randomness from one epoch ago is read from the relay chain state proof and used to fulfill all randomness requests that are due in the current block.
Request & Fulfill Process¶
In general, the request and fulfill process for randomness is as follows:
- Pay the deposit required to request random words
-
Request the randomness either using the local VRF or BABE epoch source of randomness. When requesting randomness you'll need to specify a few things:
- a refund address where any excess fees will be sent to
- the amount of fees which will be set aside to pay for fulfillment. If the specified amount is not enough you can always increase the request fees later, or if it's more than enough you'll be refunded the excess fees to the specified address after fulfillment
- a unique salt that will be used to generate different random words
- the number of random words you would like to request
- for local VRF, the delay period in blocks, which is used to increase unpredictability. It must be between the minimum and maximum number of blocks as listed above. For BABE epoch randomness, you do not need to specify a delay but can fulfill the request after the epoch delay has passed
-
Wait for the delay period to pass
- Fulfill the randomness request, which triggers the random words to be computed using the current block's randomness result and the given salt. This can manually be done by anyone using the fee that was initially set aside for the request
- For fulfilled requests, the random words are returned and the cost of execution will be refunded from the request fee to the address that initiated the fulfillment. Then any excess fees and the request deposit are transferred to the specified refund address
If a request expires it can be purged by anyone. When this happens, the request fee is paid out to the address that initiated the purge and the deposit is returned to the original requester.
The happy path for a randomness request is shown in the following diagram:
Security Considerations¶
A method with the ability to call your fulfillRandomness
method directly could spoof a VRF response with any random value, so it's critical that it can only be directly called by the RandomnessConsumer.sol
contract's rawFulfillRandomness
method.
For your users to trust that your contract's random behavior is free from malicious interference, it's best if you can write it so that all behaviors implied by a VRF response are executed during your fulfillRandomness
method. If your contract must store the response (or anything derived from it) and use it later, you must ensure that any user-significant behavior which depends on that stored value cannot be manipulated by a subsequent VRF request.
Similarly, the collators have some influence over the order in which VRF responses appear on the blockchain, so if your contract could have multiple VRF requests in flight simultaneously, you must ensure that the order in which the VRF responses arrive cannot be used to manipulate your contract's user-significant behavior.
Since the output of the random words generated for requestLocalVRFRandomWords
is dependent on the collator producing the block at fulfillment, the collator could skip its block, forcing the fulfillment to be executed by a different collator and therefore generating a different VRF. However, such an attack would incur the cost of losing the block reward to the collator. It is also possible for a collator to be able to predict some of the possible outcome of the VRF if the delay between the request and the fulfillment is too short. It is for this reason that you can choose to provide a higher delay.
Since the output of the random words generated for requestRelayBabeEpochRandomWords
is dependent on the relay chain validator producing the blocks during an epoch, it is possible for the last validator of an epoch to choose between two possible VRF outputs by skipping the production of a block. However, such an attack would incur the cost of losing the block reward to the validator. It is not possible for a parachain collator to predict or influence the output of the relay chain VRF, not to censor the fulfillment, as long as there is one honest collator.
| Created: August 19, 2022