4

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

 1 year ago
source link: https://www.ccvalue.cn/article/1409251.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Groth16 存在延展性攻击风险,可通过简单的计算伪造出新的证明,实际运用中需要注意防止出现双花攻击。

By: 大酱

前言

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

漏洞简介

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

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

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

数学原理

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

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

304de85989703c4d26d3900f79ee483b.jpg

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

4971414fef85c9b0c7e5c0a7c92eb24a.jpg

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

101d99b8719015e4df45b8b0ed85f389.jpg

这只是其中一种比较简单的构造方法,另外还有一种构造方法 [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 验证合约输入假名漏洞复现


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK