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

一、什么是隨機數(shù)

隨機數(shù)都是由隨機數(shù)生成器(Random Number Generator)生成的。隨機數(shù)分為”真隨機數(shù)“和”偽隨機數(shù)“兩種。

1、 真隨機數(shù)

真正的隨機數(shù)是使用物理現(xiàn)象產(chǎn)生的:比如擲錢幣、骰子、轉(zhuǎn)輪、使用電子元件的噪音、核裂變等等,這樣的隨機數(shù)發(fā)生器叫做物理性隨機數(shù)發(fā)生器,它們的缺點是技術(shù)要求比較高。 ----百度百科

根據(jù)百科上的定義可以看到,真隨機數(shù)是依賴于物理隨機數(shù)生成器的。使用較多的就是電子元件中的噪音等較為高級、復雜的物理過程來生成。

2、偽隨機數(shù)

真正意義上的隨機數(shù)(或者隨機事件)在某次產(chǎn)生過程中是按照實驗過程中表現(xiàn)的分布概率隨機產(chǎn)生的,其結(jié)果是不可預測的,是不可見的。而計算機中的隨機函數(shù)是按照一定算法模擬產(chǎn)生的,其結(jié)果是確定的,是可見的。我們可以這樣認為這個可預見的結(jié)果其出現(xiàn)的概率是100%。所以用計算機隨機函數(shù)所產(chǎn)生的“隨機數(shù)”并不隨機,是偽隨機數(shù)。---百度百科

從定義我們可以了解到,偽隨機數(shù)其實是有規(guī)律的。只不過這個規(guī)律周期比較長,但還是可以預測的。主要原因就是偽隨機數(shù)是計算機使用算法模擬出來的,這個過程并不涉及到物理過程,所以自然不可能具有真隨機數(shù)的特性。

二、以太坊上的隨機數(shù)

1、為什么沒有random方法?

以太坊作為區(qū)塊鏈,是一種確定性的圖靈機,所有分布式節(jié)點需要對鏈上狀態(tài)改變達成共識,就需要交易在所有節(jié)點上的計算結(jié)果都是一樣的。這意味著以太坊不能涉及隨機性。如果存在隨機的操作碼,則所有礦工將獲得不同的結(jié)果,網(wǎng)絡(luò)將無法達成共識。

2、兩種來源

以太坊上沒有random方法,但并不代表在以太坊上對隨機數(shù)沒有需求。在一些業(yè)務(wù)場景下,特別是菠菜類Dapp,對隨機數(shù)是有強需求的。

例如在彩票的場景下,現(xiàn)實生活中,彩票開獎是由彩票中心使用彩票機開獎的(看起來是隨機生成的號碼,但確一直被人懷疑)。在區(qū)塊鏈上,我們需要中獎的彩票號是隨機產(chǎn)生的,從而保證游戲的公平性和可信力。

在以太坊上,所使用的隨機數(shù)主要有兩種來源,一種是通過鏈上生成,一種是通過鏈下生成。

三、鏈上生成隨機數(shù)

鏈上生成隨機數(shù)的核心是在交易被打包到區(qū)塊之前盡可能的選取不可預測的種子(數(shù))來生成隨機數(shù)。

接下來介紹的幾種方法,其區(qū)別也是隨機數(shù)生成種子的可預測性不同,越不可預測,其安全性也就越高。

1、不怎么安全的隨機數(shù)

在一筆交易中,這筆交易什么時候,被誰打包到區(qū)塊中,對用戶來說是不可知的,但是一旦被打包到區(qū)塊中,這些值就是確定的了,因此我們可以利用區(qū)塊的打包時間block.timestamp、區(qū)塊的打包難度block.difficulty作為種子生成隨機數(shù)。0-100隨機數(shù)生成器代碼如下:

<pre style="margin: 0px; tab-size: 4; white-space: pre-wrap;">function importSeedFromThird() public view returns (uint8) {
return uint8(
uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % 100
);
}</pre>

其中:

  • abi.encodePacked 緊密打包數(shù)據(jù)的 bytes 而沒有任何填充,因為如果沒有填充,則無法從此函數(shù)中提取數(shù)據(jù)。函數(shù)返回 bytes 類型,可以轉(zhuǎn)化為 uint256 類型。
  • keccak256 是和 sha3 類似的 hash 函數(shù),只是采用不同的補齊模式。

雖然block.timestamp和block.difficulty對普通用戶來說無法預測,但是對礦工來說,卻是可以操控的。有足夠的利益驅(qū)動,礦工可以持續(xù)對區(qū)塊進行挖礦打包,直到計算出對自己有利的隨機數(shù),進而打包區(qū)塊。

針對這種情況,我們需要加強我們的隨機數(shù)生成器,可以通過引起業(yè)務(wù)數(shù)據(jù)來加強。

2、利用重復哈希加強安全性

通過對第一種生成的隨機數(shù)作為數(shù)據(jù)源重復進行哈希運算,同樣可以大大增大礦工的攻擊成本,增強安全性。

重復哈希是將哈希函數(shù)的一次運行的輸出用作下一次運行的輸入,從而多次運行哈希函數(shù)的行為。如果初始輸入值有稍微的變動,最終計算的結(jié)果也會有天壤之別。


d7414827c4a908520f38df75f02db83b.png

3、利用業(yè)務(wù)邏輯生成相對安全的隨機數(shù)

將業(yè)務(wù)數(shù)據(jù)加入到隨機數(shù)生成器中,可以解決礦工利用隨機數(shù)生成器攻擊Dapp。
這里以彩票合約為示例,用戶Tjaden Hess(https://ethereum.stackexchange.com/users/131/tjaden-hess)在stackoverflow上對彩票合約提出過比較好的解決方案。其核心是使用玩家的地址和所選號碼作為隨機數(shù)生成器的種子。

彩票合約的邏輯是:

  • 新一期彩票投注開啟,玩家提交以太坊地址和投注號碼計算的哈希,之所以提交hash是為了保障在計算隨機數(shù)(中獎號碼)之前,無法預知投注號碼
  • 按照區(qū)塊數(shù)或者參與者達到上限,投注截止
  • 投注玩家提交自己的投注號碼,合約會根據(jù)之前玩家提交的hash值進行校驗。此時玩家投注的號碼已不可改變
  • 組織者開獎,從投注號碼中隨機選擇中獎號碼(取隨機數(shù)),并將獎金發(fā)放給中獎用戶

彩票合約代碼如下:

//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 =&gt; address[]) playersByNumber ;
    mapping (address =&gt; 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 &gt; .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&lt;=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 &gt; 0) {
            uint256 balanceToDistribute = this.balance/(2*winnerCount);
            for (uint i = 0; i&lt;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 &lt; numbers.length; ++i) {
            randomNumber ^= numbers[I];
        }
        return randomNumber;

    }
}

彩票合約代碼來源于:https://gist.github.com/promentol/d94959bfaf10f6b64d3cbf9c293de468

四、鏈下生成隨機數(shù)

鏈下方式生成隨機數(shù)供鏈上使用,主要通過預言機 oracle來實現(xiàn),而預言機又分為中心化預言機和去中心預言機。

1、中心化

使用中心化的方式生成隨機數(shù)其首要前提是要保證隨機數(shù)的可信性,這里推薦使用random,地址:https://www.random.org/

2、去中心化

目前有很多oracle服務(wù)提供隨機數(shù),如

五、總結(jié)

對于以太坊合約中使用隨機數(shù),永遠沒有最安全的方式,只有最適合業(yè)務(wù)場景的方式。

如果業(yè)務(wù)數(shù)據(jù)本身具有隨機性,可選擇利用業(yè)務(wù)數(shù)據(jù)作為隨機數(shù)生成器的種子;

如果業(yè)務(wù)場景(合約)不涉及利益或者利益驅(qū)動比較小的情況下,使用區(qū)塊變量+重復hash的方式完全可以滿足需求;

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容