Skip to main content

Secure Random Numbers

This guide explains how to obtain secure random numbers on Flare. Secure randomness is generated by the Scaling protocol, which leverages a decentralized network of approximately 100 data providers who generate random numbers every 90 seconds.

The protocol aggregates these individual random numbers to produce a final random number that is both uniform and resistant to manipulation. The uniformity of this random number is ensured as long as at least one of the data providers remains honest, i.e. 1-of-N. The protocol has an in-built security mechanism to detect manipulation attempts, and will warn the end user if such an attempt is detected.

Understand the mechanism behind secure random numbers on Flare.

As described in the FTSOv2 whitepaper, the Scaling protocol consists of the following phases:

  1. Commit: During the Commit phase, data providers prepare their submissions for each of the data feeds and encode them into a 4-byte vector. Then, each data provider publishes on chain a hash commitment obtained as:

    Hash(address, voting_epoch_id, random_number, price_data)

    • Random Number: This commit includes a locally generated random number.
    • Purpose: The random number blinds the commit hash of the user from a search attack and is used later (once revealed) to contribute to onchain randomness.
  2. Reveal: During the Reveal phase, each data provider reveals all inputs to their hash commitment. As such, all locally produced random numbers become available onchain.

  3. Signing: After the Reveal phase, data providers perform a number of local computations relevant to the Scaling protocol, which include:

    • Computing the weighted median prices
    • Calculating the rewards

    All these are packaged into a Merkle root, which is published onchain together with a signature of the root.

  4. Finalization: Once enough signatures for the same Merkle root are gathered, the process is finalized.

Secure Random Numbers

For each voting epoch (90 seconds), an overall random number is generated from the local random numbers:

R=iri(modN)R = \sum_{i} r_i \pmod{N}

where rir_i is the local random number generated by the ithi^{th} data provider, and N=2nN = 2^n denotes the maximum possible size of the individual nn-bit random numbers. This mechanism ensures that the resultant RR is a uniformly generated random number as long as at least any one of the inputs was an honestly generated uniformly random number.

Importantly, the Merkle root published by the data providers contains a Boolean value that tracks whether the generated random number for the current voting epoch is secure.

Security Mechanism

The security mechanism behind the random number generator protects the protocol against withholding attacks. An adversary could wait until all submissions are known and then choose whether or not to reveal their own commit data to influence the final result. This security mechanism measures the quality of the random number as follows:

  • True: If there are no omissions of reveals for the commits provided by the data providers.
  • False: If any omission exists, or if a reveal does not match the committed value.

If a data provider causes an omission (or false reveal), they will be penalized, and their random number will not be included in the random number calculation for a number of voting rounds.

Use secure random onchain

tip

You can integrate secure random numbers into your application on Flare for no cost (not even gas!).

SecureRandomConsumer.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
import {RandomNumberV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/RandomNumberV2Interface.sol";

/**
* THIS IS AN EXAMPLE CONTRACT.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract SecureRandomConsumer {
RandomNumberV2Interface internal randomV2;

/**
* Initializing an instance with RandomNumberV2Interface.
* The contract registry is used to fetch the contract address.
*/
constructor() {
randomV2 = ContractRegistry.getRandomNumberV2();
}

/**
* Fetch the latest secure random number.
* The random number is generated every 90 seconds.
*/
function getSecureRandomNumber()
external
view
returns (uint256 randomNumber, bool isSecure, uint256 timestamp)
{
(randomNumber, isSecure, timestamp) = randomV2.getRandomNumber();
/* DO NOT USE THE RANDOM NUMBER IF isSecure=false. */
require(isSecure, "Random number is not secure");
/* Your custom RNG consumption logic. In this example the values are just returned. */
return (randomNumber, isSecure, timestamp);
}
}

In addition to the randomNumber itself, two other variables are retrieved:

  • isSecure: A boolean flag indicating whether the random number was generated securely. If the protocol detects any attempt to manipulate the random number, this flag is set to false.

  • timestamp: The UNIX timestamp marking the end of the voting epoch during which data was collected from data providers to generate the specific number. Each voting epoch lasts for a fixed 90-second window.

Set EVM Version to London
  • Using Remix: Set EVM version to london in the Advanced Configurations section of the Solidity Compiler tab:

  • Using Hardhat or Foundry: Set EVM version to london in hardhat.config.ts or foundry.toml.

  • Using Standard Solidity JSON: Set evmVersion to london:

    {
    "settings": {
    "optimizer": {
    /* ... */
    },
    "evmVersion": "london"
    }
    }
  • Using solc CLI: Set --evm-version to london:

    solc --evm-version london <args>

Example lottery application

This contract implements an example simple lottery system that utilizes a secure random number to select a winner. Participants can enter the lottery, and the winner is drawn using the secure random number generated by RandomNumberV2.

RandomNumberV2Lottery.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9.0;

import {RandomNumberV2Interface} from "@flarenetwork/flare-periphery-contracts/coston2/RandomNumberV2Interface.sol";

/**
* THIS IS AN EXAMPLE CONTRACT.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
/**
* @title LotteryWithRandomNumber
* @notice A lottery contract that utilizes a secure random number for determining winners.
*/
contract LotteryWithRandomNumber {
RandomNumberV2Interface internal randomNumberGenerator;

address[] public participants;
uint256 public lotteryId;
uint256 public lotteryEndTimestamp;

// Event to log lottery results
event LotteryDrawn(uint256 indexed lotteryId, address winner, uint256 randomNumber, uint256 timestamp);

/**
* @notice Initializes the contract with the address of the random number generator.
* @param _randomNumberGenerator The address of the RandomNumberV2Interface contract.
*/
constructor(address _randomNumberGenerator) {
randomNumberGenerator = RandomNumberV2Interface(_randomNumberGenerator);
}

/**
* @notice Enter the lottery.
* Participants can enter the lottery before it ends.
*/
function enterLottery() external {
require(block.timestamp < lotteryEndTimestamp, "Lottery has ended");
participants.push(msg.sender);
}

/**
* @notice Start a new lottery round.
* This resets participants and sets the lottery end time.
* @param duration The duration of the lottery in seconds.
*/
function startLottery(uint256 duration) external {
require(participants.length == 0, "Previous lottery must be concluded first");
lotteryId++;
lotteryEndTimestamp = block.timestamp + duration;
}

/**
* @notice Draw the winner of the lottery.
* Requires the lottery to be over and retrieves a secure random number to select the winner.
*/
function drawLottery() external {
require(block.timestamp >= lotteryEndTimestamp, "Lottery is still ongoing");
require(participants.length > 0, "No participants in the lottery");

// Get the current random number and its properties
(uint256 randomNumber, bool isSecureRandom, uint256 randomTimestamp) = randomNumberGenerator.getRandomNumber();

// Use the random number to select a winner
uint256 winnerIndex = randomNumber % participants.length;
address winner = participants[winnerIndex];

// Emit the lottery result
emit LotteryDrawn(lotteryId, winner, randomNumber, randomTimestamp);

// Reset participants for the next lottery round
delete participants;
}

/**
* @notice Get the current number of participants.
* @return count The number of participants currently in the lottery.
*/
function getParticipantCount() external view returns (uint256 count) {
return participants.length;
}

/**
* @notice Get the current lottery status.
* @return currentLotteryId The current lottery ID.
* @return endTimestamp The timestamp when the lottery ends.
*/
function getLotteryStatus() external view returns (uint256 currentLotteryId, uint256 endTimestamp) {
return (lotteryId, lotteryEndTimestamp);
}
}

Use secure random offchain

To obtain a secure random number offchain, you need two key pieces of information:

  1. RPC Endpoint URL: The RPC Endpoint URL determines which network your code will interact with. You can use a node provider service or point to your own RPC node. A comprehensive list of public and private RPC endpoints for all Flare networks is available on the Network Configuration page.

  2. Contract Address: The address for the RandomNumberV2 contract varies by network. You can obtain this address in two ways:

    • From the Solidity Reference page: Find the RandomNumberV2 address for each network on the Solidity Reference page.

      OR

    • Query the FlareContractRegistry Contract: The FlareContractRegistry contract has the same address across all networks. You can query it to get the RandomNumberV2 contract address. Refer to the specific language guides for examples:

tip

All examples in this guide are available at developer-hub/examples.

This example uses web3.js to retrieve a secure random number on Flare Testnet Coston2.

npm install web3
secure_random.js
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
import { Web3 } from "web3";

// RandomNumberV2 address where the secure RNG is served (Flare Testnet Coston2)
// See https://dev.flare.network/network/solidity-reference
const ADDRESS = "0x5CdF9eAF3EB8b44fB696984a1420B56A7575D250";
const RPC_URL = "https://coston2-api.flare.network/ext/C/rpc";
// ABI for RandomNumberV2 contract
const ABI =
'[{"inputs":[{"internalType":"address","name":"_signingPolicySetter","type":"address"},{"internalType":"uint32","name":"_initialRewardEpochId","type":"uint32"},{"internalType":"uint32","name":"_startingVotingRoundIdForInitialRewardEpochId","type":"uint32"},{"internalType":"bytes32","name":"_initialSigningPolicyHash","type":"bytes32"},{"internalType":"uint8","name":"_randomNumberProtocolId","type":"uint8"},{"internalType":"uint32","name":"_firstVotingRoundStartTs","type":"uint32"},{"internalType":"uint8","name":"_votingEpochDurationSeconds","type":"uint8"},{"internalType":"uint32","name":"_firstRewardEpochStartVotingRoundId","type":"uint32"},{"internalType":"uint16","name":"_rewardEpochDurationInVotingEpochs","type":"uint16"},{"internalType":"uint16","name":"_thresholdIncreaseBIPS","type":"uint16"},{"internalType":"uint32","name":"_messageFinalizationWindowInRewardEpochs","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"protocolId","type":"uint8"},{"indexed":true,"internalType":"uint32","name":"votingRoundId","type":"uint32"},{"indexed":false,"internalType":"bool","name":"isSecureRandom","type":"bool"},{"indexed":false,"internalType":"bytes32","name":"merkleRoot","type":"bytes32"}],"name":"ProtocolMessageRelayed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint24","name":"rewardEpochId","type":"uint24"},{"indexed":false,"internalType":"uint32","name":"startVotingRoundId","type":"uint32"},{"indexed":false,"internalType":"uint16","name":"threshold","type":"uint16"},{"indexed":false,"internalType":"uint256","name":"seed","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"voters","type":"address[]"},{"indexed":false,"internalType":"uint16[]","name":"weights","type":"uint16[]"},{"indexed":false,"internalType":"bytes","name":"signingPolicyBytes","type":"bytes"},{"indexed":false,"internalType":"uint64","name":"timestamp","type":"uint64"}],"name":"SigningPolicyInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"rewardEpochId","type":"uint256"}],"name":"SigningPolicyRelayed","type":"event"},{"inputs":[{"internalType":"uint256","name":"_protocolId","type":"uint256"},{"internalType":"uint256","name":"_votingRoundId","type":"uint256"}],"name":"getConfirmedMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRandomNumber","outputs":[{"internalType":"uint256","name":"_randomNumber","type":"uint256"},{"internalType":"bool","name":"_isSecureRandom","type":"bool"},{"internalType":"uint256","name":"_randomTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timestamp","type":"uint256"}],"name":"getVotingRoundId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastInitializedRewardEpochData","outputs":[{"internalType":"uint32","name":"_lastInitializedRewardEpoch","type":"uint32"},{"internalType":"uint32","name":"_startingVotingRoundIdForLastInitializedRewardEpoch","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"protocolId","type":"uint256"},{"internalType":"uint256","name":"votingRoundId","type":"uint256"}],"name":"merkleRoots","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"relay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint24","name":"rewardEpochId","type":"uint24"},{"internalType":"uint32","name":"startVotingRoundId","type":"uint32"},{"internalType":"uint16","name":"threshold","type":"uint16"},{"internalType":"uint256","name":"seed","type":"uint256"},{"internalType":"address[]","name":"voters","type":"address[]"},{"internalType":"uint16[]","name":"weights","type":"uint16[]"}],"internalType":"struct IIRelay.SigningPolicy","name":"_signingPolicy","type":"tuple"}],"name":"setSigningPolicy","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"signingPolicySetter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"rewardEpochId","type":"uint256"}],"name":"startingVotingRoundIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stateData","outputs":[{"internalType":"uint8","name":"randomNumberProtocolId","type":"uint8"},{"internalType":"uint32","name":"firstVotingRoundStartTs","type":"uint32"},{"internalType":"uint8","name":"votingEpochDurationSeconds","type":"uint8"},{"internalType":"uint32","name":"firstRewardEpochStartVotingRoundId","type":"uint32"},{"internalType":"uint16","name":"rewardEpochDurationInVotingEpochs","type":"uint16"},{"internalType":"uint16","name":"thresholdIncreaseBIPS","type":"uint16"},{"internalType":"uint32","name":"randomVotingRoundId","type":"uint32"},{"internalType":"bool","name":"isSecureRandom","type":"bool"},{"internalType":"uint32","name":"lastInitializedRewardEpoch","type":"uint32"},{"internalType":"bool","name":"noSigningPolicyRelay","type":"bool"},{"internalType":"uint32","name":"messageFinalizationWindowInRewardEpochs","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"rewardEpochId","type":"uint256"}],"name":"toSigningPolicyHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}]';

export async function main() {
// Connect to an RPC node
const w3 = new Web3(RPC_URL);
// Set up contract instance
const randomV2 = new w3.eth.Contract(JSON.parse(ABI), ADDRESS);
// Fetch secure random number
const res = await randomV2.methods.getRandomNumber().call();
// Log results
console.log("Random Number:", res["_randomNumber"]);
console.log("Is secure random:", res["_isSecureRandom"]);
console.log("Timestamp:", res["_randomTimestamp"]);
return res;
}

main();