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>

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 Relay contract varies by network. You can obtain this address in two ways:

    • From the Solidity Reference page: Find the Relay 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 Relay contract address. Refer to the specific language guides for examples:

tip

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

This example shows two ways, one using web3.js and the other using ethers.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();