# 关于智能合约随机数生成

By [0xTang](https://paragraph.com/@0xtang) · 2022-01-12

---

在区块中关于随机数的概念：
-------------

在以太坊的智能合约中，没有提供像其他面向对象编程一样的生成随机数的工具类或方法。其实，所谓的随机数也是伪随机的，没有哪一种语言能够真正的生成随机数。

对于solidity来说通过复杂的算法生成随机数成本太高，同时智能合约又运行在多个节点上，因此，不能使用像时钟时间来生成随机数。

我们在 Solidity 里如何生成随机数呢？真正的答案是你不能，或者最起码，你无法安全地做到这一点。因为区块链的全部内容对所有参与者来说是透明的， 这就让这个问题变得很难，但是我们有一些方案可以让随机数的使用变得更加安全。

1.用 keccak256 来制造随机数。
---------------------

在一笔交易中，这笔交易什么时候，被谁打包到区块中，对用户来说是不可知的，但是一旦被打包到区块中，这些值就是确定的了，因此我们可以利用区块的打包时间block.timestamp、区块的打包难度block.difficulty作为种子生成随机数。0-100随机数生成器代码如下：

![](https://storage.googleapis.com/papyrus_images/dc46aab03a27d9467cbbade559c2651884267456bd358191ea74489a20cfabaa.png)

### 这个方法很容易被不诚实的节点攻击

在以太坊上, 当你在和一个合约上调用函数的时候, 你会把它广播给一个节点或者在网络上的 **_transaction_** 节点们。 网络上的节点将收集很多事务, 试着成为第一个解决计算密集型数学问题的人，作为“工作证明”，然后将“工作证明”(Proof of Work, PoW)和事务一起作为一个 **_block_** 发布在网络上。

一旦一个节点解决了一个PoW, 其他节点就会停止尝试解决这个 PoW, 并验证其他节点的事务列表是有效的，然后接受这个节点转而尝试解决下一个节点。

**这就让我们的随机数函数变得可利用了**

我们假设我们有一个硬币翻转合约——正面你赢双倍钱，反面你输掉所有的钱。假如它使用上面的方法来决定是正面还是反面 (`random >= 50` 算正面, `random < 50` 算反面)。

如果我正运行一个节点，我可以 **只对我自己的节点** 发布一个事务，且不分享它。 我可以运行硬币翻转方法来偷窥我的输赢 — 如果我输了，我就不把这个事务包含进我要解决的下一个区块中去。我可以一直运行这个方法，直到我赢得了硬币翻转并解决了下一个区块，然后获利。

当然， 因为网络上成千上万的以太坊节点都在竞争解决下一个区块，我能成功解决下一个区块的几率非常之低。 这将花费我们巨大的计算资源来开发这个获利方法 — 但是如果奖励异常地高(比如我可以在硬币翻转函数中赢得 1个亿)， 那就很值得去攻击了。

所以尽管这个方法在以太坊上不安全，在实际中，除非我们的随机函数有一大笔钱在上面，你游戏的用户一般是没有足够的资源去攻击的。但是要谨记它是不安全的。

2.利用业务逻辑生成相对安全的随机数
------------------

将业务数据加入到随机数生成器中，可以解决矿工利用随机数生成器攻击Dapp。这里以彩票合约为示例，用户Tjaden Hess（[https://ethereum.stackexchange.com/users/131/tjaden-hess](https://ethereum.stackexchange.com/users/131/tjaden-hess)）在stackoverflow上对彩票合约提出过比较好的解决方案。其核心是使用玩家的地址和所选号码作为随机数生成器的种子。

彩票合约的逻辑是：

*   新一期彩票投注开启，玩家提交以太坊地址和投注号码计算的哈希，之所以提交hash是为了保障在计算随机数（中奖号码）之前，无法预知投注号码
    
*   按照区块数或者参与者达到上限，投注截止
    
*   投注玩家提交自己的投注号码，合约会根据之前玩家提交的hash值进行校验。此时玩家投注的号码已不可改变
    
*   组织者开奖，从投注号码中随机选择中奖号码（取随机数），并将奖金发放给中奖用户
    

3.利用重复哈希加强安全性
-------------

通过对第一种生成的随机数作为数据源重复进行哈希运算，同样可以大大增大矿工的攻击成本，增强安全性。

重复哈希是将哈希函数的一次运行的输出用作下一次运行的输入，从而多次运行哈希函数的行为。如果初始输入值有稍微的变动，最终计算的结果也会有天壤之别。

4.链下生成随机数
---------

链下方式生成随机数供链上使用，主要通过**预言机 oracle**来实现，而预言机又分为中心化预言机和去中心预言机。

### 1、中心化

使用中心化的方式生成随机数其首要前提是要保证随机数的可信性，这里推荐使用random，地址：[https://www.random.org/](https://www.random.org/)

### 2、去中心化

目前有很多oracle服务提供随机数，如

*   randao提供了commit reveral合约和BLS合约两种方式提供随机数，其白皮书详见：[https://randao.org/whitepaper/Randao\_v0.85.pdf](https://randao.org/whitepaper/Randao_v0.85.pdf)
    
*   Niguez随机引擎，使用说明详见：[https://github.com/niguezrandomityengine/guide/blob/master/guide.md](https://github.com/niguezrandomityengine/guide/blob/master/guide.md)
    

利用 oracle 来访问以太坊区块链之外的随机数函数。
----------------------------

这种情况通常是中心化的解决方案，通过一个可信的 [Oracle](https://so.csdn.net/so/search?q=Oracle) 来提供独立的随机数源。智能合约发送请求给独立于区块链系统之外的 Oracle，当 Oracle 监听到链上相关请求后，生成随机数并调用回调函数将结果返回区块链。

这个方案的主要问题式是中心化的解决方案，与基于区块链的分布式精神相悖，另外还有其他的弱点，比如信息在 p2p 网络中传递过程中的延时问题，对于不同等级的应用需求没法提供差异化的服务等。

例：Chainlink最近推出一款革命性的产品，VRF—Verifiable Random Function可验证随机数，给智能合约带来了真正安全的随机数。

### Chainlink VFR的工作流程。

1.  首先，智能合约应用，也就是我们的Dapp，需要先发起一个获取随机数的请求，这个请求需要给定一个合约地址，这个合约称为VRFCoordinator合约。
    
2.  与VRFCoordinator合约所关联的Chainlink链下节点，会（通过椭圆曲线数字签名算法）生成一个随机数，以及一个证明。
    
3.  Chainlink节点将上面生成的随机数和证明发送到VRFCoordinator合约中。
    
4.  VRFCoordinator合约收到随机数和证明后，会对通过证明来验证所生成随机数的合法性。
    
5.  随机数验证成功后，会将随机数发送回用户的智能合约应用
    

整个过程中有两次的交易提交确认的过程，用户合约需要支付LINK给VRF合约作为交易费用。

![](https://storage.googleapis.com/papyrus_images/7ec42da26eecd7d98624507e000c07f3dd8a320f617125328bdfcecd8d0e8942.png)

---

*Originally published on [0xTang](https://paragraph.com/@0xtang/YzrpK8OWsBqWY3zm8vFo)*
