深入研究 Tornado.Cash,揭示了零知识证明项目的可扩展性攻击

对 Tornado.Cash 进行深入研究,揭示了零知识证明项目的可扩展性攻击。

Tornado.Cash魔改漏洞版分析

在上篇文章里,我们从原理的角度阐述了 Groth16 证明系统本身存在的延展性漏洞,本文中我们将以 Tornado.Cash 项目为例,魔改其部分电路和代码,介绍延展性攻击流程以及该项目中对应的防范措施,希望其他zkp项目方也引起注意。

1 Tornado.Cash 架构

Tornado.Cash的交互流程中主要包含4个实体:User、Web LianGuaige、Relayer和Contract。User使用该DApp进行混币器隐私交易,包括存、取款。Web LianGuaige是DApp的前端网页,网页上包含一些用户按钮。Relayer为防止链上节点记录发起隐私交易的IP地址等信息,该服务器会代替用户重放交易,进一步增强隐私性。Contract包含一个代理合约Tornado.Cash Proxy,该代理合约会根据用户存取款的金额选择指定的Tornado池子进行后续的存取款操作。目前已存在4个池子,金额分别为:0.1、1、10、100。

具体流程如下:

User首先在Tornado.Cash的前端网页上进行对应操作,触发存款或取款交易,接着由Relayer将其交易请求转发到链上的Tornado.Cash Proxy合约,并根据交易金额转发到对应的Pool中,最终进行存款和取款等处理。

2 Tornado.Cash魔改

Tornado.Cash在电路中增加了一个公共信号nullifierHash来防止相同的Proof被重复使用,该信号由nullifier进行Pedersen哈希得到,并作为参数传到链上,Pool合约使用该变量来标识一个Proof是否已经被使用过。但是如果项目方不采用修改电路的方式,而是直接以记录Proof方式来防止双花,可能会在节省开销的同时导致项目资金的威胁。

针对第一篇文章中介绍的延展性攻击原理,我们知道攻击者使用相同的nullifier、secret其实可以生成多个不同的Proof,那么如果开发者没有考虑到Proof重放造成的双花攻击,就会威胁到项目资金。为此,本文将删除电路中新增的nullifierHash公共信号,并将合约校验改为Proof校验。具体的魔改流程如下:

  1. 删除电路中新增的nullifierHash公共信号。
  2. 删除相应的合约代码,并将合约校验改为Proof校验。

通过上述魔改后的电路与合约,我们进行了实验验证,发现攻击者使用重放Proof的方法进行双花攻击在没有防护措施的情况下能够得逞。同时,在普通防重放合约的情况下,我们测试发现尽管可以防止普通Proof的重放攻击,但由于groth16算法的延展性漏洞,攻击者可以使用伪造的Proof绕过防护措施,进一步进行资金提取。

为了一劳永逸地解决重放漏洞,我们按照Tornado.Cash的做法,新增一个记录已使用过的输入数据的方式,以防止使用验证过的Proof进行重放攻击。该方法通过校验原始input是否已经被使用来实现。经过实验验证,我们发现相同input的Proof只能通过验证一次,后续重复使用相同Proof将无法通过校验。

3 总结和建议

本文介绍了使用魔改的Tornado.Cash电路和合约验证了重放漏洞的真实性和危害,并探讨了对应的防范措施。在开发zkp项目时,我们提出以下建议:

1.注意业务逻辑是否允许插入相同数值节点的情况。相同的叶子节点数据可能导致部分用户资金被锁死在合约中,或者是同一叶子节点数据存在多个Merkle Proof混淆业务逻辑的情况。

2.记录已使用的Proof,防范双花攻击。需要注意使用Groth16开发时,由于存在延展性攻击,因此记录需使用节点原始数据,而不能仅仅使用Proof相关数据标识。

3.进行全面的安全审计。复杂电路可能存在不确定性、欠约束等问题,合约实现逻辑也可能存在漏洞,建议寻求对电路和合约都有一定研究的安全审计公司进行全面审计,以确保项目的安全性。

通过以上措施,我们可以提高zkp项目的安全性,保护用户的资金。