从零开始学习 zk-SNARK(一)——多项式的性质与证明
导读
17 年最早接触 zk-SNARK 开始,就断断续续得学习了一些 zk-SNARK 的知识,但对其原理始终存在诸多困惑,没有形成一个完整的认识。偶然一次机会,看到了 Maksym Petkus 的这篇文章。文章从最基本的多项式性质讲起,从一个简单易懂的证明协议开始,然后像堆积木一样在发现问题,修改问题中逐步去完善协议,直到最终构造出完整的 zk-SNARK 协议。另外作者这种从问题出发的讲解方式,让读者知其然,也知其所以然 。作为一枚毕业多年早把数学知识还给老师的程序媛,读到这篇文章如获至宝,这篇文章读下来的感受像找到了一个脚手架,将脑海里碎片化的知识逐渐拼凑完整。于是想把它翻译出来(已获得作者授权),一方面加深自己的学习,另一方面也将这份宝藏分享给小伙伴们。
文章翻译存在不足之处,欢迎纠正,补充,指导。
—— even@安比实验室
作者:Maksym Petkus
翻译 & 注解:even@安比实验室([email protected])
校对:valuka@安比实验室
本系列文章已获作者中文翻译授权。
当我第一次了解到 zk-SNARK 技术是如何将这些东西完美地融合在一起的时候,就被数学之美震撼到了,并且随着我发现的维度越多,好奇心就越强烈。在这篇文章中,我主要就基于一些实例简洁明了地阐明 zk-SNARK ,并对这里面的很多问题做出了解释,并利用这种方式分享了我的经验,进而让更多人也能够欣赏到这项最先进的技术以及它的创新之处,最终欣赏到数学之美。
这篇文章的主要贡献是比较简洁明了的解释了其中相当复杂的技术,这些简单的解释对于在不具备任何与之相关的先决知识,比如密码学和高等数学的前提下理解 zk-SNARK 是很有必要的。文章中并不仅仅只解释 zk-SNARK 是如何工作的,还解释了为什么这样就可以工作,以及它是怎么来的。
序言和介绍
其实零知识证明在无数的应用中都具备优势,包括:
-
一个人 A 的银行账户金额多于 X -
去年,一家银行未与实体 Y 进行交易 -
在不暴露全部 DNA 数据的前提下匹配 DNA -
一个人的信用评分高于 Z
-
在不揭露身份的情况下(比如登录密码),证明请求者 R 有权访问网站的受限区域 -
证明一个人来自一组被允许的国家/地区列表中的某个国家/地区,但不暴露具体是哪个 -
证明一个人持有地铁月票,而不透露卡号
-
付款完全脱离任何一种身份 -
纳税而不透露收入
-
将昂贵的计算外包,并在不重新执行的情况下验证结果是否正确;它打开了一种零信任计算的类别 -
改进区块链模型,从所有节点做同样的计算,到只需一方计算然后其它节点进行验证
-
完整性 —— 只要「陈述」是正确的, prover 就可以让 verifier 确信 -
可靠性 —— 如果「陈述」是错误的,那么作弊的 prover 就没有办法让 verifier 相信 -
零知识 —— 协议的交互仅仅揭露「陈述」是否正确而不泄漏任何其它的信息
证明的媒介
verifier 一次只能检查(即,读)一位。为了验证这个陈述,我们以某种任意的顺序读取元素并检查其是否确实等于 1 。如果第一次抽样检查的结果是 1,就设置「陈述」的可信度为 ⅒= 10%,否则,如果等于 0,就说明「陈述」是错误的。验证者继续进行下一轮验证,直到获得足够的可信度为止。假如在一些场景下要信任 prover 需要至少 50% 的可信度,那就意味着必须执行 5 次校验。但假如在其它一些场景下需要 95% 的可信度,就需要检查所有的元素。很明显这个证明协议的缺点是必须要根据元素的数量进行检查,如果我们处理数百万个元素的数组,这么做是不现实的。
同样,我们也可以令上文中原始的多项式和修改后的多项式相等,找到它们的交点。
x³ – 6x² + 11x – 6 =x³ – 6x² + 10x – 5
任意一个由阶数为 d 的多项式组成的等式,最后都会被化简为另外一个阶数至多为 d 的多项式,这是因为等式中没有能够用来构造更高阶数的乘法。例如: 5x³ + 7x² – x + 2 = 3x³ – x² + 2x– 5 ,简化为 2x³ + 8x² – 3x + 7 = 0 。另外代数的基本原理也告诉我们,对于一个阶数为 d 的多项式至多有 d 个解(以下部分将对此进行详细介绍),因而也就至多有 d 个共同点。
所以我们可以得出结论,任何多项式在任意点的计算结果(更多关于多项式求值参考:[Pik13])都可以看做是其唯一身份的表示。我们来计算一下当 x = 10 时,示例多项式的结果。
-
verifier 选择一个随机值 x 并在本地计算多项式结果 -
verifier 将 x 值给到 prover,并让他计算相关的多项式结果 -
prover 代入 x 到多项式计算并将结果给到 verifier -
verifier 检查本地的计算结果和 prover 的计算结果是否相等,如果相等那就说明 prover 的陈述具有较高的可信度
证明多项式的知识
我们的讨论从证明多项式的知识开始,再将证明协议逐步转换成一种通用的方法,在这个过程中我们也将发现多项式的很多其它性质。
因式分解
(x-1)(x-2) · …
换句话说 ( x – 1 ) 和 ( x – 2 ) 是问题中多项式的两个因式。因而如果 prover 想要在不揭示多项式的前提下证明他的多项式确实有这两个根,那么他就需要去证明他的多项式 p(x) 是 t ( x ) = ( x - 1)( x - 2) (也称为目标多项式)和一些任意多项式 h(x) (也就是我们的例子里面的 x - 0 )的乘积,即:
p(x) = t(x) · h(x)
换句话说,存在一些多项式 h(x) 能够使得 t(x) 与之相乘后等于 p(x) ,由此得出, p(x) 中包含 t ( x ),所以 p(x) 的根中也包含 t ( x ) 的所有根,这也就是我们要证明的东西。
自然算出 h(x) 的方式就是直接相除:
如果一个 prover 不能找到这样一个 h(x) 也就意味着 p(x) 中不包含因式 t ( x ),那么多项式相除就会有余数。例如我们用 p(x) = x³ – 3x² + 2x 除以 t(x) = (x – 1)(x – 2) = x² – 3x+ 2
注意: 左边的式子是分母,右上角的是计算结果。底部是余数(多项式相除的解释及示例可以看这里 [Pik14] )。
我们算出结果 h(x) = x ,没有余数。
注意: 为了简化起见,后面我们会用多项式的字母变量来代替计算结果值,例如:p = p(r)。
-
verifier 挑选一个随机值 r , 计算 t = t(r) (即,求值) ,然后将 r 发送给 prover。 -
prover 计算 h ( x ) = p ( x ) / t ( x ) ,并对 p(r) 和 h(r) 进行求值,将计算结果 p , h 提供给 verifier。 -
verifier 验证 p= t⋅h ,如果多项式相等,就意味着 t(x) 是 p(x) 的因式。
-
verifier 选一个随机数 23,并计算 t = t (23) = (23 – 1)(23 – 2) = 462,然后将 23 发给 prover -
prover 计算 h ( x ) = p ( x ) / t ( x ) = x , 并对 p(r) 和 h(r) 进行求值, p = p (23) = 10626, h = h (23) = 23,将 p 和 h 提供给 verifier -
verifier 再验证 p= t⋅h :10626 = 462 ⋅ 23 是正确的,这样陈述就被证明了。
-
prover 可能并不知道他所声称的 p(x),他可以先算一下 t = t(r) ,然后选择一个随机值 h ,由此计算出 p = t⋅h 。因为等式是成立的,所以也能通过 verifier 的校验。 -
因为 prover 知道随机点 x = r ,他可以构造出一个任意的多项式,这个任意多项式与 t ( r ) ⋅ h ( r ) 在 r 处有共同点。 -
在前面的「陈述」中,prover 声称他知道一个特定阶数的多项式,但现在的协议对阶数并没有明确的要求。因而 prover 完全可以拿一个满足因式校验的更高阶数的多项式来欺骗 verifier。
5 3 =125
这里 125 就是 3 对应的密文。如果我们想要对被加密的值乘 2,我们可以以 2 为指数来对这个密文进行计算。
125² = 15625 = (5³)² = 5 2×3 = 5 6
我们不仅可以用 2 来乘以一个未知的值并保持密文的有效性,还可以通过密文相乘来使两个值相加,例如 3+2:
5 3 · 5 2 = 5 3+2 = 5 5 =3125
同样的,我们还可以通过相除提取加密的数字,例如:5-3
5 5 /5 3 = 5 5 ·5 -3 =5 5-3 = 5 2 =25
不过由于基数 5 是公开的,很容易就可以找到被加密的数字。只要将密文一直除以 5,直到结果为 1,那么做除法的次数也就是被加密值的数。
模运算
我们执行算术运算,结果都将落在这 n 的范围内。现在开始我们将用符号 “ mod n ” 来表示这个范围内的数。
3 × 5 = 3 mod 6
5 + 2 = 1 mod 6
另外,模运算最重要的性质就是运算顺序无所谓。例如,我们可以先做完所有的操作,然后再取模,或者每操作完一步都去取模。例如 (2 × 4 – 1) × 3 = 3 (mod 6) 就等于:
2 × 4 = 1 mod 6
2 - 1 = 1 mod 6
1 × 3 = 3 mod 6
那么模运算到底有什么用呢?就是如果我们使用模运算,从运算结果再回到原始值并不容易,因为很多不同的组合会产生一个同样的运算结果:
5 × 4 = 2 mod 6
4 × 2 = 2 mod 6
2 × 1 = 1 mod 6
……
没有模运算的话,计算结果的大小会给找出原始值提供一些线索。除非这里既能把信息隐藏起来,又可以保留常见的算术属性。
我们再回到同态加密上,使用模运算,例如取模 7,我们可以得到:
5 1 = 5(mod 7)
5 2 = 4(mod 7)
5 3 = 6(mod 7)
……
不同指数下运算得到了同样的结果:
5 5 = 3(mod 7)
5 11 = 3(mod 7)
5 17 = 3(mod 7)
……
这样就很难知道指数是多少了。事实上,如果模取得相当大,从运算结果倒推指数运算就不可行了;现代密码学很大程度上就是基于这个问题的“困难”。
方案中所有的同态性质都在模运算中保留了下来:
encryption: 5 3 = 6 (mod 7)
multiplication: 6 2 = (5 3 ) 2 = 5 6 = 1 (mod 7)
addition: 5 3 · 5 2 = 5 5 = 3(mod 7)
注意: 模相除有点难已经超出范围了。
我们来明确地说明一下加密函数:
E(v) = g v (mod n)
这里 v 就是我们要加密的值。
Remark 3.2 这个同态加密模式有一个限制,我们可以把一个加密的值和一个未加密的值相乘,但我们不能将两个加密的值相乘(或者相除),也就是说我们不能对加密值取幂。虽然这些性质第一感觉看起来很不友好,但是这却构成了 zk-SNARK 的基础。这个限制后面将在“加密值乘法”一节中讲到。
注解 even@安比实验室:通过模运算形成的集合被称为「有限域」,而通过计算指数再进行模运算形成的集合构成「循环群」。常见的同态加密方式除了整数幂取模之外,还有椭圆曲线上的倍乘。
配合这些工具,我们现在就可以在加密的随机数 x 上做运算并相应地修改 零知识 协议了。
我们来看一下如何计算多项式 p(x) = x³ – 3x² + 2x 。我们前面明确了,知道一个多项式就是知道它的系数,也就是这个例子中知道:1,-3,2。因为同态加密并不允许再对加密值求幂,所以我们必须要给出 x 的 1 到 3 次幂取加密值: E ( x ) ,E ( x ²) ,E ( x ³),那么我们要计算的加密多项式就是:
E(x 3 ) 1 · E(x 2 ) -3 · E(x) 2 = (g x³ ) 1 · (g x² ) -3 ·(g x ) 2 = g 1x³ · (g -3x² )·(g x ) 2 = g x³-3x²+2x
所以通过这些运算,我们就获得了多项式在一些未知数 x 处的加密计算结果。这确实是一个很强大的机制,因为同态的性质,同一个多项式的加密运算在加密空间中始终是相同的。
我们现在就可以更新前面版本的协议了,比如对于阶数为 d 的多项式:
-
Verifier -
取一个随机数 s ,也就是秘密值 -
指数 i 取值为 0,1,…,d 时分别计算出 s 的 i 次幂的加密结果,即: -
代入 s 计算未加密的目标多项式: t(s) -
将对 s 的幂的加密值提供给 prover: E(s 0 ) , E(s 1 ),, E(s d )
-
-
Prover -
计算多项式 h(x) = p(x)/t(x) -
使用加密值 ,,…, 和系数 ,,…, 计算 g^p(s) 然后同样计算 E(h(s)) =g h(s) -
将结果 g p 和 g h 提供给 verifier
-
-
Verifier -
最后一步是 verifier 去校验 p = t(s) ·h: g p = (g h ) t(s) => g p = g t(s)·h
-
以太坊颠覆了以太坊:引入密码学实现 2.0 性能突破 https://www.8btc.com/article/609567
分布式系统的状态分片设计与密码学的成员证明设计相结合,实现以太坊 2.0 在性能上突破。...
云中「秘密」:构建非交互式零知识证明---探索零知识证明系列(五) https://www.8btc.com/article/544716
Once exposed, a secret loses all its power. 一旦泄露,秘密就失去了全部威力 ― Ann Aguirre...
技术干货 | 零知识证明Learn by Coding:libsnark 入门篇 https://www.8btc.com/article/542280
本文将带你亲自上手实践,在短时间内迅速入门 libsnark,一步步了解 libsnark 的基本概念,学会如何开发 zk-SNARKs 电路。...