Solidity中随机数的生成

Solidity中无法原生地完成随机数生成(RNG)。要在区块链上生成一个真正可验证的随机数,智能合约必须将种子发送到像预言机这样的链外资源,而预言机必须将随机数连同可验证的证明一起返回给智能合约,证明随机数是使用种子生成的。随着Chainlink VRF现已 在Ethereum主网上线 ,开发者可以在Solidity中以安全可靠、可验证的方式轻松生成随机数。在这篇技术文章中,我们将向您展示如何使用Chainlink VRF 在Solidity中生成随机数

在您的智能合约中生成安全随机数的例子可以在 Chainlink文档 中找到。这里有一个Remix上在Kovan测试网 生成区块链随机数 的例子,供现在想测试的人使用。只要记得按照 请求和接收方法 ,用 LINK转账到你的智能合约 就可以了。

Chainlink VRF的高级概述

Chainlink VRF (可验证随机函数)是一个为智能合约设计的公平的可验证的随机性来源。Solidity开发人员可以使用它作为防篡改的随机数生成器,为依赖不可预测结果的Ethereum应用构建安全可靠的智能合约。

在Solidity中使用Chainlink VRF生成随机数的第一步是确定一个 种子 。选择一个 难以被影响或预测 的种子极为重要。如果有人能够影响或预测种子,理论上他们可以尝试与执行随机性请求的预言机节点串通,产生一个对自己有利的结果。正因为如此,建议不要使用来自区块链状态的值,如区块高度或区块时间戳。

然后,这个种子会以请求的方式发送到Chainlink预言机。然后,预言机会用给定的种子生成一个伪随机数,并将结果返回给智能合约,一同返回的还有一个加密证明,用来验证随机数是使用种子生成的。这种加密证明是通过 公钥加密技术 创建的,这是区块链技术中广泛使用的技术。重要的是,结果可以被验证,避免矿工或预言机等参与者为了自己的利益而影响随机数的结果。

这是对Chainlink VRF工作方式的高级概述。关于底层技术实现的更多细节可以在我们对 Chainlink VRF的介绍 中找到。然而,作为开发者,除了获取种子,然后向Chainlink预言机创建一个请求之外,你不需要担心任何事情。

创建消费者合约

如何在Solidity智能合约中获得一个随机数,我们应该首先从Chainlink VRFConsumerBase 合约中继承。消费者合约还应该包含存储随机数结果的变量,用于生成随机性的公钥哈希,以及为完成请求而付给预言机的费用。

import "https://raw.githubusercontent.com/smartcontractkit/chainlink/master/evm-contracts/src/v0.6/VRFConsumerBase.sol";

contract RandomNumberConsumer is VRFConsumerBase {
    
    bytes32 internal keyHash;
    uint256 internal fee;
    uint256 public randomResult;
    
}

接下来,在构造函数中,我们应该初始化Chainlink VRF协调器。调用VRFConsumerBase函数,传入VRF协调器的地址和给定环境的Chainlink token的地址作为参数。还需要设置keyHash变量,这是生成随机性的公钥。这些的环境特定值可以在Chainlink VRF文档的 合约地址 部分获得。最后,我们需要设置LINK token的支付金额。对于Kovan测试环境,它是0.1个LINK。

constructor() 
    VRFConsumerBase(
        0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator
        0xa36085F69e2889c224210F603D836748e7dC0088  // LINK Token
    ) public
{
    keyHash = 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4;
    fee = 0.1 * 10 ** 18; // 0.1 LINK
}

接下来,我们重写合同中的两个函数 getRandomNumberfulfillRandomnessgetRandomNumber 函数应该把种子作为输入参数,并且调用VRFConsumerBase中的 requestRandomness 函数,传递keyHash,费用金额和给定的种子作为参数。

/** 
   * Requests randomness from a user-provided seed
   */
function getRandomNumber(uint256 userProvidedSeed) public returns (bytes32 requestId) {
    require(LINK.balanceOf(address(this)) > fee, "Not enough LINK - fill contract with faucet");
    return requestRandomness(keyHash, fee, userProvidedSeed);
}

执行时,这个函数将请求发送到给定的VRF协调器合约,然后建立一个最终种子,并将其发送到该VRF协调器的Chainlink预言机。最后的种子是用以下值的哈希值建立起来的。

  • 用户提供的种子
  • 满足请求的Chainlink预言机的公钥哈希值
  • 请求时的用户nonce
  • 提出请求的合约地址
  • 当前区块号

使用这些额外的值的原因是为了防止合约使用相同的种子不止一次地得到相同的结果。nonce有助于防止合约在同一区块内做多个请求,所以理论上,合约可以在同一区块内使用相同的种子为请求多个随机数,而且他们仍然会为每个请求得到唯一的可验证的随机数。

fulfillRandomness 函数接受随机数响应参数为一个无符号整数,以及请求的ID,然后将给定的随机数存储在合约中。当VRFCoordinator合约接收并验证一个随机数时,这个函数会被调用。关于这两个函数的更多信息可以在 Chainlink VRF文档 中找到。

/**
 * Callback function used by VRF Coordinator
 */
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
    randomResult = randomness;
}

现在,我们在Solidity中拥有了一个完整且可行的随机数生成(RNG)示例,现在可以部署和测试该合约了。

测试随机数生成消费者合约

以上 完整的合约 可以很方便地在Remix中打开、编译,并部署在Kovan网络上。部署好后,一定要给合约转入一些LINK。

一旦合约至少有0.1个LINK的资金,我们就可以调用 getRandomNumber 函数,传入一个数字作为种子。这将把请求和种子一起发送给运行在Chainlink oracle上的VRF协调器。

事务被处理后,需要等待几秒钟,让Chainlink预言机完成对随机数的请求,然后调用我们之前创建的’fulfillRandomness’函数,将随机数返回给我们的消费者合约。

然后我们可以调用 randomResult getter函数来查看Chainlink oracle用给定种子生成的可验证随机数的结果。现在我们有了一个可验证的随机数,它可以在我们的消费者合约和任何其他应用中使用。

验证随机性

现在,我们有一个随机数返回到我们的合约,你可能会想知道,我们如何确定它是由执行请求的Chainlink oracle的给定种子和公钥哈希生成的。当使用Chainlink VRF时,答案是你不需要这样做。验证作为 VRFCoordinator 合约完成请求的一部分,会自动进行。

如果验证失败,那么随机数就不会返回到消费合约,交易也会被还原。因此,使用Chainlink VRF的区块链开发者可以放心,他们通过Chainlink VRF获得的随机数是可验证的随机数。关于验证的底层技术细节,可以参考我们对 Chainlink VRF的技术演练

总结

Chainlink VRF帮助Solidity开发者以安全、可靠和经过验证的方式在智能合约中快速、轻松地生成随机数。

如果您是一名智能合约开发者,并希望利用Chainlink VRF功能,请访问 Chainlink开发者文档 并加入 Discord 上的技术讨论。你可以通过访问 Chainlink网站 和在 TwitterReddit 上关注Chainlink来了解更多关于Chainlink的信息。