游乐游手机版
首页/web3.0/文章详情

Tornado Cash混币器:监管眼中钉,却是最精妙的ZK零知识应用

时间:2026-07-14 09:01
TornadoCash利用零知识证明实现隐私混币,存款时生成随机数K和r计算Commitment存入Merkle树,取款时通过ZK证明隐藏存款记录,并用Nullifier标识防止重放攻击,从而切断存取款地址关联,是ZK-SNARK应用层项目的典范。

Tornado Cash 被监管者盯上,这本身就是个值得琢磨的信号。但抛开监管层面的争议,这个混币器协议其实是理解 ZK 应用的最佳切片。

大家都在用的虚拟币交易平台推荐:

最近 Vitalik 和几位学者联名发表了一篇新论文,其中提到了 Tornado Cash 如何实现反冼钱方案——本质上是让取款人证明,自己的存款记录属于一个不包含黑钱的集合。但问题在于,论文里对 Tornado Cash 的业务逻辑和原理并没有展开细讲,读完之后总有种隔靴搔痒的感觉,核心机制还是没完全吃透。

更值得留意的是,以 Tornado 为代表的隐私项目,才是真正把 ZK-SNARK 算法的零知识性用到了极致。反观大多数打着 ZK 旗号的 Rollup,其实只是借用了 ZK-SNARK 的简洁性。很多人经常把 Validity Proof 和 ZK 这两个概念混为一谈,而 Tornado 恰好是厘清这组概念的完美案例。

「龙卷风」 的原理

Tornado Cash 是一个利用零知识证明的混币器协议,旧版本早在 2019 年就已经上线,新版本则在 2021 年底启动了 beta 测试。旧版本的去中心化程度相当高:链上合约开源且没有多签控制,前端代码同样开源,并且备份在了 IPFS 网络里。由于旧版的结构更简洁、更容易理解,我们就拿它来拆解。

Tornado 的核心思路其实相当直观:把大量存取款行为混杂在一起,存款者存入 Token 后,需要出示一个 ZK Proof 来证明自己存过款,然后换一个全新的地址提款。这样一来,存取款地址之间的关联性就被彻底切断了。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

更形象地说,Tornado 就像一个透明的玻璃箱,里面混杂着很多人放进去的硬币。从外面看,谁放了硬币一目了然,但这些硬币高度同质化——如果有个生面孔从玻璃箱里拿走一枚硬币,你很难判断他拿走的到底是谁当初放进去的那一枚。

这种场景其实并不陌生。你去 Uniswap 的池子里换几枚 ETH,同样无法知道划走的 ETH 最初是谁提供的,毕竟提供流动性的人太多了。但区别在于,每次用 Uniswap 划走 Token,你得用其他 Token 作为等价成本,而且不能把资金「私密地」转让给别人。混币器则不同,它只需要提款者出示存款凭证就行。

为了让存取款动作看起来完全同质化,Tornado 的池子在设计上做了个巧妙安排:存款地址每次存入的金额、取款地址每次取出的金额都保持一致。也就是说,某个池子里有 100 个存款者和 100 名取款者,虽然他们的地址都是公开可见的,但彼此之间看不出任何联系,而且每人存入和取出的金额完全一样。

这样一来,资金转移的痕迹就被彻底混淆了——没法通过存取款金额来判断关联性。当然,这也为冼钱行为提供了天然的便利,这也是它被监管者盯上的直接原因。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

但这里有个关键问题:取款者在提款时,怎么证明自己确实存过款?发起取款的地址和所有存款地址都不关联,合约凭什么相信他?最直接的办法是让取款者披露自己的存款记录是哪一笔,但这样一来身份就暴露了。这时候,零知识证明登场了。

提款者只需要出具一个 ZK Proof,证明自己在 Tornado 合约里有一笔存款记录,并且这笔存款还没有被提取过,就可以顺利提款。零知识证明的妙处在于:外界只知道「取款人确实往资金池里存过款」,但完全不知道他对应的是哪个存款者。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

要证明「我在 Tornado 资金池里存过款」,可以转化为「我的存款记录可以在 Tornado 合约里找到」。如果用 Cn 表示存款记录,问题就变成了:

已知 Tornado 的存款记录集合为 {C1,C2,…C100…},取款者 Bob 需要证明自己曾用手上的密钥生成了某个 Cn,但同时不能泄露 Cn 具体是哪一个。

这里用到了 Merkle Proof 的特殊性质。Tornado 的所有存款记录,都存进了链上构造的一棵 Merkle Tree,作为底层的叶子节点。叶子总数大约是 2 的 20 次方,也就是超过 100 万,其中大部分处于空白状态(被赋予了初始值)。每当有新存款发生时,合约就会把对应的特征值 Commitment 写入一个叶子,然后更新 Merkle Tree 的根。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

举个例子,假设 Bob 的存款操作是 Tornado 有史以来第 1 万笔,那么与这笔存款关联的特征值 Cn 会写入 Merkle Tree 的第 1 万个叶子节点,即 C10000 = Cn。然后合约会自动算出新的 Root 并更新(为了节约计算量,Tornado 合约会缓存之前一批有变化的节点数据,比如图中的 Fs1、Fs2、Fs0)。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

Merkle Proof 本身非常简洁轻便,它利用了树状数据结构在检索和溯源过程中的高效性。要证明某笔交易 TD 存在于 Merkle Tree 中,只需要给出 Root 对应的 Merkle Proof(如下图右边部分),数据量极小。即便 Merkle Tree 有 100 万笔存款记录,Merkle Proof 也只需要包含 21 个节点的数值,非常短。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

如果要证明某笔交易 H3 确实包含在 Merkle Tree 中,只需要证明用 H3 和树上的其他部分数据可以生成 Root。而生成 Root 所需的那部分数据(包括 Td 在内)就构成了 Merkle Proof。

Bob 在取款时,需要证明两件事:第一,Cn 存在于链上 Tornado 合约的 Merkle Tree 中,具体可以通过构造一个包含 Cn 的 Merkle Proof 来证明;第二,Cn 与 Bob 手上的存款凭证有关联。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

Tornado 业务逻辑详解

Tornado 用户界面的前端代码中,事先已经实现了很多功能。当你打开 Tornado Cash 网页并点击存款按钮后,前端代码附带的程序会在本地生成两个随机数 K 和 r,然后计算出 Cn = Hash(K, r) 的值,再把 Cn(也就是图中的 commitment)传入 Tornado 合约,插入到合约记录的 Merkle Tree 里。说白了,K 和 r 就相当于你的私钥,系统会提示你妥善保存——后续提款时仍然要用到它们。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

值得注意的是,以上所有操作都在链下完成,也就是说,Tornado 合约和外界观察者都不知道 K 和 r。一旦 K 和 r 被泄露,后果就跟钱&包私钥被盗一样严重。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

Tornado 合约收到用户存款和用户提交的 Cn = Hash(K, r) 后,将 Cn 插入到 Merkle 树的最底层,作为新的叶子节点,同时更新 Root 的数值。 所以,Cn 和用户的存款动作是一一对应的。外界可以看到每个 Cn 对应着哪个用户,知道有哪些人往混币器里存入了 Token,以及每个存款者对应的存款记录 Cn。

在取款环节,取款者在前端网页里输入凭证(即存款时生成的随机数 K 和 r),Tornado Cash 前端代码中的程序会使用 K、r、Cn = Hash(K, r)、以及 Cn 对应的 Merkle Proof 作为输入参数,生成一个 ZK Proof。这个 Proof 证明了两件事:Cn 是存在于 Merkle Tree 上的某笔存款记录,而 K 和 r 是对应 Cn 的凭证。这一步相当于在说:「我知道某笔记录在 Merkle Tree 上的存款记录对应的密钥。」当 ZK Proof 被提交给 Tornado 合约时,上述四个参数全部被隐藏,外界(包括 Tornado 合约)都无法获知,隐私由此得到保障。

生成 ZK Proof 涉及的其他参数还包括: 取款时 Tornado 合约里 Merkle Tree 的根 root、自定义的收款地址 A、以及用于防止重放攻击的标识符 nf(后面会详细解释)。这三个参数会公开发布到链上,外界可以获知,但不影响隐私。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

这里有个细节值得注意:存款操作生成 Cn 时,用了两个随机数 K 和 r,而不是单个。原因很简单,单个随机数不够安全,有概率发生碰撞——比如两个不同的存款者恰巧用了同一个随机数,导致生成的 Cn 撞车。

上图中的 A 代表接收提款的地址,由提款者自己填写。nf 则是一个防止重放攻击的标识符,其数值 nf = Hash(K),K 就是存款生成 Cn 时用到的两个随机数之一。这样一来,nf 就与 Cn 关联了起来,每个 Cn 都有自己对应的 nf,两者一一对应。

为什么要防止重放攻击? 混币器的设计特性决定了取款时无法知道用户提走的币对应 Merkle 树的哪个叶子 Cn,也就无法知道提款人到底存过几次款。提款者可以利用这一漏洞频繁提款,发起重放攻击,多次从混币器池子里取走 Token,直到把资金池抽干。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

nf 标识符的作用类似于每个以太坊地址都有的交易计数器 nonce,都是为了防止某笔交易被重复执行。当一笔取款发生时,取款者需要提交一个 nf,合约会检查这个 nf 是否已经被使用过(记录在案)。如果已经被使用过,此次取款无效;如果没有,则取款有效,同时该 nf 会被记录下来。下次再有人提交这个 nf,对应的取款动作直接判定为无效。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

那如果有人胡乱生成一个合约没有记录过的 nf 行不行? 当然不行。因为取款者生成 ZK Proof 时,需要保证 nf = Hash(K),而随机数 K 与存款记录 Cn 关联。也就是说,nf 必须与某笔有记录的存款 Cn 对应。如果随便编造一个 nf,这个 nf 与存款记录中的所有存款都对不上号,就无法生成有效的 ZK Proof,取款操作自然不会成功。

可能有人会问:不用 nf 行不行?既然提款者在提款时需要提交 ZK 证明,证明自己和某个 Cn 有关联,那么每当提款动作发生时,只需要查找对应的 ZK Proof 是否已经被提交到链上过,不就可以了吗?

理论上可行,但成本太高。Tornado Cash 合约不可能永久保存所有提交过的 ZK Proof,因为这会严重浪费存储空间。与其比较每个新提交的 ZK Proof 和已有的 Proof 是否一致,不如设置一个占地很小的标识符 nf 并永久保存,性价比高得多。

按照取款函数的代码示例,其需要的参数和业务逻辑如下:

用户提交 ZK Proof、nf(NullifierHash)= Hash(K),以及一个自定义的接收提款地址 recipient。ZK Proof 隐藏了 Cn、K、r 的数值,外界无法据此判断用户身份。recipient 往往填写一个干净的新地址,也不会泄露个人信息。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

但这中间有个小问题:用户在取款时,为了不可溯源,往往会用新申请的地址来发起取款交易。可新地址里没有 ETH 来支付 gas 费。所以取款地址发起取款时,需要显式声明一个中继者 relayer,由它来代付 gas 费。之后混币器合约会直接从用户提款里扣除一部分交给 relayer,作为回报。

解读Tornado Cash混币器:监管者的眼中钉,却是最精妙的ZK零知识应用

总结一下,Tornado Cash 能够隐藏取款者和存款者之间的关联,一旦用户量足够大,就如同一个闹市区——犯人混进人群后,警方就很难追踪。整个取款过程中最关键的一环,就是 ZK-SNARK 的运用:被隐藏起来的 witness 部分包含了取款人的关键信息。从这个角度看,Tornado 可能是目前与 ZK 相关的最巧妙的应用层项目之一。

来源:https://www.jb51.net/blockchain/1030964zsor.html
上一篇以太坊首个DApp竟是预测市场?揭秘冷知识 下一篇揭秘代币从Coinbase到Upbit的28天接盘之路
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多