如何在智能合约中安全地生成一个真正的随机数?
Posted 云灬沙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在智能合约中安全地生成一个真正的随机数?相关的知识,希望对你有一定的参考价值。
随机数和区块链一直很难达到“一致”(区块链要求确定性,而随机数正相反)。
原因是:交易被矿工出块后,需要网络上的多个节点来确认才算真实有效。就要求每个节点验证时都必须得出相同的结果。如果函数是随机的(每次运行的结果不一样),则每个节点将得出不同的结果,从而导致交易得不到确认。
到目前为止,已有的方法都不算是真正的随机,或存在操控的可能。
设计的原则[1]
[1]翻译自https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract
- 用户可以做出任何给用户带来优势的决定和行为。 如:
- 使用blockhash、时间戳或其他矿工定义的值。 要明确一点,矿工可以有选择的是否发布一个块,所以他们有机会选择有利的区块。
- 任何用户提交的随机数。 即使用户预先提交了一个号码,他们也可以选择是否透露号码。
- 一切合约上的数据都是公开的。
- 这意味着,直到进入抽奖后已经关闭之前,随机号码都不应该被生成。
- EVM不会快过物理计算机。
- 合约生成的任何数字可以在该块结束之前知道。 在生成数字和使用数字之间留出时间。
完全去中心的彩票方案(也许扩展不好):
-
赌场为一个随机数字预留了奖励
-
每个用户生成自己的秘密随机数N
-
用户计算N和地址的Hash :bytes32 hash = sha3(N,msg.sender)
注意: 2 和 3 应该在离线的安全环境下执行
-
发送上一步生产的 hash 给合约(尽管可能hash数据量比N大)
-
其他的用户继续按同样的方法提交各自的hash, 知道回合结束。
需要所有的提交完成之后,才进行开奖环节。
-
每个用户向合约提交之前生成的随机数N。
-
合约根据hash值验证随机数N,使用同样的方法sha3(N,msg.sender),无法通过验证的N,可以没收罚金。
-
可以考虑所有有效的N在一起生成一个最终的随机数。
-
用这个随机数来决定谁可以获奖。
使用Chainlink VRF在以太坊上生成随机数[2]
[2]摘自https://cloud.tencent.com/developer/article/1634665
VRF 事件发生的顺序:
1.你的智能合约通过交易向VRF请求一个随机数。
2.VRF会生成该随机数字并进行验证。
3.VRF准备响应1 的请求。
4.VRF通过另一笔交易将随机数字发送回你的智能合约。
如何实现随机性
1.创建一个名为RandomGenerator
的新合约,在合约里调用VRF并接收结果。
引入 Chainlink提供的VRFConsumerBase
的合约,这是一个抽象合约,它定义了一个获取和消耗VRF的最少实现(后面也会列出VRFConsumerBase
的代码),我们定义“ RandomGenerator.sol”文件开头:
pragma solidity ^0.6.2;
import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/VRFConsumerBase.sol";
contract RandomGenerator is VRFConsumerBase {
constructor(address _vrfCoordinator, address _link) VRFConsumerBase(_vrfCoordinator, _link) public {
}
}
VRFConsumerBase
的合约的源码如下:
pragma solidity 0.6.2;
import "./vendor/SafeMath.sol";
import "./interfaces/LinkTokenInterface.sol";
import "./VRFRequestIDBase.sol";
abstract contract VRFConsumerBase is VRFRequestIDBase {
using SafeMath for uint256;
function fulfillRandomness(bytes32 requestId, uint256 randomness)
external virtual;
function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed)
public returns (bytes32 requestId)
{
LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, _seed));
// This is the seed actually passed to the VRF in VRFCoordinator
uint256 vRFSeed = makeVRFInputSeed(_keyHash, _seed, address(this), nonces[_keyHash]);
// nonces[_keyHash] must stay in sync with
// VRFCoordinator.nonces[_keyHash][this], which was incremented by the above
// successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest)
nonces[_keyHash] = nonces[_keyHash].add(1);
return makeRequestId(_keyHash, vRFSeed);
}
LinkTokenInterface internal LINK;
address internal vrfCoordinator;
// Nonces for each VRF key from which randomness has been requested.
//
// Must stay in sync with VRFCoordinator[_keyHash][this]
mapping(bytes32 /* keyHash */ => uint256 /* nonce */) public nonces;
constructor(address _vrfCoordinator, address _link) public {
vrfCoordinator = _vrfCoordinator;
LINK = LinkTokenInterface(_link);
}
}
VRFConsumerBase
仍在后期测试中,因此还没有产品软件包对外提供。这就是为什么使用Github的HTTP URL进行导入的原因。
VRFConsumerBase
抽象合约有两个参数,分别代表协调器(coordinator)和LINK ERC20 代币合约的地址。
第 2 步: 重载函数
VRFConsumerBase
中有两个对VRF流程至关重要的函数。
第一个是 requestRandomness
,这个函数已经实现了,我们不需要重载。这个函数是用来对VRF进行初始请求调用。
另一个是 fulfillRandomness
, 这是VRF在生成数字后,用来回调的函数。我们需要重载它,以便在获取随机数后执行相应的操作。
在我们合约的实现里,仅仅是把随机数存储在一个名为randomNumber
的状态变量中,以便我们可以在结束时查询它。代码像这样:
pragma solidity ^0.6.2;
import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/VRFConsumerBase.sol";
contract RandomGenerator is VRFConsumerBase {
bytes32 public reqId;
uint256 public randomNumber;
constructor(address _vrfCoordinator, address _link) VRFConsumerBase(_vrfCoordinator, _link) public {
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) external override {
reqId = requestId;
randomNumber = randomness;
}
}
我们在fulfillRandomness
函数上添加了override[6] 修饰符以实现重载,在实现中,使用reqId
和 randomNumber
来保存接收变量的值。
第 3 步: 生成随机数
正如在前面 第1步提到的,函数调用需要传递一些地址和其他值作为参数。在部署智能合约并调用构造函数时,它需要VRF协调器(coordinator)合约地址和网络上LINK 代币合约地址。在Ropsten测试网上,合约地址如下:
VRF coordinator: 0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb
LINK 代币: 0x20fE562d797A42Dcb3399062AE9546cd06f63280
当调用 requestRandomness
函数时,我们需要传递几个参数:生成随机数的key hash,生成随机数的费用fee(使用LINK代币)和生成随机性的种子seed(最后一个由我们提供)。requestRandomness函数签名如下:
function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed) public returns (bytes32 requestId)
在 Ropsten 网络上,参数值如下:
•Key hash值: 0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205
•费用 Fee (1 LINK): 1000000000000000000
•种子 Seed: [我们想要的任意值]
因此我们的调用代码如下
// 设置ropsten key hash
bytes32 keyHash = "0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205";// // 设置 ropsten LINK 费用
fee = 1000000000000000000;
// 设置种子
seed = 123456789;
// 请求随机数
bytes32 reqId = rand.requestRandomness(keyHash, fee, seed);
当结果返回时,随机值将存储并且可以通过以下方法获取:
rand.randomNumber;
以上是关于如何在智能合约中安全地生成一个真正的随机数?的主要内容,如果未能解决你的问题,请参考以下文章
智能合约实战 solidity 语法学习 12 [ virtual 重写override 抽象abstract 接口interface 库library 销毁selfdestruct] 附代码(代码片