你好,我是不羈烘挫,一名程序員诀艰,帶你玩轉EOS智能合約開發(fā)。如果你對EOS智能合約感興趣饮六,歡迎關注我的專欄其垄。
簡介:昨天我在為什么EOSBet不敢開源?一文中卤橄,和大家探討了EOS上隨機數(shù)的生成問題绿满。隨機數(shù)的生成問題的確是很多區(qū)塊鏈項目的硬傷,也是不少項目不愿意開源的原因之一窟扑。今天我們繼續(xù)探討怎樣生成隨機數(shù)喇颁,既能保證公平,又能讓黑客無法根據(jù)隨機過程提前預測隨機結果嚎货,從而避免項目方利益受損橘霎;同時本文會引出一個目前業(yè)界用的比較成熟的方案(其實仍稍欠完美,本文也會解析)殖属。
我們昨天留了這么個問題:
學過C++的都知道姐叁,向系統(tǒng)請求開辟一塊內存時,該內存的地址是不確定的洗显,那你覺得可以利用這個特點外潜,在智能合約中生成隨機數(shù)嗎?
不知道你有沒有寫個試驗代碼試試呢挠唆?不羈做過實驗橡卤,實驗過程就不在這里演示了。
這個問題的答案是损搬,雖然內存的地址生成我們是不確定的碧库,但對于虛擬機來說是確定的,在它每次實行時巧勤,相應執(zhí)行路徑上的變量的地址都是一樣的嵌灰。這是可以想見的,因為每次執(zhí)行action颅悉,虛擬機都是初始化一個新的運行沙盒環(huán)境沽瞭,相同的action,執(zhí)行過程也必然相同剩瓶,中間沒有任何不確定的因素擾動執(zhí)行過程驹溃,從而執(zhí)行過程也都是可以確定的城丧。這樣黑客把你的代碼完全拿過來,然后打印出相關變量的地址豌鹤,就可以確定了亡哄。而因為每次執(zhí)行都一樣,所以黑客在本地打印的結果也就是你在主網(wǎng)上運行時相關變量的地址布疙。
可見蚊惯,利用隨機開辟的內存地址作為隨機種子
也是不可取的。
隨機種子
我剛才用到了隨機種子
這個詞灵临,什么是隨機種子
呢截型?因為隨機過程是計算機程序,程序分兩種儒溉,一種是無狀態(tài)的宦焦,一種是有狀態(tài)的。
無狀態(tài)的程序顿涣,每次對于相同的輸入總是給出相同的輸出赶诊,也就是說,只要你給我一個輸入园骆,我都可以私下把程序運行一下舔痪,然后給你一個結果,你自己再執(zhí)行一次锌唾,也必要是這個結果锄码。
有狀態(tài)的程序,雖然每次的執(zhí)行結果可能不同晌涕,但只要你告訴我初始狀態(tài)滋捶,我也可以根據(jù)你每次給的輸入,預測出它的輸出余黎。
嚴格來數(shù)重窟,所有的隨機過程都是有狀態(tài)的計算機程序。而隨機種子呢惧财,就相當于它的初始狀態(tài)巡扇,或者可對它的當前狀態(tài)產(chǎn)生一個不可預測的擾動,變成另外一個狀態(tài)垮衷。
所以假如你想讓你的隨機數(shù)是不可預測的厅翔,你必須保證隨機種子是不可預測的。
這樣我們就可以把產(chǎn)生隨機數(shù)的機理分成兩部分了搀突,一部分是隨機種子刀闷,一部分是隨機過程。只要隨機種子不公開,把隨機過程公開甸昏,那么黑客也就無法對產(chǎn)生的隨機數(shù)進行預測了顽分。隨機過程可以寫在智能合約里,隨機種子就需要外界的輸入了施蜜,并且是不可預測的外界輸入卒蘸。只要隨機種子是不可預測的,那么隨機結果也就是不可預測的了花墩。
我前面討論過悬秉,用時間和wasm虛擬機分配的內存地址都是可以預測的澄步,盡管預測的難度有點技術大冰蘑,但還是能預測的,所以都不是良好的隨機種子村缸。
什么是良好的隨機種子呢祠肥?因為虛擬機執(zhí)行過程的確定性,所以這個隨機種子我們不能從虛擬機中來找了梯皿,只能從外界來找了仇箱。
利用區(qū)塊中的數(shù)據(jù)作為隨機種子
昨天有一位同學在留言中提到了這個想法,他是這么說的:
他提到使用當前區(qū)塊的hash值
作為隨機種子东羹,這是很好的想法剂桥。
目前EOS的實際使用的tps平均在16左右,一般情況下属提,每個區(qū)塊都不止一個交易权逗,也就是說,黑客發(fā)起的攻擊性交易總會伴隨著其他的交易冤议,而其他的交易數(shù)據(jù)是黑客無法預測的斟薇,所以用當前區(qū)塊的hash值
作為隨機種子是比較優(yōu)良的隨機種子
。
然而恕酸,據(jù)我目前所知堪滨,當前EOS智能合約中還沒有獲得當前區(qū)塊的hash值的接口,如果你發(fā)現(xiàn)了蕊温,歡迎告訴我袱箱。倒是有獲取當前action所在的transaction的數(shù)據(jù),對于dice
合約而言义矛,這個東西黑客就是可以預測的了犯眠,因為交易是由黑客主動發(fā)起的,它完全知道自己的數(shù)據(jù)症革。
EOS智能合約中筐咧,還有獲得當前區(qū)塊的blocknumber
以及當前交易的ref block
的接口,不過這些也不能作為隨機種子,前者是可以預測的量蕊,后者是黑客可以自己指定的铺罢。
一個可能的方案
經(jīng)過我們的分析發(fā)現(xiàn),目前在EOS上残炮,好的隨機種子無法在智能合約內部獲得韭赘,那么只能求助外界了。我們看看外界目前有什么公共的或許可用的不可預測的噪音數(shù)據(jù)势就,我列出了如下幾條:
- RAM的交易數(shù)據(jù)
- 某些活躍度比較高的dapps公開的數(shù)據(jù)
這些數(shù)據(jù)的確是不可預測的泉瞻,任何一秒鐘都可能發(fā)生變化,并且變化的方式是極難預測的苞冯,他們可以作為隨機種子的一部分袖牙,但如果單純依靠它們中的任何一個,都是不夠可靠的舅锄。以RAM的交易數(shù)據(jù)為例鞭达,交易往往幾秒鐘才有一次,變化頻次不夠皇忿,黑客完全可以在這個變化的空檔期下手畴蹭。最活躍的賬號,和活躍的dapps公開的數(shù)據(jù)也是如此鳍烁,不過如果把它們結合起來一起用作隨機種子叨襟,可靠性會高很多。
不過這種方式也有缺點:
- 太依賴于別人的公開數(shù)據(jù)了幔荒,如果別人的數(shù)據(jù)格式變了糊闽,你的智能合約可能也要跟著更新
- 如果這些公開的數(shù)據(jù)變化的頻率,有時高铺峭,有時低墓怀,你必須收集足夠多的頻繁變化公開數(shù)據(jù)才能確保每時每刻的不可預測性
- 目前EOS上滿足條件的可作為隨機種子使用的公開數(shù)據(jù)仍然比較少
成熟可靠的方案
我們仍然需要沿著前面的思路,好的隨機種子只能從我們自己的智能合約以外去尋找卫键。我們發(fā)現(xiàn)其他的dapps的智能合約的公開的傀履、并且變化的數(shù)據(jù)可以作為隨機種子,但因為現(xiàn)階段EOS上的生態(tài)還在起步階段莉炉,可以作為隨機種子的數(shù)據(jù)還是太少钓账,可靠性不足。
那怎么辦呢絮宁?讓游戲的參與方提供隨機種子梆暮。
以dice
游戲為例,雙方同時提供隨機種子绍昂,然后智能合約把這兩個種子一起作運算啦粹,這個運算算法是公開的偿荷。因為雙方是同時提供的,所以任何一方都無法提前預知對方的種子是什么唠椭,自然無法對結果做出預測跳纳。
“可是不行啊,同時提供種子贪嫂,這在實操中很難辦到八伦!”
是的力崇,的確如此斗塘。不過可以用另外一個方法來解決這個問題。
EOSIO提供了一個dice
的示例合約亮靴,它是兩個玩家一起玩馍盟,不像EOSbet那種是玩家和項目方玩的。
我們先看看EOSIO提供的dice
示例的玩法:
- 用戶A和B各有一個自己的種子台猴,分別是
seedA
和seedB
朽合,但一開始互相保密的俱两。 - A對
seedA
進行hash運算
饱狂,生成seedA_hash
,然后把seedA_hash
發(fā)給智能合約宪彩。 - B對
seedB
進行hash運算
休讳,生成seedB_hash
,然后把seedB_hash
發(fā)給智能合約尿孔。 - 在上面的步驟完成之后俊柔,A把
seedA
發(fā)送給智能合約 - B把
seedB
發(fā)送給智能合約 - 智能合約先驗證A和B的seed,驗證通過后活合,再對兩個seed進行運算雏婶,根據(jù)運算結果判定輸贏。
在1白指,2之后留晚,B雖然先看到了A的seedA_hash
,但因為hash運算的性質告嘲,B無法通過seedA_hash
計算出seedA
错维,而智能合約的運算過程是對雙方的seed
進行的,所以B此時無法通過調整自己的seed
來影響運算結果的傾向性橄唬。于是B也只能老老實實的生成一個隨機的seedB
赋焕,并進行hash運算。
在5步的時候仰楚,B看到了seedA
隆判,自己還沒有發(fā)送seedB
犬庇,那這個時候他可以調整seedB
來影響結果了嗎?不行侨嘀,因為第6步械筛,智能合約會對seed
進行有效性驗證,具體是判斷這個等式是否成立:
hash(seed) == seed_hash
如果不成立飒炎,就代表這個驗證失敗了埋哟。seed_hash是玩家在第2和3步已經(jīng)發(fā)送給智能合約了的。同樣因為hash的性質郎汪,你想生成另外一個seed同時還能滿足上面的等式赤赊,是相當困難對策。
通過這種方式煞赢,dice的玩家雙方就可以不用顧慮誰先出示seed_hash
了抛计,也不用擔心誰先出示seed
了,這個過程巧妙的利用了hash運算的性質:
- 很難根據(jù)hash(seed)的結果倒推出seed
- 不同的seed進行hash之后照筑,生成的結果極大概率是不同吹截。這個極大概率無限接近于100%
hash運算在加密領域應用非常廣泛,并且有多種不同的hash算法凝危,這里就不展開了波俄。
改進的版本
上面的方案中,玩家雙方都各生成了一個隨機種子參與運算蛾默,所以任何一方都無法提前預測隨機結果懦铺,對于雙方都是公平的。
我們假設A支鸡、B雙方中冬念,A是用戶,B是項目方牧挣。項目方提供智能合約急前,并開源,然后采用上面的玩法瀑构,可以嗎裆针?
完全可行,B的相關操作可以根據(jù)A的請求自動執(zhí)行检碗,但A的操作就變復雜了据块。
以dice
合約為例,對于A而言折剃,最好的體驗是像EOSBet那樣另假,用戶只需要進行一次操作,即可得到輸贏的結果怕犁。然而在上面的方案中边篮,A卻需要兩步:第一步是向合約發(fā)送seed_hash
己莺,第二步是向合約發(fā)送seed
。
如何做到用戶A只需要一步合約操作就可以得到結果呢戈轿?
方案是有的凌受,我們再回頭看看上面的過程,第2步和第3步是可以替換的思杯,像下面這樣:
- 用戶A和B各有一個自己的種子胜蛉,分別是
seedA
和seedB
,但一開始互相保密的色乾。 - B對
seedB
進行hash運算
誊册,生成seedB_hash
,然后把seedB_hash
發(fā)給智能合約暖璧。 - A對
seedA
進行hash運算
案怯,生成seedA_hash
,然后把seedA_hash
發(fā)給智能合約澎办。 - 在上面的步驟完成之后嘲碱,A把
seedA
發(fā)送給智能合約 - B把
seedB
發(fā)送給智能合約 - 智能合約先驗證A和B的seed,驗證通過后局蚀,再對兩個seed進行運算麦锯,根據(jù)運算結果判定輸贏。
我們可以看到第3步和第4步是緊挨著的至会,于是我們就可以把它合成一個操作了离咐。變成一個步驟之后谱俭,seedA_hash
就不用提供了奉件,因為seedA_hash
本身是為了校驗seedA
的,現(xiàn)在在這個一次操作已經(jīng)給了seedA
昆著,那么seedA_hash
就沒有存在的必要的县貌。
如此,改進后的版本就變成下面這樣:
- 用戶A和B各有一個自己的種子凑懂,分別是
seedA
和seedB
煤痕,但一開始互相保密的。 - B對
seedB
進行hash運算
接谨,生成seedB_hash
摆碉,然后把seedB_hash
發(fā)給智能合約。 - A把
seedA
發(fā)送給智能合約 - B把
seedB
發(fā)送給智能合約 - 智能合約先驗證B的
seedB
是否合法脓豪,驗證通過后巷帝,再對兩個seed進行運算,根據(jù)運算結果判定輸贏扫夜。
如此就實現(xiàn)了用戶只需要一次智能合約交互即可楞泼,但項目方B還是需要做兩次智能合約交互驰徊,能不能再改進呢?
能的堕阔。
項目方B可以把seedB_hash
交給A棍厂,并由A在與智能合約的交互中帶上。于是超陆,整個過程就變成這樣了:
- 用戶A和B各有一個自己的種子牺弹,分別是
seedA
和seedB
,但一開始互相保密的时呀。 - B對
seedB
進行hash運算
例驹,生成seedB_hash
,然后把seedB_hash
交給A退唠。 - A把
seedA
以及seedB_hash
發(fā)送給智能合約 - B把
seedB
發(fā)送給智能合約 - 智能合約先驗證B的
seedB
是否合法鹃锈,驗證通過后,再對兩個seed進行運算瞧预,根據(jù)運算結果判定輸贏屎债。
這樣A和B與智能合約就分別只有一次交互了(省了點鏈上的資源消耗),而是在A和B之間增加了一次交互(鏈下的)垢油,很容易看出這次交互是安全的盆驹。
對于dice
合約來說,每次的游戲都是玩家發(fā)起的滩愁,也就是A發(fā)起的躯喇,而項目方B則是被動的,所以當A在把seedA
發(fā)送給智能合約之前硝枉,本身就需要通知B去生成seedB
和seedB_hash
廉丽,于是B就可以在收到A的通知的時候,順便把seedB_hash
返回給A妻味。
EOSBet或許也是這么做的正压,但因為它沒開源,我們不敢定論责球。不過倒是有一個開源的仿EOSBet的dapps: fairdicegame焦履,它的合約源碼在這里。這個項目使用的就是改進后的方案雏逾,不過因為它涉及到需要讓玩家A知道B是預先生成的seedB
嘉裤,所以它會把seedB_hash
預先發(fā)送給A,由A在與智能合約的交互中栖博,把seedB_hash
也帶上屑宠。
首先聲明一下,我和該項目沒有任何關系笛匙,卻無意間替他們做了宣傳侨把,我把他們的合約代碼地址放在這里犀变,主要是因為比較欣賞他們這種把合約開源的態(tài)度,同時也是為了方便智能合約的開發(fā)者們學習秋柄。
還能再改進嗎获枝?
上面方案從用戶使用的角度已經(jīng)能夠滿足需要了,同時也保證了公平公正骇笔,但在自證清白方面不夠徹底省店。因為在玩家操作期間,客戶端除了和智能合約有交互外笨触,客戶端還需要服務端進行一次交互懦傍,主要是為了在為玩家生成seed
以前,獲取服務端的seed_hash
芦劣,然后把服務
智能合約代碼已經(jīng)公開了粗俱,可以自證清白,但服務端就很難了虚吟。
以fairdicegame
為例寸认,首先它的服務端沒有開源,其次串慰,即便它開源了也無法自證清白偏塞,因為沒辦法驗證它開源的版本與實際使用的版本是同一個版本。智能合約因為可以進行源碼驗證邦鲫,所以沒有這個問題灸叼。
我看了一下fairdicegame
與服務端交互的數(shù)據(jù),從幾次的試驗來看庆捺,客戶端是在收到服務端的seedB_hash
之后古今,再把自己的seedA
和seedB_hash
發(fā)送給服務端的,所以服務端在生成seedB
的時候疼燥,是不知道seedA
的沧卢,從而可以認為fairdicegame
對于玩家來說還是比較公平的。不過因為客戶端的代碼和服務端代碼都完全是由項目方控制的醉者,假如項目方不定時的給了用戶另一個不公正的版本,用戶也很難感知到披诗。
你可能會說:“你是不是太嚴格了點兒撬即?”
是的,的確是嚴格了點呈队,我只有不停的嚴格要求自己剥槐,才能做出讓用戶更加可信的dapps。
另一方面宪摧,這種方式產(chǎn)生隨機數(shù)的方式粒竖,嚴格依賴于雙方共同參與隨機數(shù)的生成過程颅崩,盡管某些程序可以對交互邏輯進行優(yōu)化,但還是比較復雜蕊苗,這種復雜性也增加了隨機數(shù)生成場景的通用性沿后,比如區(qū)塊鏈游戲中生成寶物的隨機算法,完全是官方隨機生成的朽砰,整個過程無需用戶參與尖滚,上面適用于dice
的隨機數(shù)生成機制在這里就不合適了。
那如何改進呢瞧柔?
我們節(jié)后再探討漆弄。
國慶節(jié)快樂!