freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

ZKP 系列之 Groth16 证明延展性攻击原理及实现
2023-03-10 17:20:02
所属地 海外

By: 大酱

前言

之前的文章我们盘点了 ZKP 主流实现方案技术特点,我们提到了一些 ZKP 算法存在延展性风险,本篇我们继续从实战角度为大家展示它的攻击原理及防御方法。

漏洞简介

ZKP 的延展性攻击,是指给定敌手一个合法证明,敌手在不知道见证的条件下自己生成新的合法证明。

并不是所有的证明系统都存在延展性攻击风险,实际上这个问题目前主要存在于 Groth16 证明系统中。那么问题来了,目前已经有那么多的证明系统,为什么还要坚持使用 Groth16 呢?其实也没得选择,Groth16 生成的证明体积实在是小到极致,验证也极其之快,在计算代价十分昂贵的区块链上,使用 Groth16 似乎是最理想的选择。

延展性风险会带来哪些风险呢?我们可以想象一下如果有这么一个存款系统,它使用用户提交的 ZKP 证明来验证其身份,验证成功则可以提款。由于这个系统的验证过程是公开的,任何人都可以获取到这个证明,如果我们是用证明值本身做为取款登记,如果这个证明被获取到并进行变换,那就可被利用来多次提款。漏洞的利用需要看具体的场景,但我们可以看到延展性首先会带来双花的风险。

数学原理

理解攻击原理我们首先需要从理解算法开始,这需要有一定的密码学基础,感兴趣的同学可以自行寻找 Groth16 算法资料。这里我们直击漏洞根源:验证函数。

我们来看一下这个验证函数公式:

ZKP 系列之 Groth16 证明延展性攻击原理及实现

如果没有对这些字母一一从头介绍,我想很难看懂它在表达什么,但我觉得过多的介绍是不需要了,记好公式左边的 “A 乘 B” 就这么简单,然后我们开始施展数学魔法。咒语如下:

ZKP 系列之 Groth16 证明延展性攻击原理及实现

根据群的结合律,那么上面公式中:

ZKP 系列之 Groth16 证明延展性攻击原理及实现

这只是其中一种比较简单的构造方法,另外还有一种构造方法 [1],这里不做阐述,因为我们已经得到想要的东西了。

工程实现

有了上面的公式,就可以在工程上实现 Groth16 证明的延展。选择一个要伪造证明的对象,获取它的 proof,例如:

{pi_a: ['17566212007750634279332191898019870443899908963707812937725971557556988121113','13653824972036797689593667463260040326059024360787769597142078414930263663703','1'],pi_b: [['14906111038352923510344648516413952434168552622848767570599399834157918236589','15289017543994496306320102143103349779456992442925111629326024552687168229256'],['18841235948006283310515755114762069779103481848435391875780416574913227842443','6835281862874020275059416795628130939104366467185014410026268177455413514889'],[ '1', '0' ]],pi_c: ['21641806348662631815866837255154640732047306895903168385641666607914783128458','2082587994352117459125871298218148663854896572836176277773049196516560449682','1'],protocol: 'groth16',curve: 'bn128'}

我们看这样的一个 proof,pi_a, pi_b, pi_c 就是上面公式里描述的 A, B, C,这个证明使用的是 BN128 曲线,然后我们需要去寻找支持 BN128 曲线开发库。这里我们选择 ffjavascript,它是一个基于 Javascript 的有限域库,支持 BN128, BLS12381 曲线。

首先,我们任意构造一个域上的元素及它的逆元:

const X = F.e("123456");const invX = F.inv(X);

然后,分别相乘,核心代码如下:

const A = curve.G1.fromObject(proof.pi_a);const B = curve.G2.fromObect(proof.pi_b);new_pi_a = curve.G1.timesScalar(A, X);  //A'=x*Anew_pi_b = curve.G2.timesScalar(B, invX);  //B'=x^{-1}*B

最后,用 new_pi_a, new_pi_b 去替换原 proof,得到新的 proof:

{pi_a: ['6515337738552169645617263495374285821912767490069335826295120714428977813009','10671874016637483602721966808912960491553808325993800847672325376634242358838','1'],pi_b: [['20523135654483520737281403147507843211011765855706506084021355785019229409285','4032527486736971273144842057682931136787425732029780739716144011227563817375'],['9389285843105460816015935120908213706233585149018458753845466963847282799614','7207137211649923819130654483456848273137049778520784010268635580504303221849'],[ '1', '0' ]],pi_c: ['21641806348662631815866837255154640732047306895903168385641666607914783128458','2082587994352117459125871298218148663854896572836176277773049196516560449682','1'],protocol: 'groth16',curve: 'bn128'}

至此,我们已经成功构造出了新的证明,把 proof 放到验证函数中去验证可以发现它能通过验证。

防范

如何防范 Groth16 延展性攻击呢?可以参考这四种方法:

  • 对 proof 进行签名,验证者在验证 proof 同时也验证签名是否正确;

  • 参考 TornadoCash 在电路的公开输入中增加 nullifier 值,nullifier 确保证明对应的公开输入只能被使用一次;

  • 在电路中将证明者的身份信息(如以太坊的 msg.sender )加入到公开输入中,然后验证者就可以对提交证明的人进行身份验证;

  • 使用其它的证明系统,参考我们之前的文章

总结

Groth16 存在延展性攻击风险,可通过简单的计算伪造出新的证明,实际运用中需要注意防止出现双花攻击。

考链接:

[1]. https://medium.com/ppio/how-to-generate-a-groth16-proof-for-forgery-9f857b0dcafd

往期 ZKP 系列:

慢雾:盘点 ZKP 主流实现方案技术特点

ZKP 系列之 Circom 验证合约输入假名漏洞复现

# 零知识 # 零知识证明
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录