zkSNARK合約「輸入假名」漏洞致眾多混幣項目爆雷
安比(SECBIT)实验室
2019-07-29 07:41
本文约4349字,阅读全文需要约17分钟
希望這波新技術浪潮中,社區能充分吸收以往的慘痛教訓,重視安全問題。

編者按:本文來自 編者按:本文來自(ID:secbitlabs)編者按:本文來自

大量零知識證明項目由於錯誤地使用了某個zkSNARKs 合約庫,引入「輸入假名(Input Aliasing) 」漏洞,可導致偽造證明、雙花、重放等攻擊行為發生,且攻擊成本極低。眾多以太坊社區開源項目受影響,其中包括三大最常用的zkSNARKs 零知開發庫snarkjs、ethsnarks、ZoKrates,以及近期大熱的三個混幣(匿名轉賬)應用hopper、Heiswap、Miximus。這是一場由Solidity 語言之父Chris 兩年前隨手貼的一段代碼而引發的血案。

二級標題

二級標題

二級標題

雙花漏洞:最初暴露的問題

semaphore 是一個使用零知識證明技術的匿名信號系統,該項目由著名開發者barryWhiteHat 此前的混幣項目演化而來。

俄羅斯開發者poma 最先指出該項目可能存在雙花漏洞[1]。

該問題最早可追溯到2017 年,由Christian Reitwiessner 大神,也就是Solidity 語言的發明者,提供的zkSNARKs 合約密碼學實現示例[3]。其後,幾乎以太坊上所有使用zkSNARKs 技術的合約,都照用了該實現。因此都可能遭受以下流程的攻擊。

二級標題

二級標題

二級標題

混幣應用:該安全問題的重災區

零知識證明技術在以太坊上最早和最廣泛的應用場景是混幣合約,或匿名轉賬、隱私交易。由於以太坊本身不支持匿名交易,而社區對於隱私保護的呼聲越來越強烈,因此湧現出不少熱門項目。這里以混幣合約的應用場景為例,介紹「輸入假名」漏洞對零知項目的安全威脅。

混幣合約或匿名轉賬涉及兩個要點:

1. 證明自己有一筆錢

2. 證明這筆錢沒有花過

為了方便理解,這裡簡單描述一下流程:

1. A 要花一筆錢。

2. A 要證明自己擁有這筆錢。 A 出示一個zkproof,證明自己知道一個hash (HashA) 的preimage,且這個hash 在以root 為標誌的tree 的葉子上,且證明這個preimage 的另一種hash 是HashB。其中HashA 是witness,HashB 是public statement。由於A 無需暴露HashA,所以是匿名的。

很多zkSNARKs 合約尤其是混幣合約,核心邏輯都與第82 行和83 行類似,因此都存在同樣的安全問題,可利用「輸入假名」漏洞進行攻擊。

二級標題

二級標題

二級標題

漏洞解析:一筆錢如何匿名地重複花5 次?

上面verifyProof(a, b, c, input)函數的作用是根據傳入的數值在橢圓曲線上進行計算校驗,核心用到了名為scalar_mul()的函數,實現了橢圓曲線上的標量乘法[4 ]。

我們知道以太坊內置了多個預編譯合約,進行橢圓曲線上的密碼學運算,降低zkSNARKs 驗證在鏈上的Gas 消耗。函數scalar_mul()的實現則調用了以太坊預編譯7 號合約,根據EIP 196 實現了橢圓曲線alt_bn128 上的標量乘法[5]。下圖為黃皮書中對該操作的定義,我們常稱之為ECMUL 或ecc_mul。

密碼學中,橢圓曲線的{x,y}的值域是一個基於mod p 的有限域,這個有限域稱之為Zp 或Fp。也就是說,一個橢圓曲線上的一個點{x,y}中的x,y 是Fp 中的值。一條橢圓曲線上的某些點構成一個較大的循環群,這些點的個數稱之為群的階,記為q。基於橢圓曲線的加密就在這個循環群中進行。如果這個循環群的階數(q)為質數,那麼加密就可以在mod q 的有限域中進行,該有限域記作Fq。

一般選取較大的循環群作為加密計算的基礎。在循環群中,任意選定一個非無窮遠點作為生成元G(通常這個群的階q是個大質數,那麼任選一個非零點都是等價的),其他所有的點都可以通過G+ G+. ...產生出來。這個群裡的元素個數為q,也即一共有q個點,那麼我們可以用0,1,2,3,....q-1 來編號每一個點。在這裡第0 個點是無窮遠點,點1 就是剛才提到的那個G,也叫做基點。點2就是G+G,點3就是G+G+G。

於是當要表示一個點的時候,我們有兩種方式。第一種是給出這個點的坐標{x,y},這裡x,y屬於Fp。第二種方式是用n*G 的方式給出,由於G 是公開的,於是只要給出n 就行了。 n 屬於Fq。

看一下scalar_mul(G1Point point, uint s) 函數簽名,以point 為生成元,計算point+point+.....+point,一共n 個point 相加。這屬於使用上面第二種方法表示循環群中的一個點。

在Solidity 智能合約實現中需要使用uint256 類型來編碼Fq,但uint256 類型的最大值是大於q 值,那麼會出現這樣一種情況:在uint256 中有多個數經過mod 運算之後都會對應到同一個Fq中的值。比如s和s + q表示的其實是同一個點,即第s個點。這是因為在循環群中點q其實等價於點0(每個點分別對應0,1,2,3,....q-1)。同理,s + 2q等均對應到點s。我們把可以輸入多個大整數會對應到同一個Fq中的值這一現象稱作「輸入假名」,即這些數互為假名。

因此,當用戶向合約出示零知識證明進行提現後,合約會把input[1](也就是某個s)放入作廢列表。用戶(或其他攻擊者)還可以使用另外4 個值再次進行證明提交。而這4 個值之前並沒有被列入「廢棄列表」,因此“偽造”的證明可以順利通過校驗,利用5 個「輸入假名」一筆錢可以被重複花5 次,而且攻擊成本非常低!

二級標題

二級標題

這當中,影響最大的要數幾個大名鼎鼎的zkSNARKs 庫或框架項目,包括snarkjs、ethsnarks、ZoKrates 等。許多應用項目會直接引用或參考他們的代碼進行開發,從而埋下安全隱患。因此,上述三個項目迅速進行了安全修復更新。另外,多個利用了zkSNARKs 技術的知名混幣項目,如hopper、Heiswap、Miximus 也立刻進行了同步修復。這些項目在社區熱度都十分高,其中Heiswap 更是被人們稱為「Vitalik 最喜愛的項目」。

二級標題

二級標題

所幸的是,修復很簡單。僅需在驗證函數中添加對輸入參數大小的校驗,強制要求input 值小於上面提到的q 值。即嚴禁「輸入假名」,杜絕使用多個數表示同一個點。

二級標題

二級標題

二級標題

暴露的深層問題值得反思

核心問題出在哪裡?可能出在底層庫的實現者和庫的使用者雙方間對於程序接口的理解出現了偏差。換句話說:底層庫的實現者對於應用開發者的不當使用方式欠缺考慮;而上層應用開發者沒有在使用中沒有深入理解底層實現原理和注意事項,進行了錯誤的安全假設。

參考文獻

[1]https://github.com/

[2]https://github.com/

[3]https://gist.github.com/

[4]https://github.com/

[5]https://github.com/

安比(SECBIT)实验室
作者文库