重複雜湊是將雜湊函式的一次執行的輸出用作下一次執行的輸入,從而多次執行雜湊函式的行為。如果初始輸入值有稍微的變動,最終計算的結果也會有天壤之別。
3、利用業務邏輯生成相對安全的隨機數
將業務資料加入到隨機數生成器中,可以解決礦工利用隨機數生成器攻擊Dapp。這裡以彩票合約為示例,使用者Tjaden Hess(https://ethereum.stackexchange.com/users/131/tjaden-hess)在stackoverflow上對彩票合約提出過比較好的解決方案。其核心是使用玩家的地址和所選號碼作為隨機數生成器的種子。
彩票合約的邏輯是:
•新一期彩票投注開啟,玩家提交以太坊地址和投注號碼計算的雜湊,之所以提交hash是為了保障在計算隨機數(中獎號碼)之前,無法預知投注號碼
•按照區塊數或者參與者達到上限,投注截止
•投注玩家提交自己的投注號碼,合約會根據之前玩家提交的hash值進行校驗。此時玩家投注的號碼已不可改變
•組織者開獎,從投注號碼中隨機選擇中獎號碼(取隨機數),並將獎金髮放給中獎使用者
彩票合約程式碼如下:
//THIS CONTRACT IS CONSUMING A LOT OF GAS
//THIS CONTRACT IS ONLY FOR DEMONSTRATING HOW RANDOM NUMBER CAN BE GENERATED
//DO NOT USE THIS FOR PRODUCTION
pragma solidity ^0.4.8;
contract Lottery {
mapping (uint8 => address[]) playersByNumber ;
mapping (address => bytes32) playersHash;
uint8[] public numbers;
address owner;
function Lottery() public {
owner = msg.sender;
state = LotteryState.FirstRound;
}
enum LotteryState { FirstRound, SecondRound, Finished }
LotteryState state;
function enterHash(bytes32 x) public payable {
require(state == LotteryState.FirstRound);
require(msg.value > .001 ether);
playersHash[msg.sender] = x;
}
function runSecondRound() public {
require(msg.sender == owner);
require(state == LotteryState.FirstRound);
state = LotteryState.SecondRound;
}
function enterNumber(uint8 number) public {
require(number<=250);
require(state == LotteryState.SecondRound);
require(keccak256(number, msg.sender) == playersHash[msg.sender]);
playersByNumber[number].push(msg.sender);
numbers.push(number);
}
function determineWinner() public {
require(msg.sender == owner);
state = LotteryState.Finished;
uint8 winningNumber = random();
distributeFunds(winningNumber);
selfdestruct(owner);
}
function distributeFunds(uint8 winningNumber) private returns(uint256) {
uint256 winnerCount = playersByNumber[winningNumber].length;
require(winnerCount == 1);
if (winnerCount > 0) {
uint256 balanceToDistribute = this.balance/(2*winnerCount);
for (uint i = 0; i<winnerCount; i++) {
require(i==0);
playersByNumber[winningNumber][i].transfer(balanceToDistribute);
}
}
return this.balance;
}
function random() private view returns (uint8) {
uint8 randomNumber = numbers[0];
for (uint8 i = 1; i < numbers.length; ++i) {
randomNumber ^= numbers[i];
}
return randomNumber;
}
}
彩票合約程式碼來源於:https://gist.github.com/promentol/d94959bfaf10f6b64d3cbf9c293de468
四、鏈下生成隨機數
鏈下方式生成隨機數供鏈上使用,主要透過預言機 oracle來實現,而預言機又分為中心化預言機和去中心預言機。
1、中心化
使用中心化的方式生成隨機數其首要前提是要保證隨機數的可信性,這裡推薦使用random,地址:https://www.random.org/
2、去中心化
目前有很多oracle服務提供隨機數,如
•randao提供了commit reveral合約和BLS合約兩種方式提供隨機數,其白皮書詳見:https://randao.org/whitepaper/Randao_v0.85.pdf
•Niguez隨機引擎,使用說明詳見:https://github.com/niguezrandomityengine/guide/blob/master/guide.md
五、總結
對於以太坊合約中使用隨機數,永遠沒有最安全的方式,只有最適合業務場景的方式。
如果業務資料本身具有隨機性,可選擇利用業務資料作為隨機數生成器的種子;
如果業務場景(合約)不涉及利益或者利益驅動比較小的情況下,使用區塊變數+重複hash的方式完全可以滿足需求;
在一些安全性要求非常高的場景下,可以選擇預言機提供隨機數服務,但會犧牲請求效率。