首页 > 其他分享 >Divide and Conquer:ZK除法中隐藏的漏洞

Divide and Conquer:ZK除法中隐藏的漏洞

时间:2024-09-10 20:50:12浏览次数:3  
标签:Divide ZK 约束 Conquer 除法 uint32 witness 乘法

ZK的崛起与演变

曾几何时,零知识证明(以下简称ZK)仍然被认为是密码学教科书中的理论概念,至少在传统安全研究中很少被主流社群深入探索。然而在Web3.0领域,区块链技术的迅速发展,用短短几年时间实现了ZK从理论到实践的跨越式进展,一路蓬勃,高歌猛进。

1985年诞生,2014年ZCash才用SNARKs证明了ZK不仅是书本上的传说,也是实打实的“江湖绝学”,2019年开始,随着zkSync和Polygon的崛起,ZK从隐私保护的小众技术,摇身一变成了区块链扩展性问题的关键。到了2022年,Tornado Cash轰然倒下,美国财政部的制裁引发了一场关于隐私与自由的广泛讨论,也让ZK成为了茶余饭后的热门话题。2023年起,随着PLONK、Halo2等新型ZK协议的成熟,ZK技术在区块链领域高速发展,成为Web3.0的新宠。

ZK的崛起不仅仅是因为它在区块链世界中的广泛应用,也与这些年来不断创新的开发工具密不可分。尽管这些工具的核心目标都是将代码逻辑电路化,但几年间,从最初合约级应用的Circom,到链上层为性能优化推出的EVM兼容或等价的zkVM,更新速度之快令人目不暇接,甚至连应用生态脚步都还没稳住,下一次升级迭代已呼啸而至。 

ZK原理概述

想理解ZK,可以从其共性的漏洞入手。在传统安全里有个经验:直接分析代码逻辑来理清全局往往难度极大,有时不如跑个crash看dump来得直观,也就是通过漏洞回溯代码的方式去理解内在逻辑。

初识ZK,可能会被各种专有名词包围 — SNARK、STARK、PLONK、QAP、R1CS、Groth16。这些名词乍听还可理解,一旦深入探究,就会发现背后需要扎实的数学功底。所以,很多介绍ZK的内容,要么是光彩夺目的概念科普,要么是晦涩难懂的协议分析,仿佛置身于一片高深莫测的领域。今天这篇文章,希望能带给你一种不同的体验。我们将从一个简单的除法证明问题出发,从工程实践的角度带你走近ZK的世界线

在我们讨论后续问题之前,我们先用一个实践向的直观视角来解释一下ZK,以便后续讨论时有一个共同的基础。在智能合约和区块链中应用ZK技术解决的核心问题是如何在不暴露答案本身的情况下,证明自己知道这个正确的答案,例如一个多项式方程的解。越过原理,只想说目前有成熟方法能够实现这个目标:首先,将一个复杂的问题通过多个仅涉及乘法和加法的简单问题加以描述;然后,将这些简单问题转换为矩阵和代表正解的witness相乘的形式;接下来,将矩阵转化为verification-key;同时,witness则进一步转换为proof。

简而言之:一个复杂问题被转化为一组特定的key,而答案被分解为多个witness,最终演变为proof;proof能够用verification-key以固定的算法验证。一方面验证成功说明生成proof的人确实知道问题的正确答案,另一方面通过proof却无法反推出原解,保护了隐私。这一验证过程可以用于提款的同时不暴露存款凭证;也可以用于证明一个transaction引发的合约代码执行结果的真实性,进而用短proof代替多人执行transaction造成的资源消耗。

约束挑战

由于ZK所有相关计算都在椭圆曲线上进行,只有加法和乘法是直接定义的。要证明一个复杂问题,必须将其拆解成包含这些基本运算的简单子问题,即电路化。电路化的过程也是目前出问题最多的地方。 

拆分出来的简单的子问题被称为“约束”,它们联立后必须与原始复杂问题等效。如果某个约束缺失,可能导致构造出符合所有约束但不是正确答案的witness,从而伪造证明。这些伪造的证明仍然能够通过verification-key的检查,带来一系列严重后果:如合约级别的双重支付、或者zkVM级别的修改计算中间结果等。另一方面,若约束过于严格,超出了原问题的需求,则可能导致无法找到合适的witness,进而导致交易无法被证明,造成链级别的拒绝服务攻击或合约应用的功能失效。第一种利用欠约束伪造证明看起来更有趣,它相当于直接控制了执行过程,类比于传统安全漏洞利用时的控PC指针。

除法的案例分析

下面就来看一个简单的除法问题在ZK的语境下该怎么约束,又能惹出多少乱子。

设想如下场景,zkVM在运行时,执行了一个a除以b的运算,且我们要证明商是q,余数是r。在这里,abqr都是witness,我们需要确保它们满足除法的约束。假设ab已经由前序执行过程约束确定,我们仅关注qr的约束。直觉上,既然a=b*q+r是除法的乘法表达形式,是不是一个约束多项式就够用了呢?绝对不是!在实际的工程实现中,情况要复杂得多。例如,zkSync曾经的除法验证过程涉及的代码如下:

首先a(src0_view)b(src1_view)通过allocate_div_result_unchecked计算出qr,这部分仅仅是算数运算,先验地根据ab求出作为witness的qr,不涉及约束。

出于优化考虑,zkSync将乘法和除法放在同一个函数里进行约束,所以接下来是根据乘或除,通过带有约束的选择器取出要约束的变量,即rq、ba,并增加乘法约束MulDivRelation,也就是要求a=b*q+r。

MulDivRelation的乘法约束在指令循环即将结束前才由enforce_mul_relation函数施加。然而,由于zkSync选择了Goldilock域(域阶为0xffffffff00000001),这个域空间并不足以表示所有的uint64类型数据。因此,uint256需要分解成八个uint32来记录。为了处理这部分的乘法,系统采用了逐轮计算的方式,每一轮通过fma_with_carry门对两个uint32执行乘法约束。

乘法结束时,先用一次enforce_equal门约束计算结果没有进位,再用一次保证乘加的累积结果和a相等。第二个enforce_equal的目的容易理解,也就是用于满足我们之前反复提及的a=b*q+r。

第一个没有进位的约束确保了商q和余数r的值不会超出预期的范围,避免了计算结果出现溢出。除了进位检查,另一个常用方法是约束比特长度(通过限制商和余数比特数,确保计算的结果符合预期的范围)。zkSync记得带上了这个约束,但其实这是个很容易被忽略的细节,比如renegade项目计算fee相关用到的除法就漏掉过这个约束:

再比如Circom中的大整数求模库函数Bigmod也曾出现过类似的漏洞。具体来说,Bigmod函数在实现过程中,只检查了商q的比特长度,而忽略了对余数r的长度检测:

之所以要有这个约束,是因为有限域内的溢出会让结果回滚仍落入域内,使得qr不唯一。比如给定一个新的r'=r-k,总能通过q'=(a-r')*b^(-1)计算得到一个满足条件的q'使攻击者修改除法计算结果。对于日常使用的ab,这样修改r通常会导致一个非常大的q

在zkSync的代码中,乘法约束的设置只是第一步。接下来,它要比较rb(除数),确保r<b。具体来说,allocate_subtraction_result_unchecked执行了这一比较操作,它做的只是计算出r-b,并将结果存入变量subtraction_result_uncheckedremainder_is_less_than_divisor。其中remainder_is_less_than_divisor记录了长减法是否发生了借位。借位了则意味着r<b,这是我们期望看到的情况(由conditionally_enforce_true约束保证正确性)。之后b、r-b (subtraction_result_unchecked)、rof (remainder_is_less_than_divisor)会被放入AddSubRelation

在指令循环结束前,通过enforce_addition_relation函数施加UIntXAddGate加法门约束。确保(r-b)+b=r+of*2^256,其中of代表的是加法过程中产生的进位。这个约束的逻辑在于,r-b的结果应该为负数,域内表现为一个非常大的正数。为了让这个结果能够被正确表示,r-b与b相加时,必然会超过2^256导致进位,使得remainder_is_less_than_divisor的约束得以满足:

这么一套约束的目的是避免攻击者通过构造另一组商q'和余数r'来绕过除法约束,进而伪造计算结果。比如我们设定新的q'=q-k和r'=r+b*k,很容易就找到了一组也符合乘法关系的witness篡改计算结果。这个约束在实际代码中也很容易被忽略。例如,Polygon项目就曾经在代码中误加了过于宽松的r<a约束:

在zkSync的除法计算过程中,表面上看似乎所有必要的约束都已经施加,但代码实现上仍然存在漏洞。这个漏洞和zkSync的代码设计相关,之前提到,uint256类型的数据是通过八个uint32表示的,而每个uint32背后实际上是variable类型,它代表的是Goldilock域中的元素。因此,每个variable实际上最大可以表示0xffffffff00000000

如果希望uint32中的variable仅表示32位整数,则必须为每个uint32额外施加比特长度约束,以确保其数值范围受限。但在allocate_subtraction_result_unchecked函数中,并没有对计算结果subtraction_result_unchecked中的每个uint32施加这种比特长度约束。这意味着,虽然subtraction_result_unchecked被定义为[uint32; 8],但其中每个uint32实际上表示的最大值是0xffffffff00000000,而不是期望的32位限制。

因此,如果把subtraction_result_unchecked中最后一个uint32的第32比特篡改为1,则后面加法门的计算过程中必然会有进位,使得remainder_is_less_than_divisor约束天然满足。之后令r'=r+b*k和q'=q-k就可以产生同样合法的qr的组合了。

总结

通过探讨除法约束在工程实现中一些tricky的细节,我们初步感受了一下ZK世界的复杂与精妙。每个细节都可能隐藏着影响整个系统安全性的潜在风险,也正是这些细微之处构成了ZK证明技术的核心,推动了其在区块链领域的广泛应用。未来的篇章里,CertiK会继续深入ZK的技术细节,敬请期待。

标签:Divide,ZK,约束,Conquer,除法,uint32,witness,乘法
From: https://blog.csdn.net/weixin_58310340/article/details/142105920

相关文章

  • Azkaban:强大的开源工作流调度系统
    一、概述在大数据生态系统中,随着数据量的爆炸式增长和任务复杂度的提升,管理和调度大规模的批处理任务成为了一项艰巨的挑战。Azkaban是LinkedIn开发的一款开源工作流调度系统,专为管理和调度大规模的Hadoop作业设计。它提供了一种简单且有效的方式来定义、调度和监控复......
  • 零知识证明-ZK-SNARKs基础(七)
    前言这章主要讲述ZK-SNARKs所用到的算术电路、R1CS、QAP等1:算术电路算术运算电路1>半加器:实现半加运算的逻辑电路2>全加器:能进行被加数,加数和来自低位的进位信号相加,并根据求和结果给出该位的进位信号说明:2进制加,低位进位相当于结果S为=A+B+C(地位进位)高位进......
  • Azkaban的认识与使用
    在大数据领域的加工/计算层(MapReduce),涉及了繁多的工作流workflow,为了应对工作流的可视化、可调整、可跟踪等需求,Azkaban应运而生。Azkaban isabatchworkflowjobschedulercreatedatLinkedIntorunHadoopjobs.Azkaban是LinkedIn创建的一个批处理工作流作业调度器,用......
  • [AGC064C] Erase and Divide Game
    link感觉题解说的都很不清晰,这里只谈个人理解。考虑操作的本质是什么,两人从低到高确定二进制下的每一位填的数,并且场上只保留对应后缀的数字,当场上没有数字时当前操作者输。设\(f[i,S]\)表示确定了前\(i\)位,填的数为\(S\),接下来先手是否能赢,那么有\(f[i,S]=\neg(f[i......
  • zkw线段树
    事情的起因是我某天吃晚饭时打算找个电子榨菜,然后b站搜索线段树,看到了一个名叫zkw线段树(即非递归线段树),由于不是面向Oier的,所以饭后我又找了几个博客看,现在写下心得记录(其实只是不想在书签留3个位置给线段树)为什么要学习非递归线段树,这个问题大部分博客解释为普通线段树......
  • zkw线段树
    介绍非递归线段树实现方法,码量较短。zkw线段树的构造原理:普通线段树采用堆存储,zkw线段树本质上是满二叉树(若没有该区间则为空点)但根据实际情况,原区间不一定构成满二叉树,据查询方式限制,空间开到最接近的\(2^n\)(据性质树值域=底层节点数),即不存在的点有虚点填充。既然不......
  • IgniteFAQ-6-Ignite 通过zk组网时集群组网失败
    zk组网类ZookeeperDiscoverySpizkDiscoverySpi=newZookeeperDiscoverySpi();cfg.setDiscoverySpi(zkDiscoverySpi);配置示例:#ignite集群租房方式,配置zk为,zk集群组网方式fc.mybatis.ignite.discovery=zk#Ignitezk集群组网方式ignite.discoverySpi.zkConnectionStr......
  • azkaban-tools 项目介绍
    本文公众号地址本文背景应一个用户的好心和好奇心,在最近水深火热的百忙之中抽时间写完了一个简短的项目介绍,其实就是几个azkaban的批量操作脚本,但在大数据集群的“运维生涯”中,还是帮了自己不少忙,也算是为了它做一个简单的回顾吧项目背景azkaban是一个大数据领域通用的任务管......
  • Divide Interval 题解
    背景太逊了,调了三次才调出来,所以写篇题解寄念。LC好睿智题意给你两个数\(a,b\),现在要从\(a\)跑到\(b\),每次可以将当前的\(a\)拆分成\(2^n\timesm(n,m\inN)\)的形式,并将它变成\(2^n\times(m+1)\)。问最少变几次能跑到\(b\),输出次数和每次变化前后\(a\)的值。分......
  • [ABC206E] Divide Both 的题解
    题目大意求出从\(l\)至\(r\)中满足以下条件的\((x,y)\)的个数。\(\gcd(x,y)\ne1\)且\(\gcd(x,y)\nex\)且\(\gcd(x,y)\ney\)。其中\(1\lel\ler\le10^6\)。思路正难则反,所以可以求出所有互质或者是相互倍数的\((x,y)\)的对数,在将其减去所有的方案数就是......