在以太坊生成隨機數的幾種方式(含程式碼)

買賣虛擬貨幣
一、什麼是隨機數隨機數都是由隨機數生成器(Random Number Generator)生成的。隨機數分為”真隨機數“和”偽隨機數“兩種。1、 真隨機數
真正的隨機數是使用物理現象產生的:比如擲錢幣、骰子、轉輪、使用電子元件的噪音、核裂變等等,這樣的隨機數發生器叫做物理性隨機數發生器,它們的缺點是技術要求比較高。----百度百科根據百科上的定義可以看到,真隨機數是依賴於物理隨機數生成器的。使用較多的就是電子元件中的噪音等較為高階、複雜的物理過程來生成。2、偽隨機數
真正意義上的隨機數(或者隨機事件)在某次產生過程中是按照實驗過程中表現的分佈概率隨機產生的,其結果是不可預測的,是不可見的。而計算機中的隨機函式是按照一定演算法模擬產生的,其結果是確定的,是可見的。我們可以這樣認為這個可預見的結果其出現的概率是100%。所以用計算機隨機函式所產生的“隨機數”並不隨機,是偽隨機數。---百度百科從定義我們可以瞭解到,偽隨機數其實是有規律的。只不過這個規律週期比較長,但還是可以預測的。主要原因就是偽隨機數是計算機使用演算法模擬出來的,這個過程並不涉及到物理過程,所以自然不可能具有真隨機數的特性。二、以太坊上的隨機數
1、為什麼沒有random方法?以太坊作為區塊鏈,是一種確定性的圖靈機,所有分散式節點需要對鏈上狀態改變達成共識,就需要交易在所有節點上的計算結果都是一樣的。這意味著以太坊不能涉及隨機性。如果存在隨機的操作碼,則所有礦工將獲得不同的結果,網路將無法達成共識。2、兩種來源
以太坊上沒有random方法,但並不代表在以太坊上對隨機數沒有需求。在一些業務場景下,特別是菠菜類Dapp,對隨機數是有強需求的。例如在彩票的場景下,現實生活中,彩票開獎是由彩票中心使用彩票機開獎的(看起來是隨機生成的號碼,但確一直被人懷疑)。在區塊鏈上,我們需要中獎的彩票號是隨機產生的,從而保證遊戲的公平性和可信力。在以太坊上,所使用的隨機數主要有兩種來源,一種是透過鏈上生成,一種是透過鏈下生成。
三、鏈上生成隨機數鏈上生成隨機數的核心是在交易被打包到區塊之前儘可能的選取不可預測的種子(數)來生成隨機數。接下來介紹的幾種方法,其區別也是隨機數生成種子的可預測性不同,越不可預測,其安全性也就越高。
1、不怎麼安全的隨機數在一筆交易中,這筆交易什麼時候,被誰打包到區塊中,對使用者來說是不可知的,但是一旦被打包到區塊中,這些值就是確定的了,因此我們可以利用區塊的打包時間block.timestamp、區塊的打包難度block.difficulty作為種子生成隨機數。0-100隨機數生成器程式碼如下:function importSeedFromThird() public view returns (uint8) {
        return uint8(            uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % 100        );
}其中:•abi.encodePacked 緊密打包資料的 bytes 而沒有任何填充,因為如果沒有填充,則無法從此函式中提取資料。函式返回 bytes 型別,可以轉化為 uint256 型別。
•keccak256 是和 sha3 類似的 hash 函式,只是採用不同的補齊模式。雖然block.timestamp和block.difficulty對普通使用者來說無法預測,但是對礦工來說,卻是可以操控的。有足夠的利益驅動,礦工可以持續對區塊進行挖礦打包,直到計算出對自己有利的隨機數,進而打包區塊。針對這種情況,我們需要加強我們的隨機數生成器,可以透過引起業務資料來加強。
2、利用重複雜湊加強安全性透過對第一種生成的隨機數作為資料來源重複進行雜湊運算,同樣可以大大增大礦工的攻擊成本,增強安全性。

重複雜湊是將雜湊函式的一次執行的輸出用作下一次執行的輸入,從而多次執行雜湊函式的行為。如果初始輸入值有稍微的變動,最終計算的結果也會有天壤之別。

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的方式完全可以滿足需求;

在一些安全性要求非常高的場景下,可以選擇預言機提供隨機數服務,但會犧牲請求效率。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;