Skip to main content

On-Chain Randomness

Overview

This guide will walk you through using randomness-solidity library to generate a verifiable random number directly within your smart contract. In decentralized applications like NFT mints or on-chain lotteries, unpredictable values are essential to ensure fairness and prevent manipulation. We'll show you how to leverage the dcipher threshold network to achieve this.

💡 Key Benefit: The library leverages the dcipher threshold network to provide publicly verifiable, tamper-resistant, and unpredictable random values.

Core Concepts

The interface is built around a request-and-receive pattern. Your contract requests a random number, and the oracle calls a function back in your contract to deliver the result. The primary contract you will work with is RandomnessReceiverBase, which your contract must inherit from.

Walkthrough

1. Contract Initialization

When inheriting from RandomnessReceiverBase, your contract's constructor must call the base constructor. This crucial step links your contract to the deployed RandomnessSender proxy address for your target network, which is passed as the randomnessSender parameter.

import { RandomnessReceiverBase } from "randomness-solidity/src/RandomnessReceiverBase.sol";

contract DiceRoller is RandomnessReceiverBase {
// ... state variables ...

/// @notice Initializes the contract with the address of the randomness sender
constructor(address randomnessSender) RandomnessReceiverBase(randomnessSender) {}

// ... other functions ...
}

You can find the address of RandomnessSender contract deployed on various networks here

2. Requesting Randomness

RandomnessReceiverBase provides two internal functions for requesting randomness, based on your preferred payment method.

Direct Funding Method

The _requestRandomnessPayInNative(uint32 callbackGasLimit) function initiates a randomness request using the Direct Funding model, where the cost is covered by the msg.value sent with the call. It returns a unique requestID for tracking and the requestPrice for the transaction.

function rollDiceWithDirectFunding(uint32 callbackGasLimit) external payable returns (uint256, uint256) {
// Fund the request via msg.value when calling this function
(uint256 requestID, uint256 requestPrice) = _requestRandomnessPayInNative(callbackGasLimit);

// Store the requestID to verify the callback
requestId = requestID;

return (requestID, requestPrice);
}

Subscription Method

The _requestRandomnessWithSubscription(uint32 callbackGasLimit) function initiates a randomness request using the Subscription model, drawing payment from a pre-funded balance associated with your contract. It returns a unique requestID to identify the request.

function rollDiceWithSubscription(uint32 callbackGasLimit) external returns (uint256) {
// The request is paid for by the contract's subscription balance
uint256 requestID = _requestRandomnessWithSubscription(callbackGasLimit);

// Store the requestID for verification
requestId = requestID;

return requestID;
}

3. Cost Estimation

The calculateRequestPriceNative(uint32 _callbackGasLimit) view function, available on the RandomnessSender contract, is used to estimate the fee required for a direct funding request. It is highly recommended to add a buffer to the returned price to account for network gas fluctuations.

// View function to estimate request cost
calculateRequestPriceNative(uint32 _callbackGasLimit) returns (uint256)

⚠️ Important: Add a buffer to the estimated price to account for gas price fluctuations.

4. Handling Randomness

The onRandomnessReceived(uint64 requestID, bytes32 _randomness) function is the mandatory callback function you must override in your contract to handle the result of your randomness request. The RandomnessSender triggers this function to deliver the secure _randomness, and your implementation must first verify the incoming requestID before using the value.

/**
* @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.
*/
function onRandomnessReceived(uint64 requestID, bytes32 _randomness) internal override {
// Critical security check: ensure this is the response to our request
require(requestId == requestID, "Request ID mismatch");

// Now you can safely use the verified random number
randomness = _randomness;
}

5. On-Chain Verification

For additional security, you can also verify randomness on-chain using the Randomness.sol contract. This is particularly useful when you need to verify randomness in a smart contract that didn't generate it.

verify() Function

The verify() function in Randomness.sol allows you to cryptographically verify that a random value was properly generated by the dcipher network for a specific request.

function verify(
address randomnessContract,
address signatureContract,
bytes calldata signature,
uint256 requestID,
address requester,
string calldata schemeID
) returns (bool)

Parameters:

  • randomnessContract: Address of the contract that generated the randomness
  • signatureContract: Address of the signature contract used
  • signature: The cryptographic signature to verify
  • requestID: The ID of the randomness request
  • requester: Address of the contract that requested the randomness
  • schemeID: The signature scheme identifier (e.g., "BLS")

Example usage:

// In your smart contract
import { Randomness } from "randomness-solidity/src/Randomness.sol";

contract RandomnessVerifier {
Randomness public randomness;

constructor(address _randomness) {
randomness = Randomness(_randomness);
}

function verifyRandomness(
address randomnessContract,
address signatureContract,
bytes calldata signature,
uint256 requestID,
address requester,
string calldata schemeID
) external view returns (bool) {
return randomness.verify(
randomnessContract,
signatureContract,
signature,
requestID,
requester,
schemeID
);
}
}

For a detailed understanding of the on-chain logic, the complete source code is available for review in the Solidity Library GitHub Repository