详解 Tornado Cash V2 设计原理
DeFi
Signal
CORE
开发者 ameen.eth 将「Proof-of-Innocence」概念和 Tornado Cash 结合,提供另一个「隐私不等同于犯罪」的方向。
撰文:林玮宸 Albert Lin
前言
TornadoCash 是加密货币世界中著名的匿名交易服务。TornadoCash 利用 ZKP(Zero-Knowledge Proof)技术来隐藏资金来源。美国政府主张这样的机制助长了非法金流活动,最终在 2022 年 8 月被美国财政部制裁而被迫下架。隐私保护和洗钱活动在很多情况下似乎总是紧密相连。在追求隐私的同时,不法分子往往利用这些隐私特性进行非法资金清洗。是否能找到一种方法,既能让人们拥有隐私,又能有效遏制洗钱活动? TornadoCash 早期开发者 ameen.eth 的 Privacy-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 0 和 1 的位置在 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 资料为 allowed 和 blocked。
- 领款者除了需要提供建构 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 文章和给出宝贵的意见!
Reference:
Stealth Address
Tornado Cash 實例解析
ZKP 與智能合約的開發入門
[ZKP 讀書會] Tornado Cash
Tornado Cash — How it Works | DeFi + Zero Knowledge Proof
https://github.com/tornadocash/tornado-core
深入理解 TornadoCash 技术细节
https://www.privacypools.com/
https://github.com/ameensol/privacy-pools
0xhhh Thread 介紹新功能和其原理
demo video
Vitalik’s 講如何改進 Tornado v2
privacy pools v0 tweet
Introducing Proof of Innocence built on Tornado Cash
免责声明:
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表Bi123的观点或立场