Salus 向 0xPARC 的 zk-bug-tracker 庫添加了一種新型的 ZK 漏洞,算術運算后缺乏多項式標準化, 該漏洞由以太坊基金會 PSE 安全團隊負責人 Kyle Charbonnet 審核。該漏洞會破壞假設并導致錯誤的計算,或者導致通過 rust panic 進行的拒絕服務攻擊。為了更好地理解這個漏洞,我們將以 Zendoo 庫中的一個具體實例進行說明。請大家對此類漏洞保持警惕。
1. 背景
在代碼中,多項式被表示為向量的形式。即,多項式 a0+a1x+...+an-1xn-1+an*xn 被表示為[a0,a1,...,an-1,an]。在 ZK 證明系統中,需要對多項式進行標準化操作,即將多項式的最高次項的系數調整為非零。比如,將[1,2,0]調整為[1,2]才是標準化的多項式表示。
對多項式進行標準化操作是必要的。如果不進行標準化,系統會錯誤地存儲多項式的最高次數,即它會大于其實際的最高次數。比如,對于[1,2,0],如果不進行標準化操作,它的最高次數會被錯誤地存儲為 2,而實際是 1。基于非標準化的多項式生成證明時,錯誤的多項式實現將會使得 ZK 證明系統 panic,導致無法生成證明。
2. 案例分析
算術運算后缺乏多項式標準化,該漏洞屬于 ZK 證明系統實現上的通用性漏洞。以下,我們以 Zendoo 庫中用于快速傅里葉變換(FFT)的密集多項式(dense polynomials)實現的代碼為例,說明其中存在的算術運算后缺乏多項式標準化的漏洞。
add() 函數是用來對兩個密集多項式(self 和 other)進行加法運算的,加法運算的結果(result)也是一個密集多項式,這需要進行標準化。然而,該函數僅在最后一個分支處對 result 進行了標準化操作(19-21 行)。該函數默認在前三個分支出計算得到的 result 就是標準化的,但這是不合理的。比如,當 self 是[1,2,3],other 是[1,2,-3],此時滿足第三個分支(7-12 行),即 self 和 other 這兩個多項式最高次數相等,都是 2。而在第三個分支處計算后的 result 是[2,4,0],并未對其進行標準化操作。
非標準化的多項式在之后的計算過程中會產生錯誤。具體的實現代碼如下:
而且,在這段代碼中,不只是在加法算法后缺乏多項式標準化。在加法運算前,self 和 other 作為 add() 函數的入參,也并沒有檢查他們是否是標準化的多項式表示。或者說,構造 self 和 other 的函數是否是按照標準化的方法進行構建的,也未可知。degree() 函數用來返回多項式的最高次項的指數。在 add() 函數中,非標準化的 self 和 other 在調用 degree() 函數時會引起 rust panic。
舉個例子,self 是非標準化的多項式 1+2x+0x2,即向量[1,2,0],其最高次項的系數為 0。當 other 也不是零多項式時,滿足 add() 函數的第三個分支,以 self 來調用 degree() 函數。在 degree() 函數中進入 else 分支。在 else 分支中有一個 assert! 宏,用來確保多項式的最高次數的系數不為 0。如果為 0,self.coeffs.last().map_or(false, |coeff| !coeff.is_zero()) 表達式結果為 false。即 self 向量的最后一個元素,即多項式的最高次項的系數為 0,返回 false。此時,assert! 宏會 panic。
Rust panic 會導致 ZK 證明系統遭受 DOS 攻擊。攻擊者可以通過構造大量的非標準化多項式,并且不斷調用 add() 函數。由于這些輸入會導致程序 panic,所以程序會不斷地停止并重啟。這將占用大量的計算和網絡資源,從而影響到其他正常用戶的使用,這就構成了一種 DOS 攻擊。
3. 總結
Salus 在 0xPARC 的 zk-bug-tracker 庫中添加的新型 ZK 漏洞,即算術運算后缺乏多項式標準化,是具有通用性的。在 ZK 證明系統中,我們需要特別注意避免該漏洞。該漏洞會造成 ZK 證明系統的計算錯誤,或使系統遭受 DOS 攻擊。可以在返回算術運算結果之前調用truncate_leading_zeros()函數進行標準化操作,同時,基于from_coefficients_vec()函數來構造標準化的多項式也是必要的。
針對此漏洞,Salus 團隊提醒 ZK 項目方,在構建多項式時和執行多項式操作之后對其進行標準化,以免破壞 ZK 證明系統的完整性。同時,強烈建議項目方在項目上線之前,尋求專業的安全審計公司進行充分的安全審計,確保項目安全。