DApp中“要命”的隨機——合約隨機數安全問題解析
公鏈上目前存在為數眾多的競猜類DApp。競猜類遊戲的邏輯就是生成無法預測的隨機數,玩家公平競猜。安全的隨機數生成器可以保證所有玩家在遊戲中的競猜是公平的,玩家的獲勝概率也是相同的。但是近期多個EOS和以太坊平臺DApp因為隨機數生成漏洞被黑客攻擊。如EOS的Luckyos、EOS.Win、EosDice,以太坊上的Fomo3D。
針對這種情況,北京鏈安的安全專家Hardman就公鏈DApp中隨機數安全做了相關解析,讓我們看看如何安全的使用隨機數。
1 隨機數和發生器介紹
在以區塊鏈為代表的去中心化公共賬本提法之前,資料都是私有的,中心化的。因此在此之前的隨機數發生器也都是中心化的,其中的代表方案有 NIST Randomness Beacon 以及 random.org等。
1.1 NIST隨機數發生器
NISTRandomness Beacon4用於實現公共隨機數源。它使用兩個獨立、商用的隨機數發生器,每個發生器配備一個獨立的物理熵源和 SP800-90 認可元件。NIST 隨機數生成器旨在提供不可預測、自主、一致的隨機數源。不可預測是指:任何演算法都無法預測該生成器將會給出的隨機數。自主是指:能夠抵抗不相關者介入或阻止分發隨機數的過程。一致是指:一組使用者訪問該服務能夠確實地獲得相同的隨機數。
1.2 random.org
random.org 使用大氣噪音生成隨機數。即先用錄音裝置獲得大氣中的聲波,再檢測其細微變化作為生成隨機數的熵源。random.org還提到了兩類物理現象作為熵源的比較:量子現象和混沌現象。量子現象作為隨機數源是利用了在原子尺度下粒子的行為具有隨機性,而且其本質還未被人類發現,因此可以將其看做一個具有良好不確定性的熵源。混沌現象是指在混沌系統中,初始量的微小差異會導致未來的發展截然不同,因此除非獲得初始時刻的全部準確資訊,則無法預測未來的發展趨勢。實際上應用這兩種方法均能實現不可預測的隨機數發生器,random.org使用大氣噪音生成隨機數的方法就屬於後者。
2 為什麼公鏈上不支援直接獲取隨機數
開發合約,獲取隨機數是一個很大的問題,以太坊和EOS都不支援隨機數的生成,這是為什麼?
假設合約裡可以生成隨機數,那麼該合約執行結果就是完全具備隨機可能了,其他節點完全無法直接驗證該執行結果是否合法。不誠實的節點直接可以自行隨便指定一個數字,或者不停重新計算,直到該節點得到一個對自己有利的數字,再打包到區塊中。因為位元組碼在不同的節點的虛擬機器驗證執行過程上,得到的隨機數都是不同的,虛擬機器完全無法的確定該結果是否合法。
這種結果很顯然在設計上是不被接受的。公鏈節點通過打包區塊來實現資料儲存,資料需要非常高的確定性,因此在公鏈上是沒辦法得到一個隨機數的。
3 以太坊和EOS PRNG漏洞歸總
由於非常多的場景需要使用到隨機數。競猜、賭博、隨機分配任務等。在官方不提供直接獲取隨機數背景下,公鏈上的開發者自行根據公鏈上能取到的各種變數,自行開發偽隨機數生成器(pseudo-random number generator),簡稱 `PRNG`。有漏洞的PRNG就為以後的安全問題埋下了隱患。這些有漏洞的PRNG主要依賴於:區塊變數、過往區塊hash。
3.1 以太坊中易引發PRNG漏洞的變數
3.1.1 區塊變數
(1)block.coinbase 表示當前區塊的礦工地址
(2)block.difficulty 表示當前區塊的挖掘難度
(3)block.gaslimit 區塊內交易的最大限制燃氣消耗量
(4)block.number 表示當前區塊高度
(5)block.timestamp 表示當前區塊挖掘時間
以上所有的區塊變數都可以被礦工操縱,所以都不能用來做資訊熵源。因為這些區塊變數在同一區塊上是共用的。攻擊者通過其惡意合約呼叫受害者合約,那麼此交易打包在同一區塊中,其區塊變數是一樣的。
3.1.2 過往區塊hash
(1)block.blockhash(block.number)
通過`block.number` 變數可以獲取當前區塊區塊高度。但是還沒執行時,這個“當前區塊”是一個未來區塊,即只有當一個礦工拾取一個執行合約程式碼的交易時,這個未來區塊才變為當前區塊,所以合約才可以可靠地獲取此區塊的區塊雜湊。而一些合約曲解了 `block.blockhash(block.number)` 的含義,誤認為當前區塊的區塊雜湊在執行過程中是已知的,並將之做為熵源,其實此時在以太坊虛擬機器(EVM)中,區塊雜湊恆為 0。
(2)block.blockhash(block.number-1)
有一些合約則基於負一高度區塊的區塊雜湊來產生偽隨機數,這也是有缺陷的。攻擊合約只要以相同程式碼執行,即可以產生到同樣的偽隨機數。
3.2 EOS中易引發PRNG漏洞的變數
(1)transacation_id(交易id)
其中transacation_id,是一筆交易的唯一id,以sha256計算後的雜湊形式體現。交易id與使用者發起的 transacation有唯一關聯, 是個可計算並且確定的值。通過read_transaction 拿到當前 transacation的資料,sha256 可以得到。
(2)合約餘額amount(合約當前的餘額)
合約金額拉長時間來看是動態的,但是大多情況下變動的頻率不是非常快,更不是每分每秒都在變,在固定的一個短的時間段內是一個確定的定值。
(3)current_time(當前時間)
目前來看如果是實時開獎,攻擊合約交易和出結果的交易被約束在一個區塊當中即可確定取到當前時間。如果是延時開獎,攻擊合約只需要在延時refer的那個區塊上加上延時時間即可。這兩種情況均可以取到確定的當前時間。
(4)user_name/game_id
user_name使用者名稱是固定的。game_id的生成也是有規律的,通常是自增的。
(5)tapos_block_prefix() 和tapos_block_num()
其中tapos_block_prefix()和tapos_block_num() 均與ref_block_num引數關聯,一旦ref_block_num確定,前兩個引數就是確定的。
EOS的cleos部署工具中有一個命令列引數 -r,用來指定該ref_block的資訊。
實時開獎中,在不指定ref_block_num的情況下, cleos 或eosjs 客戶端,會設定ref_block_num為預設值last_irreversible_block_id,即上一個區塊的id。此時上一個區塊是確定的,tapos_block_prefix 和tapos_block_num此時關聯的上一個區塊資訊也變成確定可計算的了。
在非同步的延時開獎中,tapos_block_prefix、tapos_block_num直接使用head_block_id 作為 ref_block_num,即當前的區塊資訊關聯著tapos_block_prefix 和 tapos_block_num兩個引數。
從對目前的攻擊合約的分析來看,很多人推薦在使用tapos_block_prefix、tapos_block_num兩個引數做隨機因子的時候,採用發起連續的兩次延時交易,然後在第二次延時交易中進行開獎,這樣tapos_block_prefix 和 tapos_block_num便不容易確定,但是使用該種方法,攻擊者模擬相同的邏輯,讓自己的交易也對應延時,依然有機率可以讓攻擊合約交易和出結果的交易打包進一個塊中,從而固化隨機因子,讓結果變得對自己有利。
4 鏈上偽隨機數獲取方案
4.1 請求中心化隨機數發生器
我們在1.1和1.2已經介紹過中心化的隨機數發生器。以太坊上有第三方介面Oraclize,通過 Oraclize,智慧合約能夠通過 Web API 請求第三方隨機數網站,如 random.org 來獲取偽隨機數。缺點也比較明顯,一個去中心化的公鏈卻要依賴第三方介面去獲取中心化的隨機數發生結果。這個第三方介面 Oraclize 不會篡改結果嗎?開發者該信任 random.org網站和它的隨機數發生器的底層實現嗎?
不過就筆者看來,比起合約被偽隨機數漏洞攻擊,這樣妥協的解決方案還是可以接受的。
4.2 Commit–revealapproach
提交-揭示方法包括兩個階段:
1. ”提交”階段 :一方提交加密內容給到智慧合約。
2. ”揭示”階段 :一方宣佈明文種子,智慧合約驗證它們的正確性,並使用此種子生成隨機數。
目前以太坊上的Randao合約,實現了提交-揭示方法方式的PRNG。該 PRNG 從多方收集雜湊種子,並且每一方都獲得參與獎勵。沒有人知道其他人的種子,所以結果能保證真正地隨機。但是,如果激勵機制設計存在問題,有一方拒絕揭示種子,將導致合約拒絕服務。
EOS平臺目前還沒有類似的完全在鏈上獲取偽隨機數的合約。
5 總結
目前來看,以太坊和EOS都沒有辦法提供原生的隨機數生成介面,並且單純使用基於鏈上資料編寫的偽隨機數生成器都有可能被攻擊。以太坊上可以使用三方介面 Oraclize 或者 Commit–reveal approach 機制去取隨機數,但 Oraclize 取到的隨機數是中心化的,而Commit–revealapproach不是一個單純的介面,在設計的時候會直接面臨激勵機制的問題。而對於EOS,暫時官方還沒提供或者推薦較好的隨機數獲取方案。
(作者:北京鏈安,內容來自鏈得得內容開放平臺“得得號”;本文僅代表作者觀點,不代表鏈得得官方立場)