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