🔥 Gate 动态大使专属发帖福利任务第三期报名正式开启!🏆 第二期获奖名单将于6月3日公布!
👉️ 6月3日 — 6月8日期间每日发帖,根据帖子内容评级瓜分 $300奖池
报名即可参与:https://www.gate.com/zh/questionnaire/6761
报名时间:6月3日10:00 - 6月8日 24:00 UTC+8
🎁 奖励详情:
一、S级周度排名奖
S级:每周7日均完成发帖且整体帖子内容质量分数>90分可获S级,挑选2名优质内容大使每人$50手续费返现券。
二、A/B 等级瓜分奖
根据各位动态大使发帖数量及帖子内容质量获评等级,按评定等级获奖:
A级:每周至少5日完成发帖且整体帖子内容质量90>分数>80可获A级,从A级用户中选出5名大使每人$20手续费返现券
B级:每周至少3日完成发帖且整体帖子内容质量80>分数>60可获B级,从B级用户中选出10名大使每人$10手续费返现券
📍 活动规则:
1.每周至少3日完成发帖才有机会获奖。
2.根据发帖天数和整体发帖内容质量分数给予等级判定,分为S/A/B等级,在各等级下选择幸运大使获奖。
💡 帖子评分标准:
1.每帖不少于30字。
2.内容需原创、有独立见解,具备深度和逻辑性。
3.鼓励发布市场行情、交易知识、币种研究等主题,使用图例或视频可提高评分。
4.禁止发布FUD、抄袭或诋毁内容,违规将取
详解 Tornado Cash V2 设计原理
撰文:林玮宸 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」概念可以分成两个方向
这两种做法都是可以证明领款资金都不是来自于拒绝清单裡的存款。下图的做法是採用拒绝清单的方式,证明领款的资金并非来自于拒绝清单的存款(红色)。
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 leaf 资料可以分成下面二类:
下图可以看到 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 ( 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
开发者 ameen.eth 将「Proof-of-Innocence」概念和 TornadoCash 结合,提供另一个「隐私不等同于犯罪」的方向。笔者觉得有趣的角度是利用另一个 ZKP 来证明另一件事实,有点像是 ZKP 的加法。这样的使用方式会比建构一个更大行更複杂的 ZKP 更来为简单,效率也更高。关于 Allow Merkle Tree 的选择,感觉之后会是由一个比较公正的单位来建构,这样对于其他人也有比较高的说服性。
最后感谢 Chih-Cheng Liang 以及 Ping Chen 帮忙 Review 文章和给出宝贵的意见!