Complete Example

Implementing randomness with on-chain verification on a contract is extremely simple, and here’s the full example including the previous code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVRFConsumer {
    function requestRandomness(
        bytes32 userProvidedSeed
    ) external returns (uint256);
    function getRequestById(
        uint256 nonce
    ) external view returns (RandomnessRequest memory);
    function getVRFPublicKey() external view returns (address);
}

struct RandomnessRequest {
    address requester;
    uint256 requestBlock;
    uint256 requestId;
    bytes32 userProvidedSeed;
    uint8 status;
    bytes32 randomNumber;
    bytes32 proof;
}

contract SimpleVRFContract {
    uint256 public lastRequestId;
    bytes32 public lastRandomNumber;
    bytes public lastProof;
  
    function requestRandomNumber() external {

        // Perform your game functions here possibly and
        // request your random number at the end

        // Generate a seed using the block timestamp and the caller's address
        bytes32 seed = keccak256(
            abi.encodePacked(block.timestamp, msg.sender)
        );

        // Initialize the interface for the VRFConsumer contract with its address
        IVRFConsumer vrfConsumer = IVRFConsumer(0x7efDa6beA0e3cE66996FA3D82953FF232650ea67);
        
        // Request randomness from the VRFConsumer contract using the generated seed
        lastRequestId = vrfConsumer.requestRandomness(seed);
    }

    function randomnessCallback(
        bytes32 randomNumber,
        uint256 requestId,
        bytes memory proof
    ) external {
        // Ensure the caller is the VRFConsumer contract
        require(
            msg.sender == address(0x7efDa6beA0e3cE66996FA3D82953FF232650ea67),
            "Only the VRFConsumer can call this function"
        );

        // Retrieve the randomness request details from the VRFConsumer contract
        RandomnessRequest memory request = vrfConsumer.getRequestById(requestId);
        
        // Verify the proof to ensure the random number was generated correctly
        require(
            _verifyProof(
                request.userProvidedSeed,
                requestId,
                randomNumber,
                proof
            ),
            "Invalid proof"
        );

        // Check if the requestId matches the lastRequestId stored in the contract
        require(requestId == lastRequestId, "Request ID does not match the last request");

        // Store the random number and proof in the contract state
        lastRandomNumber = randomNumber;
        lastProof = proof;

        // Additional logic using the random number can be added here
    }
    
    function _verifyProof(
        bytes32 userProvidedSeed,
        uint256 requestId,
        bytes32 randomNumber,
        bytes memory proof
    ) internal view returns (bool) {
        // Reconstruct the message that was signed to generate the proof
        bytes32 message = keccak256(abi.encodePacked(userProvidedSeed, requestId));

        // Hash the message according to the Ethereum signed message format
        bytes32 ethSignedMessageHash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", message)
        );

        // Recover the signer address from the proof (signature)
        address recoveredSigner = recoverSigner(ethSignedMessageHash, proof);

        // Retrieve the VRF public key from the VRFConsumer contract
        address vrfPublicKey = vrfConsumer.getVRFPublicKey();

        // Verify that the recovered signer matches the VRF public key
        if (recoveredSigner != vrfPublicKey) {
            return false;
        }

        // Verify that the provided random number matches the hash of the signature
        return randomNumber == keccak256(proof);
    }

    function recoverSigner(
        bytes32 ethSignedMessageHash,
        bytes memory signature
    ) internal pure returns (address) {
        require(signature.length == 65, "Invalid signature length");
        bytes32 r;
        bytes32 s;
        uint8 v;
        assembly {
            // Extract the r, s, and v components from the signature
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }
        // Recover and return the signer address
        return ecrecover(ethSignedMessageHash, v, r, s);
    }
}

With this you now have the basic building blocks to produce your new decentralized application utilizing low latency randomness on chain!

Last updated