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.

Quantum Random Number Generation

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.

Australian National University’s QRNG Airnode

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.

Read more about how ANU Generates random numbers in real time by measuring the quantum fluctuations of the vacuum

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.

How Airnode and API3 QRNG works

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.

Coding QrngRequester.sol

Getting started

Make sure you have the following installed:

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

Compiling the Contract

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().

  1. The setRequestParameters() takes in airnode, endpointIdUint256, sponsorWallet and sets these parameters.
  2. The makeRequestUint256() function calls the airnodeRrp.makeFullRequest() function of the AirnodeRrpV0.sol protocol contract which adds the request to its storage and returns a requestId.
  3. The targeted off-chain ANU Airnode gathers the request and performs a callback to the requester with the random number.

Request Parameters

The makeRequestUint256() function expects the following parameters to make a valid request.

Response Parameters

The callback to the QrngRequester contains two parameters:

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.

Deploying the 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 _rrpAddressis 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.

Calling the 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.

After you’ve set up the Airnode CLI, installed and built all the dependencies and packages, run the following command to derive your _sponsorWallet:

Linux

npx @api3/airnode-admin derive-sponsor-wallet-address \
  --airnode-xpub xpub6CUGRUo... \
  --airnode-address 0xe1...dF05s \
  --sponsor-address 0xF4...dDyu9

Windows

npx @api3/airnode-admin derive-sponsor-wallet-address ^
  --airnode-xpub xpub6CUGRUo... ^
  --airnode-address 0xe1...dF05s ^
  --sponsor-address 0xF4...dDyu9

ANU’s airnode-addressand airnode-xpub can be found here.

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.