Skip to main content

Developer Guide for ZKVRF Integration

This guide provides a comprehensive approach to integrating the ZKVRF (Zero-Knowledge Verifiable Random Function) system into your smart contract. We'll detail the steps to request and receive verifiable random numbers securely and efficiently.

1. Introduction

ZKVRF provides a mechanism to generate verifiable random numbers on the blockchain. This is crucial for applications that require randomness, such as lotteries, gaming, and various decentralized applications (dApps).

2. Prerequisites

  • Solidity Version: Ensure you are using Solidity version 0.8.24 or later.
  • Contracts: You will need the following contracts:
    • ZKVRFConsumerBase
    • ZKVRFCoordinatorInterface

3. Create and deploy a ZKVRF compatible contract

For this example, use the zkc-vrf-example.sol sample contract. This contract imports the following dependencies:

ZKVRFConsumerBase.sol

ZKVRFCoordinatorInterface.sol

Build and deploy the contract(on Sepolia):

  1. Open the zkc-vrf-example.sol contract in Remix
  2. On the Compile tab in Remix, compile the zkc-vrf-example contract.
  3. Configure your deployment. On the Deploy tab in Remix, select the Injected Web3 Environment(Sepolia) and select the zkc-vrf-example contract from the contract list.
  4. Click the Deploy button to deploy your contract onchain. MetaMask opens and asks you to confirm the transaction.

4. Request random values

The deployed contract requests random values from ZKVRF, receives those values, builds a struct RequestStatus containing them, and stores the struct in a mapping s_requests. Run the requestRandomWords() function on your contract to start the request.

    1. Return to Remix and view your deployed contract functions in the Deployed Contracts list.
    1. Click the requestRandomWords() function to send the request for random values to ZKVRF. Function requestRandomWords() requires two parameters:

      • Seed: A random input for ZKVRF.
      • Grouphash: The signing group hash which could be found at https://beta.zkvrf.xyz/

      image

      In this example, seed is 9 and group_hash is 106651045272248281329034530416119353156388698457438994187042521815273962483711

    1. MetaMask opens and asks you to confirm the transaction.
    1. After you approve the transaction, ZKVRF processes your request. ZKVRF fulfills the request and returns the random values to your contract in a callback to the fulfillRandomWords() function. At this point, a new key requestId is added to the mapping s_requests. Depending on current testnet conditions, it might take a few minutes for the callback to return the requested random values to your contract.
    1. After the oracle returns the random values to your contract, the mapping s_requests is updated. The received random values are stored in s_requests[_requestId].randomWords.
    1. Call getRequestStatus() and specify the requestId to display the random words. image

5. Analyzing the example contract

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "https://github.com/zkcrossteam/zkc-vrf/blob/main/contracts/ZKVRFConsumerBase.sol";
import "https://github.com/zkcrossteam/zkc-vrf/blob/main/contracts/ZKVRFCoordinatorInterface.sol";

contract zkvrf_example is ZKVRFConsumerBase {
event RequestFulfilled(uint256 requestId, uint256 seed, uint256 randomWord);

struct RequestStatus {
uint256 seed;
uint256 randomWord;
bool fulfilled; // whether the request has been successfully fulfilled
}
mapping(uint256 => RequestStatus) s_requests; /* requestId --> requestStatus */
uint256[] public requestIds;

ZKVRFCoordinatorInterface _vrf;
address private constant DEFAULT_ZKVRF_COORDINATOR = 0x2eE398996503D61fc47a6621cF8aB1c9Bb243f1a; //Sepolia

constructor() ZKVRFConsumerBase(DEFAULT_ZKVRF_COORDINATOR) {
_vrf = ZKVRFCoordinatorInterface(DEFAULT_ZKVRF_COORDINATOR);
}

function requestRandomWords(uint256 seed, uint256 group_hash)
external
returns (uint256 requestId)
{
requestId = _vrf.requestRandomWords(seed, group_hash, address(this));
s_requests[requestId] = RequestStatus({
seed: seed,
randomWord: uint256(0),
fulfilled: false
});
requestIds.push(requestId);
return requestId;
}

function fulfillRandomWords(uint256 _requestId, uint256 _seed, uint256 _randomWord) internal override {
require(s_requests[_requestId].seed > 0, "request not found");
s_requests[_requestId].fulfilled = true;
s_requests[_requestId].randomWord = _randomWord;
emit RequestFulfilled(_requestId, _seed, _randomWord);
}

function getRequestStatus(uint256 _requestId) external view returns (uint256 seed, bool fulfilled, uint256 randomWord) {
require(s_requests[_requestId].seed > 0, "request not found");
RequestStatus memory request = s_requests[_requestId];
return (request.seed, request.fulfilled, request.randomWord);
}
}

The contract includes the following functions:

  • requestRandomWords(): Takes your specified parameters and submits the request to the ZKVRF Wrapper contract.
  • fulfillRandomWords(): Receives random values and stores them with your contract.
  • getRequestStatus(): Retrive request details for a given _requestId.

6. Best Practices

6.1 Importing

Start by importing the ZKVRFConsumerBase and ZKVRFCoordinatorInterface contracts in your Solidity file.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "https://github.com/zkcrossteam/zkc-vrf/blob/main/contracts/ZKVRFConsumerBase.sol";
import "https://github.com/zkcrossteam/zkc-vrf/blob/main/contracts/ZKVRFCoordinatorInterface.sol";

6.2 Defining Your Contract

Create your contract that inherits from ZKVRFConsumerBase. Initialize the ZKVRFCoordinatorInterface in the constructor from DEFAULT_ZKVRF_COORDINATOR.

address private constant DEFAULT_ZKVRF_COORDINATOR = 0x2eE398996503D61fc47a6621cF8aB1c9Bb243f1a; //Sepolia

This example is adapted for Sepolia testnet but you can change the configuration and make it run for any supported network.

contract zkvrf_example is ZKVRFConsumerBase {
...
ZKVRFCoordinatorInterface _vrf;
address private constant DEFAULT_ZKVRF_COORDINATOR = 0x2eE398996503D61fc47a6621cF8aB1c9Bb243f1a; //Sepolia

constructor() ZKVRFConsumerBase(DEFAULT_ZKVRF_COORDINATOR) {
_vrf = ZKVRFCoordinatorInterface(DEFAULT_ZKVRF_COORDINATOR);
}
...

6.3 Requesting Random Numbers

Create a function to request random numbers. This function will take a seed and a group hash as parameters and call the requestRandomWords function from the ZKVRFCoordinatorInterface.

  • Seed: A random input for ZKVRF.
  • Grouphash: The signing group hash which could be found at https://beta.zkvrf.xyz/
    function requestRandomWords(uint256 seed, uint256 group_hash)
external returns (uint256 requestId)
{
requestId = _vrf.requestRandomWords(seed, group_hash, address(this));
...
return requestId;
}

6.4 Handling Random Number Fulfillment

Override the fulfillRandomWords function to handle the random numbers received. You can use the received _randomWord as your want.

    function fulfillRandomWords(uint256 _requestId, uint256 _seed, uint256 _randomWord) internal override {
...
emit RequestFulfilled(_requestId, _seed, _randomWord);
...
}

7. Conclusion

By following this guide, you can integrate the ZKVRF system into your smart contract to request and receive verifiable random numbers securely. This integration is useful for applications requiring randomness, such as lotteries, games, and other decentralized applications.