我們將帶大家了解智能合約中一個經常被用到的東西——隨機數。

智能合約的開發中常常會用到隨機數,例如Lottery 和現在流行的NFT 數字藏品的屬性等都需要用到隨機數。目前來說常見的隨機數獲取有兩種:使用區塊變量生成隨機數,使用預言機來生成隨機數。下面我們了解一下這兩者的特點:

1 使用區塊變量生成隨機數

我們先了解一下常見的區塊變量有哪些:

block.basefee(uint):當前區塊的基本費用

block.chainid(uint):當前鏈id

block.coinbase():當前區塊礦工地址address payable

block.difficulty(uint):當前區塊難度

block.gaslimit(uint):當前區塊gaslimit

block.number(uint):當前區塊號

block.timestamp(uint):Unix 紀元以來的當前區塊時間戳(以秒為單位)

blockhash(uint blockNumber) returns (bytes32):給定區塊的哈希,僅適用於256 個最近的區塊

其中block.difficulty, blockhash, block.number 和block.timestamp 這四個是用得比較多的。由區塊數據生成的隨機數可能會限制普通用戶預測隨機數的可能性,但是並不能限制礦工作惡,礦工可以決定一個區塊是否被廣播,他們挖出了一個區塊不是一定要廣播出去也可以直接扔掉,這個就叫礦工的選擇性打包。他們可以持續嘗試生成隨機數,直至得到想要的結果再廣播出去。當然,礦工會這樣做的前提是有足夠的的利益誘惑,例如可以獲得一個很大的獎勵池中的獎勵,因此使用區塊變量獲取隨機數的方法更適合於一些隨機數不屬於核心業務的應用。

2)使用預言機生成隨機數

預言機是專門為生成隨機數種子而搭建的鏈上或者鏈下的服務。除了使用第三方服務,也可以由DApp 開發商自己搭建一個鏈下服務提供隨機數,這種在鏈上獲取鏈下數據的場景通常是通過鏈上預言機的方式來實現。

當然這種方法也會有一些安全風險,例如依賴第三方給出的隨機數種子的話同樣會存在第三方作弊或者受賄的情形,即使是自己搭建的隨機數服務也可能因為故障等原因無法使用,項目方也有可能操控隨機數對DApp 的運行和用戶造成重大的損失。因此使用鏈下服務獲取隨機數的方法依賴於是否有一個可信又穩定的第三方服務,如果有,那麼這個方法相較於使用區塊鏈變量生成隨機數的方法,隨機數的不可預測性會更強一些。

接下來我們還是用合約代碼來給大家演示弱隨機數可能帶來的危害。

漏洞示例

智能合約安全——隨機數

漏洞分析

首先我們先來了解一下代碼中的兩個函數, abi.encodePacked 和keccak256:

l abi.encodePacked 對參數進行編碼,solidity 提供兩種編碼方法encode 和 encodePacked,前者對每一個參數進行32 字節補齊,後者不進行補齊而是直接將待編碼參數連接起來。

l keccak256 哈希算法,可以將任意長度的輸入壓縮成64 位的16 進制的數,且哈希碰撞的概率近乎為0。

接下來我們來看合約代碼,這個合約是一個猜數字贏以太的遊戲,我們可以看到,部署者使用上個區塊的區塊哈希和區塊時間作為隨機數種子生成隨機數,我們只需要模擬他的隨機數生成方法就可以得到獎勵。下面我們來看攻擊合約:

攻擊合約

智能合約安全——隨機數

下面我們先來分析攻擊流程:

攻擊者調用Attack.attack()函數,它模擬了GuessTheRandomNumber 合約中隨機數的生成方式生成隨機數後調用guessTheRandomNumber.guess() 並將生成的隨機數傳入,由於從Attack.attack() 生成隨機數到調用guessTheRandomNumber.guess() 都是在同一區塊中完成的,且在同一區塊中block.number 和block.timestamp 這兩個參數是不變的,所以,Attack.attack() 和guessTheRandomNumber.guess () 這兩個函數生成的隨機數的結果是相同的,從而攻擊者可以順利通過if(_guess == answer) 判斷得到獎勵。

修復建議

如果隨機數屬於非核心業務的話可以使用未來區塊哈希來生成隨機數也就是將猜數和領獎分開做異步處理。針對這次的漏洞合約寫一個優化版本,大家可以看下:

智能合約安全——隨機數

添加了deadline 參數將guess 和claim 做異步處理,在部署合約後的72 小時內可以調用guess() 猜隨機數,在72 小時後guess() 關閉claim() 開啟,玩家可以通過claim() 來驗證自己是否猜中。當然,這個修復合約並不是完美的解決方案,正如前置知識中提到的,如果礦工來玩的話他可以在打包的時候知道自己是否猜中,如果猜中打包上鍊,如果沒有猜中放棄打包(相信沒有任何一個礦工願意為了得到一個以太而付出這麼大的代價)。所以最優的解決辦法還是接入知名預言機來獲取隨機數。

如果想了解更多的智能合約和區塊鏈知識,歡迎到區塊鏈交流社區CHAINPIP社區,一起交流學習~社區地址: https://www.chainpip.com/