如何在智能合约中安全地生成一个真正的随机数?

随机数和区块链一直很难达到“一致”(区块链要求确定性,而随机数正相反)。

原因是:交易被矿工出块后,需要网络上的多个节点来确认才算真实有效。就要求每个节点验证时都必须得出相同的结果。如果函数是随机的(每次运行的结果不一样),则每个节点将得出不同的结果,从而导致交易得不到确认。

到目前为止,已有的方法都不算是真正的随机,或存在操控的可能。

设计的原则[1]

[1]翻译自https://ethereum.stackexchange.com/questions/191/how-can-i-securely-generate-a-random-number-in-my-smart-contract

  • 注意的点

  1. 用户可以做出任何给用户带来优势的决定和行为。 如:
    1. 使用blockhash、时间戳或其他矿工定义的值。 要明确一点,矿工可以有选择的是否发布一个块,所以他们有机会选择有利的区块。
    2. 任何用户提交的随机数。 即使用户预先提交了一个号码,他们也可以选择是否透露号码。
  2. 一切合约上的数据都是公开的
    1. 这意味着,直到进入抽奖后已经关闭之前,随机号码都不应该被生成。
  3. EVM不会快过物理计算机
    1. 合约生成的任何数字可以在该块结束之前知道。 在生成数字和使用数字之间留出时间。
  • 一个技术方案

完全去中心的彩票方案(也许扩展不好)

  1. 赌场为一个随机数字预留了奖励

  2. 每个用户生成自己的秘密随机数N

  3. 用户计算N和地址的Hash :bytes32 hash = sha3(N,msg.sender)

    注意: 2 和 3 应该在离线的安全环境下执行

  4. 发送上一步生产的 hash 给合约(尽管可能hash数据量比N大)

  5. 其他的用户继续按同样的方法提交各自的hash, 知道回合结束。

    需要所有的提交完成之后,才进行开奖环节。

  6. 每个用户向合约提交之前生成的随机数N。

  7. 合约根据hash值验证随机数N,使用同样的方法sha3(N,msg.sender),无法通过验证的N,可以没收罚金。

  8. 可以考虑所有有效的N在一起生成一个最终的随机数。

  9. 用这个随机数来决定谁可以获奖。

使用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] 修饰符以实现重载,在实现中,使用reqIdrandomNumber 来保存接收变量的值。

第 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;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智能合约是由代码编写的自动执行合约,通常在区块链技术使用。与传统的合约不同,智能合约可以自动执行、验证和执行合约条款,无需第三方干预。然而,由于智能合约是由代码编写的,因此它们可能存在一些安全漏洞,这些漏洞可能会导致合约执行不当或被黑客攻击。 以下是一些常见的智能合约安全漏洞: 1. 重入攻击:重入攻击是一种攻击,攻击者利用合约的漏洞,使其可以重复执行一个函数,从而实现对合约的攻击。攻击者可以在合约执行期间多次调用函数,从而获取更多的资产。 2. 溢出错误:当智能合约处理某些数值时,如果没有正确的检查,可能会发生溢出错误。攻击者可以利用这种漏洞来篡改合约的状态或窃取资产。 3. 未授权的访问:如果智能合约的某些函数未正确实现访问控制,攻击者可能会访问他们不应该访问的功能。这可能会导致资产丢失或合约被篡改。 4. 错误的随机数生成:在智能合约随机数很重要,因为它们可以用来确保合约的安全性。如果随机数生成不正确,攻击者可能会利用这种弱点来攻击合约。 5. 不正确的代码:当智能合约的代码有错误时,可能会引发安全漏洞。攻击者可能会利用这些漏洞来对合约进行攻击或篡改。 这些是智能合约常见的安全漏洞,因此在编写智能合约时,必须严格遵循安全最佳实践,以确保合约的安全性。同时,定期进行安全审核和测试也是非常重要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值