DApp中“要命”的随机:合约随机数安全问题解析

公链上目前存在为数众多的竞猜类DApp。竞猜类游戏的逻辑就是生成无法预测的随机数,玩家公平竞猜。安全的随机数生成器可以保证所有玩家在游戏中的竞猜是公平的,玩家的获胜概率也是相同的。但是近期多个EOS和以太坊平台DApp因为随机数生成漏洞被黑客攻击。如EOS的Luckyos、EOS.Win、EosDice,以太坊上的Fomo3D。

针对这种情况,北京链安的安全专家Hardman就公链DApp中随机数安全做了相关解析,让我们看看如何安全的使用随机数。

1  随机数和发生器介绍

在以区块链为代表的去中心化公共账本提法之前,数据都是私有的,中心化的。因此在此之前的随机数发生器也都是中心化的,其中的代表方案有 NIST Randomness Beacon 以及 random.org等。

1.1  NIST随机数发生器

NISTRandomness Beacon4用于实现公共随机数源。它使用两个独立、商用的随机数发生器,每个发生器配备一个独立的物理熵源和 SP800-90 认可组件。NIST 随机数生成器旨在提供不可预测、自主、一致的随机数源。不可预测是指:任何算法都无法预测该生成器将会给出的随机数。自主是指:能够抵抗不相关者介入或阻止分发随机数的过程。一致是指:一组用户访问该服务能够确实地获得相同的随机数。

1.2  random.org

random.org 使用大气噪音生成随机数。即先用录音设备获得大气中的声波,再检测其细微变化作为生成随机数的熵源。random.org还提到了两类物理现象作为熵源的比较:量子现象和混沌现象。量子现象作为随机数源是利用了在原子尺度下粒子的行为具有随机性,而且其本质还未被人类发现,因此可以将其看做一个具有良好不确定性的熵源。混沌现象是指在混沌系统中,初始量的微小差异会导致未来的发展截然不同,因此除非获得初始时刻的全部准确信息,则无法预测未来的发展趋势。实际上应用这两种方法均能实现不可预测的随机数发生器,random.org使用大气噪音生成随机数的方法就属于后者。

2  为什么公链上不支持直接获取随机数

开发合约,获取随机数是一个很大的问题,以太坊和EOS都不支持随机数的生成,这是为什么?

假设合约里可以生成随机数,那么该合约执行结果就是完全具备随机可能了,其他节点完全无法直接验证该执行结果是否合法。不诚实的节点直接可以自行随便指定一个数字,或者不停重新计算,直到该节点得到一个对自己有利的数字,再打包到区块中。因为字节码在不同的节点的虚拟机验证执行过程上,得到的随机数都是不同的,虚拟机完全无法的确定该结果是否合法。

这种结果很显然在设计上是不被接受的。公链节点通过打包区块来实现数据存储,数据需要非常高的确定性,因此在公链上是没办法得到一个随机数的。

3  以太坊和EOS PRNG漏洞归总

由于非常多的场景需要使用到随机数。竞猜、赌博、随机分配任务等。在官方不提供直接获取随机数背景下,公链上的开发者自行根据公链上能取到的各种变量,自行开发伪随机数生成器(pseudo-random number generator),简称 `PRNG`。有漏洞的PRNG就为以后的安全问题埋下了隐患。这些有漏洞的PRNG主要依赖于:区块变量、过往区块hash。

3.1  以太坊中易引发PRNG漏洞的变量

3.1.1  区块变量

(1)block.coinbase 表示当前区块的矿工地址

(2)block.difficulty 表示当前区块的挖掘难度

(3)block.gaslimit 区块内交易的最大限制燃气消耗量

(4)block.number 表示当前区块高度

(5)block.timestamp 表示当前区块挖掘时间

以上所有的区块变量都可以被矿工操纵,所以都不能用来做信息熵源。因为这些区块变量在同一区块上是共用的。攻击者通过其恶意合约调用受害者合约,那么此交易打包在同一区块中,其区块变量是一样的。

3.1.2  过往区块hash

(1)block.blockhash(block.number)

通过`block.number` 变量可以获取当前区块区块高度。但是还没执行时,这个“当前区块”是一个未来区块,即只有当一个矿工拾取一个执行合约代码的交易时,这个未来区块才变为当前区块,所以合约才可以可靠地获取此区块的区块哈希。而一些合约曲解了 `block.blockhash(block.number)` 的含义,误认为当前区块的区块哈希在运行过程中是已知的,并将之做为熵源,其实此时在以太坊虚拟机(EVM)中,区块哈希恒为 0。

(2)block.blockhash(block.number-1)

有一些合约则基于负一高度区块的区块哈希来产生伪随机数,这也是有缺陷的。攻击合约只要以相同代码执行,即可以产生到同样的伪随机数。 

3.2  EOS中易引发PRNG漏洞的变量

(1)transacation_id(交易id)

其中transacation_id,是一笔交易的唯一id,以sha256计算后的哈希形式体现。交易id与用户发起的 transacation有唯一关联, 是个可计算并且确定的值。通过read_transaction 拿到当前 transacation的数据,sha256 可以得到。

(2)合约余额amount(合约当前的余额)

合约金额拉长时间来看是动态的,但是大多情况下变动的频率不是非常快,更不是每分每秒都在变,在固定的一个短的时间段内是一个确定的定值。

(3)current_time(当前时间)

目前来看如果是实时开奖,攻击合约交易和出结果的交易被约束在一个区块当中即可确定取到当前时间。如果是延时开奖,攻击合约只需要在延时refer的那个区块上加上延时时间即可。这两种情况均可以取到确定的当前时间。

(4)user_name/game_id

 user_name用户名是固定的。game_id的生成也是有规律的,通常是自增的。

(5)tapos_block_prefix() 和tapos_block_num()

其中tapos_block_prefix()和tapos_block_num() 均与ref_block_num参数关联,一旦ref_block_num确定,前两个参数就是确定的。

EOS的cleos部署工具中有一个命令行参数 -r,用来指定该ref_block的信息。

实时开奖中,在不指定ref_block_num的情况下, cleos 或eosjs 客户端,会设置ref_block_num为默认值last_irreversible_block_id,即上一个区块的id。此时上一个区块是确定的,tapos_block_prefix 和tapos_block_num此时关联的上一个区块信息也变成确定可计算的了。

在异步的延时开奖中,tapos_block_prefix、tapos_block_num直接使用head_block_id 作为 ref_block_num,即当前的区块信息关联着tapos_block_prefix 和 tapos_block_num两个参数。

从对目前的攻击合约的分析来看,很多人推荐在使用tapos_block_prefix、tapos_block_num两个参数做随机因子的时候,采用发起连续的两次延时交易,然后在第二次延时交易中进行开奖,这样tapos_block_prefix 和 tapos_block_num便不容易确定,但是使用该种方法,攻击者模拟相同的逻辑,让自己的交易也对应延时,依然有几率可以让攻击合约交易和出结果的交易打包进一个块中,从而固化随机因子,让结果变得对自己有利。

4  链上伪随机数获取方案

4.1  请求中心化随机数发生器

我们在1.1和1.2已经介绍过中心化的随机数发生器。以太坊上有第三方接口Oraclize,通过 Oraclize,智能合约能够通过 Web API 请求第三方随机数网站,如 random.org 来获取伪随机数。缺点也比较明显,一个去中心化的公链却要依赖第三方接口去获取中心化的随机数发生结果。这个第三方接口 Oraclize 不会篡改结果吗?开发者该信任 random.org网站和它的随机数发生器的底层实现吗?

不过就笔者看来,比起合约被伪随机数漏洞攻击,这样妥协的解决方案还是可以接受的。

4.2  Commit–revealapproach

提交-揭示方法包括两个阶段:

1. ”提交”阶段 :一方提交加密内容给到智能合约。

2. ”揭示”阶段 :一方宣布明文种子,智能合约验证它们的正确性,并使用此种子生成随机数。

目前以太坊上的Randao合约,实现了提交-揭示方法方式的PRNG。该 PRNG 从多方收集哈希种子,并且每一方都获得参与奖励。没有人知道其他人的种子,所以结果能保证真正地随机。但是,如果激励机制设计存在问题,有一方拒绝揭示种子,将导致合约拒绝服务。

EOS平台目前还没有类似的完全在链上获取伪随机数的合约。 

5  总结

目前来看,以太坊和EOS都没有办法提供原生的随机数生成接口,并且单纯使用基于链上数据编写的伪随机数生成器都有可能被攻击。以太坊上可以使用三方接口 Oraclize 或者 Commit–reveal approach 机制去取随机数,但 Oraclize 取到的随机数是中心化的,而Commit–revealapproach不是一个单纯的接口,在设计的时候会直接面临激励机制的问题。而对于EOS,暂时官方还没提供或者推荐较好的随机数获取方案。

猜你喜欢

转载自blog.csdn.net/Fly_hps/article/details/84287343