Native Onchain Randomness on Flow
Flow enhances blockchain functionality by providing native onchain randomness, eliminating reliance on external oracles in Web3. This secure, decentralized feature empowers developers to build a variety of applications with truly unpredictable, transparent, and fair outcomes, achieved with greater efficiency.
Flow’s onchain randomness delivers immediate random values within smart contracts, bypassing the latency and complexity of oracle integration. Developers can obtain verifiably random results with a single line of Cadence code, streamlining the development process and enhancing the performance of decentralized applications.
Use Cases of Onchain Randomness
- Gaming: Integrates fairness and unpredictability into gameplay, enhancing user engagement without delays.
- NFTs: Facilitates the creation of uniquely randomized traits in NFTs quickly, adding to their rarity and value.
- Lotteries & Draws: Offers instant and verifiably fair random selection for lotteries, solidifying trust in real-time.
- DeFi Protocols: Enables rapid and innovative random reward systems within decentralized finance.
- DAOs: Assists in unbiased voting and task assignments through immediate randomness.
- Broad Applications: Extends to any domain requiring impartial randomization, from asset distribution to security mechanisms, all with the added benefit of on-demand availability.
History - Distributed Random Beacon
Within the Flow protocol, the heart of randomness generation lies in the "distributed random beacon." This module generates randomness that is distributed across the network while adhering to established cryptographic and security standards. The output from the randomness beacon is a sequence of random bytes that are unpredictable and impartial.
For over three years, the beacon has ensured protocol security by selecting which consensus node gets to propose the next block and assigning verification nodes to oversee block computations. For those interested in a more detailed exploration of the randomness beacon and its inner workings, you can read the technical deep dive on the Flow forum.
Guidelines for Safe Usage
For usage of randomness where result abortion is not an issue, it is recommended to use the Cadence built-in function revertibleRandom.
revertibleRandom
returns a pseudo-random number and is also based on the safe Random Beacon.
_10// Language reference:_10// https://developers.flow.com/cadence/language/built-in-functions#revertiblerandom_10access(all) fun main(): UInt64 {_10 // Simple assignment using revertibleRandom - keep reading docs for safe usage! _10 let rand: UInt64 = revertibleRandom()_10 return rand_10}
It is notable that the random number generation process is unpredictable (for miners unpredictable at block construction time and for cadence logic unpredictable at time of call), verifiable, uniform, as well as safe from bias by miners and previously-running Cadence code.
Although Cadence exposes safe randomness generated by the Flow protocol via revertibleRandom
, there is an additional safety-relevant aspect that developers need to be mindful about.
The revertibleRandom
function can be used safely in some applications where the transaction results are NOT deliberately reverted after the random number is revealed (i.e. a contract distributing random NFTs to registered users or onchain lucky draw). However, if applications require a non-trusted party (for instance app users) to submit a transaction calling a randomized (non-deterministic) contract, the stream of random numbers produced is potentially unsafe in the following two regards:
- The sequence of random numbers is potentially predictable by transactions within the same block and by other smart contracts calling into your smart contract.
- A transaction calling into your smart contract can potentially bias the sequence of random numbers which your smart contract internally generates. Currently, the block hash seeds
revertibleRandom
. Consensus nodes can easily bias the block hash and influence the seed forrevertibleRandom
.
🚨 A transaction can atomically revert all its action during its runtime and abort. Therefore, it is possible for a transaction calling into your smart contract to post-select favorable results and revert the transaction for unfavorable results.
In other words, transactions submitted by a non-trusted party are able to reject their results after the random is revealed.
💡 Post-selection - the ability for transactions to reject results they don't like - is inherent to any smart contract platform that allows transactions to roll back atomically. See this very similar Ethereum example.
The central aspect that a contract developer needs to think about is the following scenario:
- Imagine an adversarial user that is sending a transaction that calls your smart contract.
- The transaction includes code that runs after your smart contract returns and inspects the outcome.
- If the outcome is unfavorable (based on some criteria codified in the transaction), the transaction aborts itself.
As an example, consider a simple coin toss randomized contract where users can bet any amount of tokens against a random binary output. If the coin toss contract outputs 1
, the user doubles their bet. If the coin toss contract outputs 0
, the user loses their bet in favor of the coin toss.
Although the user (or the honest coin toss contract) cannot predict or bias the outcome, the user transaction can check the randomized result and cancel the transaction if they are losing their bet. This can be done by calling an exception causing the transaction to error. All temporary state changes are cancelled and the user can repeat the process till they double their bet.
Commit-Reveal Scheme
The recommended way to mitigate the problems above is via a commit-reveal scheme. The scheme involves two steps: commit and reveal. During the commit phase, users submit a commitment that contains the hash of their answer along with a random seed value. The smart contract stores this commitment on the blockchain. Later, during the reveal phase, the user reveals their answer and the seed value.
There are ideas how to further optimize the developer experience further in the future. For example, a transaction could delegate part of its gas to an independent transaction it spawns. Conceptually, also this future solution would be a commit-and-reveal scheme, just immediately happening within the same block. Until we eventually get to this next level, developers may need to implement their own commit-reveal. In Cadence, it is clean and short.
Protocol improvements (documented in FLIP 120) expose the random beacon to the FVM and Cadence where it can be used to seed pseudo-random number generators (PRG) for smart contracts.
In simpler terms, the native secure randomness provided by the protocol can now be safely utilized within Cadence smart contracts and is available to all developers on Flow and the FVM.
While the source values are safely generated by the Random Beacon and transmitted into the execution environment via the committing transaction, using the raw values from this contract does not guarantee non-revertible randomness. In addition to the commit-reveal pattern described below, it is also recommended to use the source values with a pseudo-random number generator (PRG) to generate an arbitrarily-long sequence of random values.
Adding a safe pattern to reveal randomness without the possibility of conditional transaction reversion unlocks applications relying on randomness. By providing examples of commit-reveal implementations we hope to foster a more secure ecosystem of decentralized applications and encourage developers to build with best practices.
FLIP 123
On Flow, we have absorbed all security complexity into the platform.
FLIP 123: On-chain Random beacon history for commit-reveal schemes was introduced to provide a safe pattern to use randomness in transactions so that it's not possible to revert unfavorable randomized transaction results.
We recommend this approach as a best-practice example for implementing a commit-reveal-recover scheme in Cadence. The RandomBeaconHistory
contract provides a convenient archive, where for each past block height (starting Nov 2023) the respective “source of randomness” can be retrieved. The RandomBeaconHistory
contract is automatically executed by the system at each block to store the next source of randomness value.
💡 While the commit-and-reveal scheme mitigates post-selection of results by adversarial clients, Flow’s secure randomness additionally protects against any pre-selection vulnerabilities (like biasing attacks by byzantine miners).
A commit-reveal scheme can be implemented as follows. The coin toss example described earlier will be used for illustration:
- When a user submits a bidding transaction, the bid amount is transferred to the coin toss contract, and the block height where the bid was made is stored. This is a commitment by the user to use the SoR at the current block. Note that the current block's
SoR_A
isn't known to the transaction execution environment, and therefore the transaction has no way to inspect the random outcome and predict the coin toss result. The current block'sSoR_A
is only available once added to the history core-contract, which only happens at the end of the block's execution. The user may also commit to using an SoR of some future block, which is equally unknown at the time the bid is made. - The coin toss contract may grant the user a limited window of time (i.e a block height range) to reveal the results and claim any winnings. Failing to do so, the bid amount remains in the coin toss contract.
- The user can submit a second transaction to call the coin toss contract and resolve the bid. The coin toss contract looks up the committed block height of the user and checks it has already passed. The contract uses the block height to query the past-block's
SoR_A
on the core-contractRandomBeaconHistory
. - The coin toss contract uses a PRG seeded with the queried
SoR_A
and diversified using a specific information to the use-case (a user ID or resource ID for instance). Diversification does not add new entropy, but it avoids generating the same outcome for different use-cases. If a diversifier (or salt) isn't used, all users that committed a bid on the same block would either win or lose. - The PRG is used to generate the random result and resolve the bid. Note that the user can make the transaction abort after inspecting a losing result. However, the bid amount would be lost anyway when the allocated window expires.
The following lines of code illustrate a random coin toss, that cannot be gamed or biased. The reveal-and-commit scheme prevent clients from post-selecting favorable outcomes.
It proceeds with two phases: first commit using the hash of the values and in a later phase revealing the values.
_51/// --- Commit ---_51/// In this method, the caller commits a bet. The contract takes note of the_51/// block height and bet amount, returning a Receipt resource which is used_51/// by the better to reveal the coin toss result and determine their winnings._51access(all) fun commitCoinToss(bet: @FungibleToken.Vault): @Receipt {_51 let receipt <- create Receipt(_51 betAmount: bet.balance_51 )_51 // commit the bet_51 // `self.reserve` is a `@FungibleToken.Vault` field defined on the app contract_51 // and represents a pool of funds_51 self.reserve.deposit(from: <-bet)_51 _51 emit CoinTossBet(betAmount: receipt.betAmount, commitBlock: receipt.commitBlock, receiptID: receipt.uuid)_51 _51 return <- receipt_51}_51_51/// --- Reveal ---_51/// Here the caller provides the Receipt given to them at commitment. The contract_51/// then "flips a coin" with randomCoin(), providing the committed block height_51/// and salting with the Receipts unique identifier._51/// If result is 1, user loses, if it's 0 the user doubles their bet._51/// Note that the caller could condition the revealing transaction, but they've_51/// already provided their bet amount so there's no loss for the contract if_51/// they do_51access(all) fun revealCoinToss(receipt: @Receipt): @FungibleToken.Vault {_51 pre {_51 receipt.commitBlock <= getCurrentBlock().height: "Cannot reveal before commit block"_51 }_51_51 let betAmount = receipt.betAmount_51 let commitBlock = receipt.commitBlock_51 let receiptID = receipt.uuid_51 // self.randomCoin() errors if commitBlock <= current block height in call to_51 // RandomBeaconHistory.sourceOfRandomness()_51 let coin = self.randomCoin(atBlockHeight: receipt.commitBlock, salt: receipt.uuid)_51_51 destroy receipt_51_51 if coin == 1 {_51 emit CoinTossReveal(betAmount: betAmount, winningAmount: 0.0, commitBlock: commitBlock, receiptID: receiptID)_51 return <- FlowToken.createEmptyVault()_51 }_51 _51 let reward <- self.reserve.withdraw(amount: betAmount * 2.0)_51 _51 emit CoinTossReveal(betAmount: betAmount, winningAmount: reward.balance, commitBlock: commitBlock, receiptID: receiptID)_51 _51 return <- reward_51}
An Invitation to Build
Flow's onchain randomness opens new doors for innovation in web3, offering developers the tools to create fair and transparent decentralized applications. With this feature, new possibilities emerge—from enhancing gameplay in decentralized gaming to ensuring the integrity of smart contract-driven lotteries or introducing novel mechanisms in DeFi.
This is an invitation for builders and creators: leverage Flow's onchain randomness to distinguish your projects and push the boundaries of what's possible. Your imagination and code have the potential to forge new paths in the web3 landscape. So go ahead and build; the community awaits the next big thing that springs from true randomness.
Learn More
If you’d like to dive deeper into Flow’s onchain randomness, here’s a list of resources:
- To learn more about how randomness works under the hood, see the forum post.
- These documents provide a more in-depth technical understanding of the updates and enhancements to the Flow blockchain.
- To see working Cadence code, explore the coin toss example on GitHub.
- Read the blog post announcing Flow’s native randomness.