一、前言

“随机数”在计算机程序中并不少见,开发人员也会经常使用随机数进行数值的模拟、预测。在C++程序里,我们经常使用一定的种子来进行随机数的生成过程。在计算机程序中,随机数可以被分为真随机数伪随机数。而真随机数是十分难实现的,例如使用筛子、转轮等等。对于伪随机数来说,我们又分为:

  1. 强伪随机数:难以预测的随机数,常用于密码学。
  2. 弱伪随机数:易于预测的随机数。

随机数有3个特性,具体如下:

  • 随机性:不存在统计学偏差,是完全杂乱的数列,即分布均匀性和独立性
  • 不可预测性:不能从过去的数列推测出下一个出现的数
  • 不可重现性:除非将数列本身保存下来,否则不能重现相同的数列

随机数的特性和随机数的分类有一定的关系,比如,弱伪随机数只需要满足随机性即可,而强位随机数需要满足随机性和不可预测性,真随机数则需要同时满足3个特性。

上述是宏观上随机数的情况,然而在区块链的场景下,随机数又有了不同。对于分布式的APP,区块链先天就在随机数的生成中有了弊端。要领所有的用户都能够得到相同的随机数数值并且还不能让延时过高确实是个很难的挑战。

在区块链中,随机数有很多的应用场景,例如抽奖活动、验证码、Token、密码应用场景(生成秘钥、生成盐)等。

随机数的安全也是一个由来已久的问题。一个广泛的原则是:随机性的生成最好不被任何个体所控制。如从比特币的未来某区块的数据来获取随机性的方式是不能使人信服的,因为这些随机性最终实际上是由某个个体决定的,无法证明这个相关人没有作恶等等。

下面我们就针对区块链中的随机数生成进行详细分析,并对现在的随机数安全进行代码实现。

全文共分上下两篇,第一篇详细讲述随机数的理论原理,第二篇对随机数代码、CTF竞赛题目、项目代码进行分析

二、区块链中的随机数

我们都知道PoW的方案是让全网共同进行哈希计算,并在出题时设置一个计算哈希的难题,谁先算出来谁就获胜。所以这就意味着算力高的赢的概率高,算力低的赢的概率低,以这样的方式保证胜出者是随机的。而在计算难题的设置上也存在的随机数的设置,这个随机数需要所有的节点均能够查看到,并且能够做到无法预测。

对于Pos来说,它为了解决Pow中存在的浪费电力的情况,它选择了了随机选举一个节点来出块,并且被选中的概率和它拥有的份额相关。倘若系统能够保证随机这个过程是无法预测且真实的,那么恶意节点只能通过增加自己的份额,增加自己被选中的概率,从而增加双花攻击的成功概率。比较Pow,Pos攻击者要实现攻击,先得成为持币大户,如果因为攻击而导致币价大跌,攻击者也会承受最大的损失。

根据我们的分析,上述两种情况均涉及到随机数的产生。我们假设此处的随机数存在问题,例如存在可预测的情况。那么攻击者就可以无限的进行作恶。在Pow中,他就会比别的节点提前知道随机数的内容,并且能够先人一步提前进行计算,也就意味着他能够有更大的概率获得记账权。对于Pos来说,攻击者就能根据随机数的生成机制,更改某些条件以达到使的选择到自己为记账人的概率加大,从而提升自己记账的权利。

传统地PoS方案尝试从链上的数据入手,比如使用上一个区块的哈希值,上一个区块的时间戳等等来作为随机数的来源,但这些会带来额外的安全风险。 因为区块本身的信息就是节点写进去的,然后又要根据里面的信息来选举后续的出块者,存在循环论证的嫌疑,安全性不会太好。 这也是传统地认为PoS方案不如PoW可靠的部分原因。

区块链是一个分布式的系统,它要求各个节点的运算结果是可验证、可共识的。然而传统的伪随机数生成算法与单台机器的物理状态或运算状态相关,不同的机器或者节点会展现出不同的运算结果,然而区块链上不能这么进行设计。

所以针对DAPP,有一下三种解决方案供参考:

第一种是引入第三方,并让可信第三方为合约提供随机数;第二种是通过合约的设定进行多方协作从而实现伪随机数的生成,为其他合约提供一致性的随机数。第三种是让所有节点上的合约可以采集到相同的种子,再通过伪随机算法计算出相同的随机数序列。

下面我们具体来看:

1 可信第三方参与生成

在区块链系统中,为了使所有的节点的随机数同一,我们可以将区块链的所有节点接入第三方的平台上来。简单来说,我们可以可以找一个应用帮助我们生成随机数,而所有的节点均对这个应用信任。

虽然这样可以解决随机数生成的问题,但是我们知道区块链作为去中心化的代表,倘若引入了中心化的应用来帮助是有些违背理论的。 然而该第三方是否值得信任,能否能提供高质量的随机数均是问题。倘若这个应用于其他攻击者联手或者被黑客攻破,那带来的威胁也是巨大的。

不过还是有一些落地的DAPP使用了这种设计思维。例如:External oracles: Oraclize

Oraclize 提供了一个连接以太坊与互联网的桥梁。通过 Oraclize,智能合约能够通过 web API 请求数据。如当前的兑换率,天气预报或股票价格。其中一个最大的作用是能提供伪随机数。一些合约通过 Oraclize 中的 URL 连接器来连接 [random.org](http://random.org/) 来获取伪随机数。如下图所示:

在这个应用中,Oracle是提供数据的一方。由于区块链应用程序(如比特币脚本和智能合约)无法直接获取所需数据,因此需要这样的数据:资产和金融应用程序的价格供给。此应用多用于点对点保险的天气相关信息。赌博的随机数生成。Oraclize是一个预言机,独立于区块链系统之外,智能合约发送请求给Oraclize,当Oraclize监听到链上相关请求后,生成随机数并将结果返回区块链。

下面代码是在以太坊中接入的函数:

pragma solidity ^0.4.11;
import "github.com/oraclize/ethereum-api/oraclizeAPI.sol";

contract ExampleContract is usingOraclize {

   string public ETHUSD;
   event LogConstructorInitiated(string nextStep);
   event LogPriceUpdated(string price);
   event LogNewOraclizeQuery(string description);

   function ExampleContract() payable {
       LogConstructorInitiated("Constructor was initiated. Call 'updatePrice()' to send the Oraclize Query.");
   }

   function __callback(bytes32 myid, string result) {
       if (msg.sender != oraclize_cbAddress()) revert();
       ETHUSD = result;
       LogPriceUpdated(result);
   }

   function updatePrice() payable {
       if (oraclize_getPrice("URL") > this.balance) {
           LogNewOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee");
       } else {
           LogNewOraclizeQuery("Oraclize query was sent, standing by for the answer..");
           oraclize_query("URL", "json(https://api.pro.coinbase.com/products/ETH-USD/ticker).price");
       }
   }
}

详细的使用方法参考oraclize

2 基础合约参与生成

第二种方法是在区块链中最符合分布式思想的一种,他需要区块链系统中不同的参与者进行合作才能生成伪随机数。具体的算法我们讲解如下:

首先,在随机数生成时,为了能够达到生成随机数的安全特性,我们需要使用密码学的理论来充实我们的概念。首先我们来看承诺和打开((Commitment & Open)

在承诺与打开的应用场景下,倘若此处存在两个用户A与B。他们两个人并没有面对面,但是他们想进行一个石头剪刀布的游戏,并决出获胜者。那我们应该怎么做呢?倘若时间不同步,那么一个人就能看到另一个人的情况,这也就意味着他们无法公平的竞争。所以我们要对每个人的结果采取一些措施:

  • 他们先自行做出选择,然后把自己的选择做个哈希;(H(A)、H(B))
  • 交换这个哈希;
  • 等双方都收到对方的哈希后,再交换双方的选择;
  • 验证对方的选择和之前的哈希一致;

这个过程中也可以添加入自己的公钥以做证据,防止以后反悔。

这样双方都知道了对方的选择,也能确认对方的选择是提前就做好的。 这个哈希值就叫做承诺,因为它里面包含了保密信息,但又没有泄漏保密信息,而最终发送对应的保密信息,就叫做打开承诺。

下面我们讲述一下如何在生产随机数的过程中记录作恶节点并对其进行惩罚。下面我们介绍下秘密共享方案:

秘密共享是说,一个人可以把一个需要保密的信息,拆分成n份,分别发送给n个人,只要恶意节点不超过一定数量,最终大家可以综合各自的信息片段把原始信息还原出来。 并且就算分发者如果作弊,大家也可以检查出来。比如密码共享方案(3,9)即将一个秘密分为九份,然后分配给9个人,只要九份中的三份放在一起就可以恢复出秘密。

例如下面的直接坐标系,我们都知道y = ax +b直线坐标需要两个点可以确定这个直线。所以我们可以用此方程做(2,n)的共享方案。我们可以在图像上寻找n个点并分发给n个用户,之后倘若有两个用户将点放在一起便可以恢复成直线,也就可以得出b的值。而b的值就是我们隐藏的秘密。

知道了这些,倘若作恶节点不发送随机数的打开方式,那么我们也可以利用合理数量的用户来打开秘密。保证系统的正常运行。如果他想在拆分信息上作弊,大家也能检查出来并把他踢掉。

最后,因为我们本来就是个区块链,所以协议过程中需要广播的信息,我们可以直接写到链上去, 这样可以简化实现,并且也不需要所有投票节点同时在线,并且如果有人作弊,作弊的记录将会永远保存在链上。采取相应的惩罚措施也会使恶意节点收手。

在讲完密码学基础后,我们引用一张图来表示这个过程:

3 公共种子采集生成

在第三种方式,合约采用了公共区块上面的信息进行随机数的seed值,由智能合约根据种子生成伪随机数。这种方法最大的缺陷就是一旦黑客知道了随机数的生成算法,也能获取正确的种子,就能轻易地对智能合约发起随机数攻击。

然而不同于传统的中心化系统,区块链上的信息是所有人都能看到的,区块链上的种子几乎是“透明”的。它是链上的区块信息,所有节点上的智能合约都能够取到,那么从原理上讲,黑客用于攻击的恶意合约同样可以获得这些数值。

下面我们列举几个危险的随机数生成函数”:

// 如果 block.number 是偶数,则 won 输出为 true。 

bool won = (block.number % 2) == 0;

在实例中,我们看到使用了block.number这个函数。而在我们的实验中,我们可以看到:

pragma solidity ^0.4.18;

contract CoinFlip {
  uint256 lastHash;
    function get() view public returns(uint256) {
      lastHash =  uint256(block.blockhash(block.number));
      return lastHash;
    }

}

var random = uint(sha3(block.timestamp)) % 2;

除此之外,我们还可以使用困难度进行随机数产生,然而这也是不安全的。

pragma solidity ^0.4.0;

contract random{

   function rand() public returns(uint256) {

        uint256 random = uint256(keccak256(block.difficulty,now));

        return  random%10;

    }

}

由此可见,这种方法也是需要更深刻的进行设计的。当黑客获取到随机数算法后,他就能根据算法的内容进行百分百预测从而进行作恶。

而在我们的的时候发现,许多合约使用了block.blockhash(block.number)这个函数作为生成随机数的种子。这是十分危险的。block.number 变量可以获取当前区块区块高度。但是还没执行时,这个“当前区块”是一个未来区块,即只有当一个矿工把交易打包并发布时,这个未来区块才变为当前区块,所以合约才可以可靠地获取此区块的区块哈希。

所以这个信息不论怎么运行都只会返回0值。

三、参考

本稿为原创稿件,转载请标明出处。谢谢。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖