智能合约中使用更安全的随机数(代码实战篇)
Chainlink 最近推出一款革命性的产品,VRF—Verifiable Random Function 可验证随机数,给智能合约带来了真正安全的随机数。本文我们就来介绍一下如何在智能合约中使用 VRF 吧。
我们先简要介绍一下 Chainlink VFR 的工作流程。
下面我们就通过写一个猜数字的小游戏,来学习如何使用 Chainlink VRF。
首先,新建一个 truffle 项目,安装 Chainlink 开发包
mkdir vrf; cd vrf truffle init npm install @chainlink/contracts --save在 contracts 目录下新建一个合约文件 MyVrfContract.sol。引入 vrf 的库文件:
pragma solidity 0.6.2;VRFConsumerBase 是我们需要继承的基类,使用 Chainlink VRF 的很多方法都在这个合约里定义了,因此我们来简单介绍一下这个合约。import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
abstract contract VRFConsumerBase is VRFRequestIDBase { ... function fulfillRandomness(bytes32 requestId, uint256 randomness) external virtual;上面列出了 VRFConsumerBase 合约的两个基本方法,一个是 requestRandomness 方法,它是用来发起一个 VRF 请求的方法,在调用的时候呢,需要传入三个参数:function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed) public returns (bytes32 requestId) { ... } ... }
function makeRequest(uint256 userProvidedSeed) public returns (bytes32 requestId) { uint256 seed = uint256(keccak256(abi.encode(userProvidedSeed, blockhash(block.number)))); // Hash user seed and block hash return requestRandomness(keyHash, fee, seed); }VRFConsumerBase 合约中的另外一个重要的方法是 fulfillRandomness,这是一个虚函数,需要在继承的类中加以实现。这个函数的主要作用就是接受节点生成的随机数。用户合约在复写这个函数的时候就可以添加一些自己的业务逻辑代码。
另外,VRFConsumerBase 合约中海油一个构造函数,需要调用者提供_vrfCoordinator 合约的地址和所在网络中 LINK token 的地址。
constructor(address _vrfCoordinator, address _link) public { vrfCoordinator = _vrfCoordinator; LINK = LinkTokenInterface(_link); }在我们的用户合约中,需要通过构造函数,来进行一些初始化的工作。初始化的内容包括向基类,也就是 VRFConsumerBase 类的构造函数进行赋值初始化,还包括初始化本合约所用到的一些常量参数。
constructor(address _vrfCoordinator, address _link) VRFConsumerBase(_vrfCoordinator, _link) public { // 其他一些初始化参数 }下面就需要发起 VRF 请求来获取随机数,调用一下基类里的 requestRandomness 方法就可以。
bytes32 internal constant keyHash = 0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205; uint256 internal constant fee = 10 ** 18;在这个请求函数中,我们把用户合约和区块哈希共同作为种子。keyHash 和 fee 就作为常量直接写在合约里,也可以写到 setter 方法中或者构造函数中。function getRandomness(uint256 userProvidedSeed) public returns (bytes32 requestId) { require(LINK.balanceOf(address(this)) > fee, "Not enough LINK - fill contract with faucet"); uint256 seed = uint256(keccak256(abi.encode(userProvidedSeed, blockhash(block.number)))); // Hash user seed and blockhash bytes32 _requestId = requestRandomness(keyHash, fee, seed); return _requestId; }
接下来就是复写接收方法。
uint256 public random; function fulfillRandomness(bytes32 requestId, uint256 randomness) external override { random = randomness.mod(100).add(1); }这里我们就直接赋值给我们的一个公开变量,方便我们查看结果。节点生成的随机数是一个非常大的一个数,我们需要对原始的随机数做一个模运算,以方面我们后面的处理。加一是为了避免得到 0 值。
下面我们贴一下完整的代码:
pragma solidity 0.6.2;好了,这样一个简单的用户合约写完了,我们就可以写根据之前的方法测试一下啦。由于步骤都是类似的,这里就不在赘述了。可以参考这里进行测试。import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
// import this if you are using remix // import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/VRFConsumerBase.sol";
contract MyVRFContract is VRFConsumerBase { constructor(address _vrfCoordinator, address _link) VRFConsumerBase(_vrfCoordinator, _link) public { }
bytes32 internal constant keyHash = 0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205; uint256 internal constant fee = 10 ** 18;
function getRandomness(uint256 userProvidedSeed) public returns (bytes32 requestId) { require(LINK.balanceOf(address(this)) > fee, "Not enough LINK - fill contract with faucet"); uint256 seed = uint256(keccak256(abi.encode(userProvidedSeed, blockhash(block.number)))); bytes32 _requestId = requestRandomness(keyHash, fee, seed); return _requestId; }
uint256 public random; function fulfillRandomness(bytes32 requestId, uint256 randomness) external override { random = randomness.mod(100).add(1); } }
总结,虽然 VRF 内涵了非常复杂深奥的密码学知识,但是得益于 Chainlink 工程师们的良好设计,已经把复杂性极大程度的掩盖了,作为 Dapp 开发者,我们在使用起来非常的容易上手,只需要注意提供好种子就可以了。快来尝试一下吧!
参考文档:
https://blog.chain.link/verifiable-random-functions-vrf-random-number-generation-rng-feature/
https://docs.chain.link/docs/chainlink-vrf
https://docs.chain.link/docs/vrf-contracts
https://learnblockchain.cn/article/1056
https://www.trufflesuite.com/docs/truffle/quickstart