開發者在進行密碼學開發時需要注意驗證群的階數。
By: Victory
概述
此前,俄羅斯開發者poma 在Semaphore 上發現了一個零知識證明驗證合約存在雙花漏洞(詳見https://github.com/semaphore-protocol/semaphore/issues/16)。出於興趣,想先嘗試復現一下該漏洞的PoC,但由於漏洞代碼是很久以前的代碼,且該項目相對複雜,因此決定自己編寫一個簡單的PoC 來復現漏洞。
前置介紹
零知識證明(ZKP)技術的核心是一個叫做「證明系統」的算法。該算法通過對消息進行一系列的計算,生成一個證明,用於證明消息的真實性。接收者無需擁有其它信息,只需驗證證明,即可確認消息的真實性。
ZKP 的實現有許多種實現方案,我們在之前的文章《盤點ZKP 主流實現方案技術特點》里為大家介紹了各種證明系統及編程平台,本次實驗中使用的正是其中的Circom 平台。
Circom 使用Groth16 和PlonK 作為其證明系統,在開發過程中我們可以任選其一,開發框架可以在不需要改變電路的情況下自動為開發者生成證明參數和驗證合約。
簡單來說,Circom 通過在客戶端生成見證數據和證明數據,將這些數據提交到合約。 verifier.sol 合約負責對提交的數據進行校驗,以驗證證明是否是在規定的規則下生成的。這種方法可以實現快速、高效和安全的驗證,無需暴露消息的具體內容,保護消息的隱私性。
漏洞解析
1、話不多說,我們直接上問題代碼,請看下圖的verifyHash 函數。圖中紅框內的代碼是記錄某次見證數據是否有使用過,這種用法在防止雙花上是比較常見的。但是此次漏洞的出現就是出現在這個見證數據hash1 上。按照正常的理解一組proof 數據應該只能匹配一組hash1 進行驗證。
2、在verifier.sol 合約中,函數verify(uint[] memory input, Proof memory proof) 的作用是對傳入的數值進行橢圓曲線計算校驗。該函數利用名為scalar_mul() 的函數實現了橢圓曲線上的標量乘法。具體地,它會使用輸入的參數對橢圓曲線進行計算,並比較計算結果與給定證明中的值是否相等,以確定輸入值是否有效。
3、在Solidity 智能合約中,需要使用uint256 類型來編碼Fq。但是,由於uint256 類型的最大值大於q 值,可能會出現多個不同的整數在進行mod 運算後會對應到同一個Fq 值的情況。例如,s 和s+q 實際上表示同一個點,即第s 個點。同樣的,s+2q 等等也都對應到點s。這種現像被稱為「Input Aliasing」,也就是這些數互為假名。
這裡的q 值是指循環群的階數,也就是可以輸入多個大整數會對應到同一個Fq 中的值的數量。簡單來說,即使將hash 加上一個q 值,仍然可以通過驗證。在uint256 類型的範圍內,最多有uint256_max/q 個不同的整數可以表示同一個點。這意味著一組證明最多可以有5 個匹配的hash1 能夠通過合約的驗證。
漏洞復現
1、實現一個簡單的電路輸入2 個數據返回一個見證數據,就是在合約裡面用到的hash1。
2、對電路進行編譯生成circuit_final.zkey, circuit.wasm 和verifier.sol。接著生成一組proof,一個正常的hash,一個攻擊hash。
3、隨後部署合約,使用前面生成的checkHash 進行一次驗證,驗證通過。
4、接下來在使用相同的見證數據與前面生成的attackHash,發現驗證一樣是通過的。這說明了一組proof 可以有多個匹配的hash 能通過合約的校驗。至此Circom 驗證合約輸入假名漏洞復現成功。
漏洞的解決方案
此次漏洞是由於一組證明可以有最多5 個匹配的hash 能在合約上通過驗證。所以漏洞修復也很簡單,就是限制所有輸入的hash 都要小於q 值。
總結
輸入假名漏洞在零知識證明及密碼學實現裡是一個比較通用的漏洞,本質原因是數值在有限域內取餘相同,開發者在進行密碼學開發時需要注意驗證群的階數。
展開全文打開碳鏈價值APP 查看更多精彩資訊