以太坊智能合约中随机数预测

作者:CQITer小编 时间:2018-07-30 21:57

字号

作为首次币发行(ICO)的平台,以太坊已经获得了极大的普及。 但是,它不仅仅用于 ERC20 通证,轮盘,彩票和纸牌游戏都可以使用以太坊区块链实现。 与任何区块链实施一样,以太坊是不可逆的,去中心化的,透明公开的。 以太坊允许运行图灵完备程序,这些程序通常用 Solidity 编写,使其成为平台创始人所说的“世界超级计算机”。 对于计算机赌博来说,所有这些特征都是非常有益的,尤其是用户信用。

 以太坊智能合约中随机数预测

而这类应用必不可少的部分就是产生随机数,但是以太坊区块链通过打包区块来实现数据存储,数据需要非常高的确定性,这为编写伪随机数发生器(PRNG)带来了一定的难度。

二、PRNG相关漏洞类型

开发者生成随机数时,一般都会使用伪随机数生成器(pseudo-random number generator),简称 `PRNG`。而有漏洞的PRNG,一般有四种类型:

1. 使用区块变量作为熵源的 PRNG

2. 基于过往区块的区块哈希的 PRNG

3. 基于过往区块和私有种子(seed)的区块哈希的 PRNG

4. 易被抢占交易(front-running)的 PRNG

下面我们来分别一一详解。

1、使用区块变量作为熵源的 PRNG

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

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

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

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

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

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

示例1:< https://etherscan.io/address/0x80ddae5251047d6ceb29765f38fed1c0013004b7>

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

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

示例2:< https://etherscan.io/address/0xa11e4ed59dc94e69612f3111942626ed513cb172>

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

示例3:< https://etherscan.io/address/0xcC88937F325d1C6B97da0AFDbb4cA542EFA70870>

address seed1 = contestants[uint(block.coinbase) % totalTickets].addr;

address seed2 = contestants[uint(msg.sender) % totalTickets].addr;

uint seed3 = block.difficulty;

bytes32 randHash = keccak256(seed1, seed2, seed3);

uint winningNumber = uint(randHash) % totalTickets;

address winningAddress = contestants[winningNumber].addr;

示例4 : 使用 block.difficulty

pragma solidity ^0.4.0;

contract random{

function rand() public returns(uint256) {

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

return  random%10;

}

}

2、基于过往区块的区块哈希的 PRNG

每一个Ethereum区块链上的区块都有认证的hash值,通过 block.blockhash() 函数可以获取此值。此函数经常被错误地使用。

block.blockhash(block.number) :基于当前区块的区块哈希

block.blockhash(block.number – 1) : 基于负一区块的区块哈希

block.blockhash() of a block that is at least 256 blocks older than the current one : 比当前区块小256个区块高度的区块哈希。

(1)block.blockhash(block.number)

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

 以太坊智能合约中随机数预测

示例1:< https://etherscan.io/address/0xa65d59708838581520511d98fb8b5d1f76a96cad>

function deal(address player, uint8 cardNumber) internal returns (uint8) {

uint b = block.number;

uint timestamp = block.timestamp;

return uint8(uint256(keccak256(block.blockhash(b), player, cardNumber, timestamp)) % 52);

}

示例2:< https://github.com/axiomzen/eth-random/issues/3>

function random(uint64 upper) public returns (uint64 randomNumber) {

_seed = uint64(sha3(sha3(block.blockhash(block.number), _seed), now));

return _seed % upper;

}

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

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

示例:< https://etherscan.io/address/0xF767fCA8e65d03fE16D4e38810f5E5376c3372A8>

//Generate random number between 0 & max

uint256 constant private FACTOR =  1157920892373161954235709850086879078532699846656405640394575840079131296399;

function rand(uint max) constant private returns (uint256 result){

uint256 factor = FACTOR * 100 / max;

uint256 lastBlockNumber = block.number - 1;

uint256 hashVal = uint256(block.blockhash(lastBlockNumber));

return uint256((uint256(hashVal) / factor)) % max;

}

(3)Blockhash of a future block

一个更好的方法是使用未来区块的区块哈希。示例应用执行的脚本逻辑示例如下:

1. 玩家发起下注,以当前区块高度下注,庄家存储此区块高度。

2. 玩家第二次调用时,调用庄家公布赢家的功能。

3. 庄家检索存储的区块高度,并以此区块高度的区块哈希来产生伪随机数。

此方法只有在十分必要的时候才能使用。因为也存在一定危险性,EVM 能存储的区块哈希为最近的 256 条。超过的话值为 0。

因此,如果第二次调用时,与第一次下注时的区块高度差超过了 256,那么此时的产生的区块哈希为 0,此时伪随机数就变成可猜测的了。

一个众所周知的案例是 SmartBillions lottery。合约对区块高度的验证不足,导致了 400 ETH 的损失 

相关资料:< https://www.reddit.com/r/ethereum/comments/74d3dc/smartbillions_lottery_contract_just_got_hacked/>

(4)Blockhash with a private seed

为了增加熵值,有些合约使用一个私有种子(seed)变量。

如 Slotthereum lottery 合约:

// case of Slotthereum lottery

bytes32 _a = block.blockhash(block.number - pointer);

for (uint i = 31; i >= 1; i--) {

if ((uint8(_a[i]) >= 48) && (uint8(_a[i]) <= 57)) {

return uint8(_a[i]) - 48;

}

}

责任编辑:CQITer新闻报料:400-888-8888   本站原创,未经授权不得转载
继续阅读
热新闻
推荐
关于我们联系我们免责声明隐私政策 友情链接