Verifiable Randomness
Introduction
Verifiable Randomness in the context of on-chain applications refers to random values that are both unpredictable and provably generated in a way that anyone (including smart contracts) can cryptographically verify without trusting a central party.
Quickstart
This quickstart focuses on an example of how to make randomness requests in a smart contract using the Randamu Verifiable Randomness Solidity library.
Installation
Solidity
We recommend foundry for managing your Solidity dependencies.
Install the latest verifiable randomness Solidity library by running forge install randa-mu/randomness-solidity
.
If you're using Hardhat, you can also install it by running npm install randomness-solidity
.
The complete Solidity library source code is available on GitHub.
Typescript
A TypeScript-compatible library is also available for interacting with verifiable randomness contracts off-chain.
Install the latest verifiable randomness library by running npm install @randamu/randomness
.
It includes smart contract TypeScript bindings out of the box, along with functionality for making requests, checking request statuses, and verifying the returned random values.
Write a simple Solidity smart contract that requests and receives a random number
In this quickstart guide, we will implement a simple dice roll contract following the steps below.
-
Import the library
Start by importing the
RandomnessReceiverBase.sol
abstract contract into your smart contract. This contract provides the interface for making randomness requests and handling callbacks:// Import the abstract RandomnessReceiverBase contract for creating randomness requests and handling randomness callbacks
import { RandomnessReceiverBase } from "randomness-solidity/src/RandomnessReceiverBase.sol"; -
Extend the
RandomnessReceiverBase
contractTo use the library, your contract must inherit from
RandomnessReceiverBase
and specify the deployedRandomnessSender
contract address for your desired network in the constructor, e.g., Base Sepolia testnet. All the randomness requests made from your contract will be sent to the dcipher threshold network via theRandomnessSender
contract.A full example randomness receiver contract
MockRandomnessReceiver
can be accessed on GitHub.contract DiceRoller is RandomnessReceiverBase {
/// @notice Stores the latest received randomness value
bytes32 public randomness;
/// @notice Stores the request ID of the latest randomness request
uint256 public requestId;
/// @notice Initializes the contract with the address of the randomness sender
/// @param randomnessSender The address of the randomness provider
constructor(address randomnessSender) RandomnessReceiverBase(randomnessSender) {}
...
} -
Request Randomness
Requests can be paid for in two ways, either direct funding or subscription.
For both payment options, the RandomnessReceiverBase contract provides two functions for making requests:
- Direct Funding:
_requestRandomnessPayInNative(uint32 callbackGasLimit)
- Subscription:
_requestRandomnessWithSubscription(uint32 callbackGasLimit)
.
noteSee the billing guide for more information on both request funding options (direct funding and subscription).
To estimate the price of a randomness request, you can use the
calculateRequestPriceNative()
function in theRandomnessSender
contract (ensure that a buffer is added to the returned estimate to accomodate network gas price fluctuations between blocks):function calculateRequestPriceNative(uint32 _callbackGasLimit)
public
view
override (FeeCollector, IRandomnessSender)
returns (uint256)
{
return _calculateRequestPriceNative(_callbackGasLimit, tx.gasprice);
}In this example, we will be showing how to use both funding options to roll a dice. Both functions return the request id (and request price for the direct funding option).
To recap, using the internal
_requestRandomnessPayInNative()
and_requestRandomnessWithSubscription()
functions derived fromRandomnessReceiverBase
, we can send randomness requests to the dcipher network using the direct funding and subscription payment options respectively. These requests are forwarded through the deployedRandomnessSender
contract on a supported network.When calling
_requestRandomnessPayInNative()
, we need to fund the request viamsg.value
which should cover the estimated price for the request. It is advised to add a buffer to cover fluctuations in network gas price to avoid delays in processing the request.Both functions return a
requestId
, which should be stored and can be used to verify the response when the randomness is delivered through a callback fromRandomnessSender
. For the subscription payment option, the_requestRandomnessWithSubscription()
function, uses thesubscriptionId
variable set inRandomnessReceiverBase
.function rollDiceWithDirectFunding(uint32 callbackGasLimit) external payable returns (uint256, uint256) {
// create randomness request using direct funding
(uint256 requestID, uint256 requestPrice) = _requestRandomnessPayInNative(callbackGasLimit);
// store request id
requestId = requestID;
return (requestID, requestPrice);
}
function rollDiceWithSubscription(uint32 callbackGasLimit) external payable returns (uint256) {
// create randomness request using subscription
uint256 requestID = _requestRandomnessWithSubscription(callbackGasLimit);
// store request id
requestId = requestID;
return requestID;
} - Direct Funding:
-
Handle the Randomness Callback
When the dcipher network fulfills the request, the
onRandomnessReceived()
callback function will be triggered with the generated random value which is automatically verified on-chain with the specified threshold signature scheme.noteIt is mandatory to override this function to handle the response with custom logic, e.g., using the random value as a seed in an array shuffle, selecting a uint from a range of values, picking random indices from an array or deriving other uint types such as uint8. In this example, we are simply storing the random value in the contract as
randomness
when we receive it./**
* @dev Callback function that is called when randomness is received.
* @param requestID The ID of the randomness request that was made.
* @param _randomness The random value received.
*
* This function verifies that the received `requestID` matches the one that
* was previously stored. If they match, it updates the `randomness` state variable
* with the newly received random value.
*/
function onRandomnessReceived(uint256 requestID, bytes32 _randomness) internal override {
require(requestId == requestID, "Request ID mismatch");
randomness = _randomness;
}
Sharing Subscription Accounts
To share a subscription account, the smart contract that owns the subscription must call the updateSubscription()
function in RandomnessSender
to approve other contracts to use its created subscriptionId
. For more information on billing, please see the billing guide.
/// @notice Adds a list of consumer addresses to the Randamu subscription.
/// @dev Requires the subscription ID to be set before calling.
/// @param consumers An array of addresses to be added as authorized consumers.
function updateSubscription(address[] calldata consumers) external onlyOwner {
require(subscriptionId != 0, "subID not set");
for (uint256 i = 0; i < consumers.length; i++) {
randomnessSender.addConsumer(subscriptionId, consumers[i]);
}
After calling updateSubscription
all approved contracts can then call the setSubId
function and start making subscription conditional encryption requests using the shared (funded) subscription account.
/// @notice Sets the Randamu subscription ID used for conditional encryption oracle services.
/// @dev Only callable by the contract owner.
/// @param subId The new subscription ID to be set.
function setSubId(uint256 subId) external onlyOwner {
subscriptionId = subId;
emit NewSubscriptionId(subId);
}
Please note that all approved contracts must also implement RandomnessReceiverBase.sol
.
Full Example Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import { RandomnessReceiverBase } from "randomness-solidity/src/RandomnessReceiverBase.sol";
/// @title MockRandomnessReceiver contract
/// @author Randamu
/// @notice A contract that requests and consumes randomness
contract MockRandomnessReceiver is RandomnessReceiverBase {
/// @notice Stores the latest received randomness value
bytes32 public randomness;
/// @notice Stores the request ID of the latest randomness request
uint256 public requestId;
/// @notice Initializes the contract with the address of the randomness sender
/// @param randomnessSender The address of the randomness provider
constructor(address randomnessSender) RandomnessReceiverBase(randomnessSender) {}
/// @notice Requests randomness using the direct funding option
/// @dev Calls `_requestRandomnessPayInNative` to get a random value, updating `requestId` with the request ID
function rollDiceWithDirectFunding(uint32 callbackGasLimit) external payable returns (uint256, uint256) {
// create randomness request
(uint256 requestID, uint256 requestPrice) = _requestRandomnessPayInNative(callbackGasLimit);
// store request id
requestId = requestID;
return (requestID, requestPrice);
}
/// @notice Requests randomness using the subscription option
/// @dev Calls `_requestRandomnessWithSubscription` to get a random value, updating `requestId` with the request ID
function rollDiceWithSubscription(uint32 callbackGasLimit) external payable returns (uint256) {
// create randomness request
uint256 requestID = _requestRandomnessWithSubscription(callbackGasLimit);
// store request id
requestId = requestID;
return requestID;
}
/// @notice Callback function that processes received randomness
/// @dev Ensures the received request ID matches the stored one before updating state
/// @param requestID The ID of the randomness request
/// @param _randomness The random value received from the oracle
function onRandomnessReceived(uint256 requestID, bytes32 _randomness) internal override {
require(requestId == requestID, "Request ID mismatch");
randomness = _randomness;
}
}
API Documentation
The following functions are available in the RandomnessReceiverBase
and RandomnessSender
contract.
RandomnessReceiverBase.sol
Function | Return | Description |
---|---|---|
_requestRandomnessPayInNative(uint32 callbackGasLimit) | uint256 requestID, uint256 requestPrice | Requests the generation of a random value from the dcipher network |
_requestRandomnessWithSubscription(uint32 callbackGasLimit) | uint256 requestID | Requests the generation of a random value from the dcipher network |
onRandomnessReceived(uint256 requestID, bytes32 randomness) | n/a | Callback function to be implemented by the inheriting contract. Called when the randomness is delivered. |
RandomnessSender.sol
Function | Return | Description |
---|---|---|
isInFlight(uint256 requestID) | bool | Returns true if the specified randomness request is still pending. |
getRequest(uint256 requestID) | TypesLib.RandomnessRequest | Returns the details of the randomness request associated with the given request ID. The RandomnessRequest object (struct) contains the following variables for each request: uint256 nonce; address callback; |
getAllRequests() | TypesLib.RandomnessRequest[] | Retrieves a list of all randomness requests submitted to the contract. |