TornadoCash V2: Privacy-Pools 及 Proof-of-Innocence

林瑋宸 Albert Lin
Taipei Ethereum Meetup
14 min readJun 2, 2023

前言

TornadoCash 是加密貨幣世界中最著名的匿名交易服務。TornadoCash 利用 ZKP(Zero-Knowledge Proof)技術來隱藏資金來源。美國政府主張這樣的機制助長了非法金流活動,最終在 2022 年 8 月被美國財政部制裁而被迫下架。隱私保護和洗錢活動在很多情況下似乎總是緊密相連。在追求隱私的同時,不法分子往往利用這些隱私特性進行非法資金清洗。是否能找到一種方法,既能讓人們擁有隱私,又能有效遏制洗錢活動? TornadoCash 早期開發者 ameen.ethPrivacy-Pools 或許給出了一個方向。(關於下架的部分只有前端網站和 GitHub Repository 受到影響,合約部份因在區塊鏈上則不受影響。最後在電子前哨基金會的爭取之下 GitHub 有恢復 Repository,詳情可以參考

簡介 TornadoCash 原理

在介紹 Privacy-Pools 之前,需要先理解 TornadoCash 相關的設計原理。詳細介紹可以參考我之前的文章 「 Breaking Down TornadoCash: A Beginner’s Guide to Explaining its Functionality to Friends 」。這邊簡單複習一下 TornadoCash 的設計原理。

TornadoCash 使用收據( commitment)來控制訪問權限。收據是由 secret(秘密值)和nullifier (註銷碼)一起 Hash 產生,每個收據只能提款一次。使用 Merkle Tree (雜湊樹)記錄存款信息,將收據作為 leaf 節點並計算出 Merkle Root (雜湊樹根)。使用者只需提供 leaf 到 root 中間經過的數據,即可證明該數據是否 Merkle Tree 的 leaves 之一,也間接證明之前有存款資金到 TornadoCash。使用 Zero-Knowledge Proof 來隱藏存款來源,另外使用 nullifier 防止Double Withdrawal 攻擊。

TornadoCash 的收據有兩個含義

  • 證明之前發送者有存入資金
  • 確保每個接收者只能領取一次資金

“Proof-of-Innocence”

根據 TornadoCash 設計的原理,只能知道領款的資金一定是來自之前的某筆存款的資金,但卻不知道是來自哪一筆存款。這是不法份子使用 TornadoCash 進行不法的洗錢活動的主要目的,也是美國政府為什麼要監管 TornadoCash 原因。若我們可以提出另外的 Proof(ZKP),來證明領款時的資金不是來自拒絕清單的存款,是否就能證明這筆領款並非來自於不法份子,這就是 “Proof-of-Innocence” (“無罪證明”)核心概念。

“Proof-of-Innocence” 概念可以分成兩個方向

  • 證明領款的資金是來自允許清單中存款的資金集合(允許清單)
  • 證明領款的資金並沒有來自拒絕清單的存款資金集合(拒絕清單)

這兩種做法都是可以證明領款資金都不是來自於拒絕清單裡的存款。下圖的做法是採用拒絕清單的方式,證明領款的資金並非來自於拒絕清單的存款(紅色)。

source: https://medium.com/@chainway_xyz/introducing-proof-of-innocence-built-on-tornado-cash-7336d185cda6

Privacy-Pools 設計原理

Privacy-Pools 在 TornadoCash 基礎上多加上了 “Proof-of-Innocence” 的概念。Privacy-Pools 領款收據除了 TorandoCash 收據原本代表的意義之外,還有第三個意思:『 證明領款的資金來自於允許清單中的存款 』。

Privacy-Pools 收據代表意義:

  • 證明之前發送者有存入資金
  • 確保每個接收者只能領取一次資金
  • 證明領款的資金來自於允許清單中的存款

這邊我們使用 Allow Merkle Tree 來解釋 Privacy-Pools 是如何把 “Proof-of-Innocence” 運用在這個系統中(Allow Merkle Tree 是運用允許清單的概念)。首先 Allow Merkle Tree 有幾個特點

  • Allow Merkle Tree 顧名思義是一個 Merkle Tree
  • 樹高和節點數都跟 Privacy-Pools 的 Deposit Merkle Tree 相同。
  • Allow Merkle Tree 的 leaf 皆對應 Deposit Merkle Tree leaf(存款) 位置。

Allow Merkle Tree leaf 資料可以分成下面二類:

  • allowed:表示該位置的存款是允許。(尚未有存款的位置也是預設allowed
  • blocked:表示該位置的存款是拒絕。

下圖可以看到 index 01 的位置在 Deposit Merkle Tree 皆為 legal funds,對應的 Allow Merkle Tree leaf 位置也是 allowed

假設今天有一名不法份子想要進行洗錢活動,他把攻擊後所得不法資金存進 Privacy-Pools ,存款位置是 Deposit Merkle Tree index:2 的位置。我們知道那是不法的資金,所以在對應的 Allow Merkle Tree index:2位置我們更新為 blocked

允許清單領款情形

假設今天有一名在美國政府允許清單存款的用戶想要提領資金,需要提供『 有存款在 Dposit Merkle Tree 中的證明 』之外,還需要提供『 在美國允許清單的 是 Allowed 的證明 』。對應美國允許清單的證明包含了 Allow Merkle Root (由用戶自行提供,在程式碼中為 subetRoot)和中途會經過的 node 值。Privacy-Pools 在驗證 ZKP 階段時,會以 leaf 值為 allowed (實際程式碼中是 keccak256(allowed))和給定中途會經過的 node 值去建構出 Merkle Root。驗證此 Merkle Root 與用戶提供的 Allow Merkle Root 是否相同。若相同代表驗證通過,表示該領款的資金是來存在於美國政府允許清單中的存款。

拒絕清單領款情形

今天有一名不在美國政府允許清單存款的用戶想要提領資金 ,而對應的存款的位置在美國政府允許清單中被標記成 blocked。這樣會導致用戶無法使用美國政府允許清單的 Allow Merkle Root 去領出資金,因為產生不出對應的證明而導致驗證失敗(因為 Privacy-Pools 使用leaf 是值 allowed 去做計算,而美國政府允許清單將該位置標記成 blocked,導致計算不出相同的 Merkle Root )。

這樣的設計被迫這名用戶需要提供其他的 Allow Merkle Root 才能提領資金(其他的 Allow Merkle Tree 需將該存款位置標記成 allowed,才能計算出相同的 Allow Merkle Root 來通過驗證)。這個其他的 Allow Merkle Tree 可能來自於其他政府或機構所維護,甚至是這名用戶自行產生。今天美國政府就可以藉由提領時所用的 Allow Merkle Root, 來判斷用戶的資金是否符合美國政府的法律規範,藉此來達到追蹤的目的。若用戶是使用自己產生或不具公信力的的 Allow Merkle Tree ,基本上該筆領款資金極有可能來自有問題的存款(每個具公信力的第三方 Allow Merkle Tree 都將該存款位置標記成 blocked)。

常見的問題

Q: 如果 Allowroot 是由提領人提供,是不是可以假造一個假 Allow Merkle Root 來證明該 leaf 是允許清單中的存款,是不是代表還是可以把錢領走呢?

A: 答案是肯定的,確實是可以把錢領走。這一點作者有特別提出說,這樣的機制並不是要禁止不法份子把錢領走,而是就算可以領走也會被知道說這筆資金是拒絕清單的資金。當提領人提供了一個沒有說服力的 Allow Merkle Root,基本上可以視為他是從拒絕清單存款提領。筆者這邊猜測允許這樣做的原因是想要保持這個服務的去中心化性質。因為每個 Allow Merkle Tree 都需要有一定的權限管理去更新每個 leaf 的 status。 若強制指定某個 allow tree root 的話,代表有人有一定權限控制資金的提領,這一點並不符合去中心化的精神。

Q: 會是由誰來決定這筆交易是來自於拒絕清單資金呢?

A: 筆者看到的部分並沒有特別提,理解是這部分應該由各監管單位自己去做。假設今天美國政府要查 Privacy-Pools 的髒錢,他可以透過去檢查每筆的 Allow Merkle Root 來判斷他是不是髒錢。至於怎樣的 Allow Merkle Root 是允許,那就是各監管單位他們自己去判斷。

Privacy-Pools 程式碼

這裡附上主要的程式碼和筆者自己的註解,希望可以幫助大家可以透過程式碼理解主要邏輯。

// circuits/withdraw_from_subset.circom
template WithdrawFromSubset(levels, expectedValue) {
// public
signal input root;
signal input subsetRoot;
signal input nullifier;
signal input assetMetadata; // abi.encode(token, amount).snarkHash();
signal input withdrawMetadata; // abi.encode(recipient, refund, relayer, fee).snarkHash();

// private
signal input secret;
signal input path; // Indicate whether the data represents the left leaf or the right leaf.
signal input mainProof[levels]; // Construct the data required for deposit root.
signal input subsetProof[levels]; // Construct the data required for allow root.

// Calculate the nullifier and commitment.
component hasher = CommitmentNullifierHasher();
hasher.secret <== secret;
hasher.path <== path;
hasher.assetMetadata <== assetMetadata;
nullifier === hasher.nullifier;

// expectedValue: keccak256("allowed") % p
component doubleTree = DoubleMerkleProof(levels, expectedValue);
doubleTree.leaf <== hasher.commitment;

// Convert the path to bits to specify whether it is the left leaf or the right leaf.
// It can be observed that the deposit tree and allow tree share the same path.
doubleTree.path <== path;
for (var i = 0; i < levels; i++) {
doubleTree.mainProof[i] <== mainProof[i];
doubleTree.subsetProof[i] <== subsetProof[i];
}
root === doubleTree.root; // Verify the deposit root.
subsetRoot === doubleTree.subsetRoot; // Verify the allow root.

signal withdrawMetadataSquare;
withdrawMetadataSquare <== withdrawMetadata * withdrawMetadata;
}

TLDR

  • “Proof-of-Innocence” 是用另一個 Proof 來證明該筆領款是來自於允許清單中的存款。“Proof-of-Innocence” 可以從允許清單拒絕清單兩個角度來建構。
  • Privacy-Pools 在 TornadoCash 基礎上疊加了 ”Proof-of-Innocence“ 的概念,原本的收據代表意義多了第三個意思:『 證明領款的資金來自於允許清單中的存款 』。
  • Allow Merkle Tree 存在可以證明提領資金是存在允許清單中。 Allow Merkle Tree 的 leaf 位置相對應於 Deposit Merkle Tree 的存款 leaf 位置。Allow Merkle Tree leaf 資料為 allowedblocked
  • 領款者除了需要提供建構 Deposit Merkle Root 所需資料之外,還需要另外提供 Allow Merkle Root 和建構 Allow Merkle Root 的資料來證明提領資金的是存在允許清單中。
  • 由於 Allow Merkle Root 是由領款者提供,不法份子仍有辦法透過假造的 Allow Merkle Root 來把不法資金領走。假造的 Allow Merkle Root 依然會呈現在鏈上並被其他人視為這筆領款仍有存有疑慮,藉此來達到追蹤不法資金的流向。

開發者 ameen.eth 將 ”Proof-of-Innocence“ 概念和 TornadoCash 結合,提供另一個『 隱私不等同於犯罪 』的方向。筆者覺得有趣的角度是利用另一個 ZKP 來證明另一件事實,有點像是 ZKP 的加法。這樣的使用方式會比建構一個更大行更複雜的 ZKP 更來為簡單,效率也更高。關於 Allow Merkle Tree 的選擇,感覺之後會是由一個比較公正的單位來建構,這樣對於其他人也有比較高的說服性。

最後感謝 Chih-Cheng Liang 以及 Ping Chen 幫忙 Review 文章和給出寶貴的意見!

TEM Medium 2023 有獎徵稿
TEM Medium 目前正在進行有獎徵稿!詳情請參考:
https://medium.com/taipei-ethereum-meetup/tem-medium-call-for-papers-with-prize-2023-q1-f384828f902f

--

--