Random number generation (RNG) has always been one of the biggest problems when working with smart contracts. A deterministic virtual machine is incapable of generating ‘true’ randomness. Due to this, RNG needs to be provided as an oracle service.
To fulfill the need for randomness in smart contracts, decentralized pseudorandom RNG has been a common way. One of the most used methods is Chainlink’s VRF or verifiable random function, which provides cryptographically provable random numbers on-chain. It generates a random number off-chain with cryptographic proof that’s used to verify the result.
However, this configuration suffers from the same issues as any other third-party oracle network. Setting up an oracle node that can provide PRNG exposes potential attack vectors like Sybil attacks, but also lacks source transparency and decentralization. For example, one needs to trust the governing entity to select the network participants, which means decentralized PRNG is only as secure and decentralized as the governing entity.
QRNG generates randomness via quantum phenomena. It uses a ‘true’ source of entropy using unique properties of quantum physics to generate true randomness.
There are different methods of implementing QRNG with varying levels of practicality, yet the common point is that the resulting numbers will be truly random because the outcome of a quantum event is theoretically uncertain with well-defined characteristics. Therefore, QRNG is the gold standard for random number generation.
As we already discussed, providing RNG through a third-party oracle network opens space for attack vectors. But first-party oracles (Airnodes) that are directly operated by the QRNG API Providers optimally counter the Sybil attack risk.
API3 QRNG is a public utility offered through the Australian National University (ANU). It is powered by an Airnode hosted by ANU Quantum Random Numbers, meaning that it is a first-party service. Australian National University’s Quantum Optics Division is one of the worlds leading research institutions in the field. The division also operates a REST API, Quantum Random Numbers API, to serve QRNG in Web2.
It is served as a public good and is free of charge (apart from the gas costs), and it provides ‘true’ quantum randomness via an easy-to-use solution when requiring RNG on-chain.
To begin, we need to deploy and sponsor the QrngRequester
with a matching sponsor wallet. The QrngRequester
will be the primary contract that retrieves the random number.
The QrngRequester
submits a request for a random number to AirnodeRrpV0
. Airnode gathers the request from the AirnodeRrpV0
protocol contract, retrieves the random number off-chain, and sends it back to AirnodeRrpV0
. Once received, it performs a callback to the requester with the random number.
You can read more about how API3 QRNG uses the request-response protocol here.
Make sure you have the following installed:
Node.js
yarn/NPM
Also, make sure you’ve already cloned and installed the Airnode Monorepo. If you haven’t, clone the Airnode Monorepo with this command:
$ git clone https://github.com/api3dao/airnode.git .
To install the dependencies, do the following:
$ yarn run bootstrap
To build all the packages, use this command:
$ yarn run build
To compile the QrngRequester contract, we are going to use Remix IDE. It’s an online IDE that allows the developing, deploying, and administering of smart contracts for EVM-compatible blockchains.
//SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";
contract RemixQrngExample is RrpRequesterV0 {
event RequestedUint256(bytes32 indexed requestId);
event ReceivedUint256(bytes32 indexed requestId, uint256 response);
address public airnode;
bytes32 public endpointIdUint256;
address public sponsorWallet;
mapping(bytes32 => bool) public waitingFulfillment;
// These are for Remix demonstration purposes, their use is not practical.
struct LatestRequest {
bytes32 requestId;
uint256 randomNumber;
}
LatestRequest public latestRequest;
constructor(address _airnodeRrp) RrpRequesterV0(_airnodeRrp) {}
// Normally, this function should be protected, as in:
// require(msg.sender == owner, "Sender not owner");
function setRequestParameters(
address _airnode,
bytes32 _endpointIdUint256,
address _sponsorWallet
) external {
airnode = _airnode;
endpointIdUint256 = _endpointIdUint256;
sponsorWallet = _sponsorWallet;
}
function makeRequestUint256() external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256.selector,
""
);
waitingFulfillment[requestId] = true;
latestRequest.requestId = requestId;
latestRequest.randomNumber = 0;
emit RequestedUint256(requestId);
}
function fulfillUint256(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
waitingFulfillment[requestId],
"Request ID not known"
);
waitingFulfillment[requestId] = false;
uint256 qrngUint256 = abi.decode(data, (uint256));
// Do what you want with `qrngUint256` here...
latestRequest.randomNumber = qrngUint256;
emit ReceivedUint256(requestId, qrngUint256);
}
}
The QrngRequester
will have three main functions: setRequestParameters()
, makeRequestUint256()
, and fulfillUint256()
.
setRequestParameters()
takes in airnode
, endpointIdUint256
, sponsorWallet
and sets these parameters.makeRequestUint256()
function calls the airnodeRrp.makeFullRequest()
function of the AirnodeRrpV0.sol protocol contract which adds the request to its storage and returns a requestId
.The makeRequestUint256()
function expects the following parameters to make a valid request.
airnode
(address) and endpointIdUint256
specify the endpoint. Get these from here.sponsorWallet
specifies which wallet will be used to fulfill the request.The callback to the QrngRequester
contains two parameters:
requestId
: First acquired when making the request and passed here as a reference to identify the request for which the response is intended.
data
: In case of a successful response, this is the requested data that has been encoded and contains a timestamp in addition to other response data. Decode it using the function decode()
from the abi
object to get your random number.
Head to Remix IDE, make a contract, and paste it in the QrngRequester code.
Now, hit compile on the right side of the dashboard and compile the smart contract.
We are going to deploy our QrngRequester
to Goerli. Make sure you have enough testnet ETH in your wallet to deploy the contract and fund the sponsorWallet
later. You can get some testnet Goerli here.
Head to deploy, run Transactions, and select the “Injected Provider — MetaMask” option under Environment. Connect your MetaMask. Make sure you’re on Goerli.
The _rrpAddress
is the main airnodeRrpAddress
. The RRP contracts have already been deployed on-chain. You can check for your specific chain here.
Once the _rrpAddress
is populated, click on “Deploy.” Confirm the transaction on your MetaMask and wait for it to deploy the Requester contract.
When your QrngRequester
gets deployed, head to Deploy, run transactions, and click on the dropdown for your Requester under Deployed Contracts.
Now select the setRequestParameters
dropdown to set all the parameters.
Add the following to the corresponding fields for the function.
_airnode
: The airnode address of the desired QRNG service provider. See its value from the ANU Airnode._endpointIdUint256
: The Airnode endpoint ID will return a single random number. See its value from the ANU Airnode._sponsorWallet
: A wallet derived from the requester contract address, the Airnode address, and the Airnode xpub. The wallet is used to pay gas costs to acquire a random number. A sponsor wallet must be derived using the command derive-sponsor-wallet-address from the Admin CLI. Use the value of the sponsor wallet address that the command outputs.
After you’ve set up the Airnode CLI, installed and built all the dependencies and packages, run the following command to derive your _sponsorWallet
:
npx @api3/airnode-admin derive-sponsor-wallet-address \
--airnode-xpub xpub6CUGRUo... \
--airnode-address 0xe1...dF05s \
--sponsor-address 0xF4...dDyu9
npx @api3/airnode-admin derive-sponsor-wallet-address ^
--airnode-xpub xpub6CUGRUo... ^
--airnode-address 0xe1...dF05s ^
--sponsor-address 0xF4...dDyu9
ANU’s airnode-address
and airnode-xpub
can be found
Fund the sponsorWallet
with some test ETH. Click on transact button and confirm the transaction to set the parameters.
To make the request, click on the makeRequestUint256
button to call the function and make a full request.
Now you can head over to https://goerli.etherscan.io/
and check your sponsorWallet
for any new transactions.
You might need to wait for a while as the Airnode calls the fulfill()
function in AirnodeRrpV0.sol
that will in turn call back the requester contract at fulfillAddress
using function fulfillFunctionId
to deliver data
(the random number).
Here, we can see the latest Fulfill
transaction.
Now go back to Remix and click on latestRequest
button to check the response.
If the callback has been successfully completed, the randomNumber
will be present. The value of waitingFulfillment
will be false
.
If you want to learn more about it, check out the QRNG Example Project.
Read more about API3 QRNG.
Also published here.