本文翻譯自: https://baincapitalcrypto.com/chosen-instance-attack/#conclusion
- Threat model
- Leaky primitives
- Non-interactive proofs
- Chosen-instance attacks in the wild
- Conclusion
如果一個證明僅僅是SNARK, 但不是zkSNARK會有什麼問題? 人們通常會產生誤解: 單個SNARK太少了, 並不會洩露關於witness的資訊. 這顯然是錯誤的. 事實上, 單個SNARK會洩露關於 witness 的資訊, 但是不會洩露 full witness(我們將在這篇部落格中討論). 值得注意的是, 今年的 ZK Hack V puzzle 就是基於這個主題.
在這片文章中, 我們將展示一種名為 "chosen-instance attack" 的攻擊, 類似於加密中的 chosen-plaintext attack. 敵手要求證明者生成對於 \((x_0, w), (x_1, w), (x_n, w)\) 的證明. 拿到這些證明敵手可以透過插值法還原出原來的 witness polynomial.
本文假設讀者明白以下術語的含義: "NP", "instance", "witness", "SNARK", "IOP/polyIOP".
Threat model
我們考慮非互動的 proof(或argument), 模型如下. 敵手 \(\mathcal A\) 知道電路的公共輸入, 與一個誠實的證明者 \(\mathcal P\) (knows witness)進行互動. 敵手是自適應的(adaptive), 可以自己選擇 statement 來讓證明者證明. 最終, 敵手必須輸出證明者的 witness. 整個模型可以用下面的圖來描述.
這種設定在實際中是常見的. 例如, 在有 client-server 的應用中, client往往相當於誠實的證明者, 具有一些 witness, 而 server 通常是敵手. 這也是我們為 ZK hack puzzle 選擇的環境. 另外, 我們可以想象一個不受信任的伺服器, 他需要生成證明去證明自己是誠實的. 一個惡意的伺服器可能會嘗試透過選擇特定的 statement 讓伺服器證明, 來抽取出伺服器的秘密.
Leaky primitives
IOPs 大部分SNARK都是基於IOP或者他的變形polyIOP. IOP的結構通常是這樣的.
第 \(1\) 輪. 證明者傳送一個 oracle(可以理解為由 witness 編碼而來的一個多項式).
第 \(i\) 輪. 證明者傳送一個 "helper" oracle. 讓驗證者把 "檢查witness" 這個任務 歸約到一個更簡單的問題上.
最後一輪. 驗證者在一個或多個點上詢問 witness 和 helper oracles.
這個構造至少洩露 witness polynomial 的一個值. 如果 witness 被編碼成一個 \(d\) 次的多項式, 那麼敵手可以收集 \(d+1\) 個證明, 然後使用這些值透過插值來恢復整個 witness polynomial! 這些協議要求Prover開啟 \(k\) 個witness value, 從而需要收集的證明數量從 \(d+1\) 減少到了 \((d+1)/k\).
我們一般使用兩種方法把IOP編譯成SNARK. 分別是透過多項式承諾和Merkle tree.
Compilation using polynomial commitments 使用多項式承諾方案對 Prover 的訊息進行承諾, 然後透過FS轉換變為非互動. 大多數 elliptic curve SNARK 是透過這種方法產生的. 這種方式通常不會洩露許多witness的值.
Compilation using IOPPs and Merkle tree 第二種方法是呼叫一個另外的協議, 生成 Prover 的訊息離某個多項式很近. 這就是 IOP of proximity(IOPP), 比如FRI[BBHR18], STIR[ACFY24]. IOP+IOPP能夠透過BCS transform[BCS16] 編譯成 SNARK. 對Prover的每個訊息進行 Merkle Tree 承諾, 再透過 FS 轉換變為非互動的. 大多數hash-based SNARK就是這樣產生的. IOPP compilation 會洩露 witness polynomial 額外的求值(因為每次會要求驗證者重複開啟多項式的點, 根據引數和安全性, 通常每個證明會開啟20~50個點).
Non-interactive proofs
正如我們上面所說: 互動式的SNARK會洩露 witness polynomial 的求值. 在互動的情況下抽取出證明者的 witness 是相對簡單的. 我們只需要作為驗證者誠實地執行協議, 保證每次詢問的點是不同的, 次數足夠多之後我們可以透過插值法獲得 witness polynomial.
在非互動的情況下抽取出 witness 沒那麼簡單. 因為我們需要使用 Fiat-Shamir transform. 在給定證明者先前的訊息後, Verifier的訊息是確定的( instance 與 previous message 的 hash). 對於一個固定的 \((x, w)\), 誠實的證明者會返回固定的 proof, 這就讓敵手不能獲得 witness polynomial 的多個值, 從而不能獲得整個 witness.
很容易想到一個解決方法: 如果我們能獲得這樣一組例項的證明就好了: \((x_1, w), (x_2, w), \cdots, (x_n, w)\). 即多個不同的例項, 但是他們的 witness 相同. 但這樣的條件是否有點太強, 是否是不可能的呢? 在下一節中, 我們會說明實際上很普通的電路就會滿足這個條件. 現在我們先假設這樣的集合是存在的.
使用不同的例項的直接結果就是: Verifier的訊息在每次證明中都不一樣, 所以可以獲得不同的evaluation. 然而還有一些問題. 在一些算數化中, Prover的第一條訊息既依賴witness \(w\), 也依賴instance \(x\). 如果我們使用不同的 instance, Prover的第一條訊息會每次都不同, 這似乎不利於我們的插值攻擊.
幸運的是這個問題也可以解決. 我們可以讓敵手把 witness 部分的多項式和 instance 部分的多項式分開. 我們用 \(\tilde{z}\) 表示combined instance-witness polynomial: 包含了instance polynomial \(\tilde{x}\) 和 witness polynomial \(\tilde{w}\). 前者敵手是可以自己算出來的. 因此敵手也可以透過某種關係計算出 witness polynomial: 給定一個evaluation \(\tilde{z}(p)\), 敵手可以求出 \(\tilde{x}(p)\), 並計算 \(\tilde{w}(p) = \tilde{z}(p) - \tilde{x}(p)\). 下圖我們用R1CS來表示, 當然可以等價地被應用到 PLONKish 或者 AIR table.
因此我們上面說的攻擊可以正常進行: 收集足夠多的 proofs, 然後插值得到 witness.
Chosen-instance attacks in the wild
乍一看, 我們描述的攻擊似乎需要一個非常人為的電路: 許多instance, 但他們可以用同樣的 witness 滿足. 另外, 敵手一開始擁有一個instance, 那麼他怎麼產生更多同樣 witness 的不同instance呢?
其實很多電路都會有這個性質. 特別地, 當區塊鏈中的 "nonces" 或者 "nullifiers" 被使用時. 當我們處理非互動證明時, 我們一定需要注意: 不要讓 proofs 被重新使用. 例如, Alice產生一個證明: 她的賬戶裡有超過32個ETH. 那麼我們需要保證 Alice 不能在不更新證明的情況下, 花費一些ETH並仍然使用之前的證明(這樣她能永遠證明自己擁有超過32個ETH). 在實際中, 這通常需要向原來的證明中引入一個public number(nonce). 如果這個 proof 被驗證過了, 那麼公開的 nonce 會被加入到日誌中, 並且這個 proof 會被標記為"已使用". 如果同樣的 proof 又被拿出來用, 這時 Verifier 就能識別這個用過的 proof, 並且拒絕. 因此這個 proof 就不會被接受.
這個 "nullifying" 機制對於阻止重放攻擊(重新使用原來的proof)是很好的嘗試. 但這個機制就讓 Prover 能夠用相同的 witness 生成不同的 instances: 我們現在有公開的輸入 \((x, \text{nonce})\), 以及證據 \(w\). 敵手可以自己留著固定的 \(x\), 然後使用不同的 \(nonce\). 這是證明者總是使用同一個witness \(w\).
Conclusion
透過上面的分析我們知道: 簡潔的證明是不足以保護隱私的(儘管單獨的一個簡潔證明不會洩露整個witness). 大多數簡潔證明都會洩露關於witness的部分資訊. 因此,
在某些情況下, 尤其是使用 nonces 去阻止重放攻擊, 我們可以利用上面的選擇例項攻擊來恢復出整個 witness. 這個攻擊對任何滿足特定條件的電路都有效(多個 instance, 同一個 witness), 對於IOPP編譯而來的SNARK尤其有效(FRI, STIR), 因為這些SNARK每一個 proof 會洩露大量的 evaluation(而不是一個).
我們所說的 chosen-instance attack 只是這種洩露的其中一個idea. 當然還有很多其他攻擊的方法. 我們可以用一句廢話來總結這篇文章: 如果一個系統的證明不是零知識的, 那麼這個系統不保證隱私.