要了解zkSNARK,必须先理解什么是零知识证明。
基本概念就是一个证明者Prover向验证者Verifier证明某个陈述(Statement)是真是假,但在证明过程中不揭露任何其他信息。比如身份证明,某一个组织让组织内成员提供身份证明,但不想泄露任何信息,就可以使用零知识证明来完成。
阿里巴巴被强盗抓住,为了保命,他需要向强盗证明自己拥有打开石门的密码,同时又不能把密码告诉强盗。他想出一个解决办法,先让强盗离开自己一箭之地,距离足够远让强盗无法听到口令,足够近让阿里巴巴无法在强盗的弓箭下逃生。阿里巴巴就在这个距离下向强盗展示了石门的打开和关闭。
这个整个过程就是零知识证明,证明者能够在不向验证者提供任何有用信息(石门的口令)的情况下,使验证者相信某个论断(阿里巴巴知道打开石门的方法)是正确的。
在计算机世界里面,零知识的应用场景就更多了,例如我们常用非对称加密来做身份认证,验证方只要使用公钥解出自己提供的随机数,即可证明被认证方的身份,不需要其提供自己的私钥。
zkSNARK
zkSNARK是zero-knowledge succint non-interactive arguments of knowledge的简称,全称里面每个单词都有特定的含义:
Zero knowledge: 零知识证明
Succinctness: 证据信息较短,方便验证
Non-interactivity: 几乎没有交互,证明者基本上只要提供一个字符串义工验证。对于区块链来说,这一点至关重要,意味着可以把该消息放在链上公开验证。
Arguments: 证明过程是计算完好(computationally soundness)的,证明者无法在合理的时间内造出伪证(破解)。跟计算完好对应的是理论完好(perfect soundness),密码学里面一般都是要求计算完好。
of knowledge: 对于一个证明者来说,在不知晓特定证明 (witness) 的前提下,构建一个有效的零知识证据是不可能的。
零知识证明大体由四部分组成:
- 多项式问题的转化- 需要证明的问题转化为多项式问题 t(x)h(x) = w(x)v(x),证明者提交证明让验证者确认多项式成立。
- 随机挑选验证- 随机选择验证的数值 s,验证 t(s)h(s) = w(s)v(s)。相对于验证多项式相等 t(x)h(x) = w(x)v(x),随机挑选验证,简单,验证数据少。随机挑选验证,安全性肯定不及多项式等式验证,但如果确实足够随机,安全性还是相当高的。
- 同态隐藏- 同态隐藏指的是函数的一种特性。输入的计算和输出的计算保持“同态”。以加法同态为例,满足如下的三个条件的函数 E(x),称为加法同态:1. 给定 E(x),很难推导出 x. 2. 不同的输入,对应不同输出 3. E(x+y) 可以由 E(x),E(y) 计算出来。乘法同态类似。
- 零知识- 证明者和验证者之间除了“问题证明与否”知识外,不知道其他任何知识(不知道随机挑选值,不知道挑选值的多项式计算结果等等)。
任意计算转换为多项式证明
假定A需要向B证明他知道c1,c2,c3,使(c1⋅c2)⋅(c1+c3)=7,按照惯例,c1,c2,c3需要对B保密。
我们要做的第一步就是把计算“拍平”,通过基本的运算符把原计算画成这样的“计算门电路”。
数学的方式来表达
S1=C1*C2
S2=C1+C3
S3=S1*S2
通过增加中间变量,我们把复杂的计算拍平,使用最简单的门电路表达。新的门电路跟原计算是等价的。
我们要做的第二步就是把每一个门电路表示为等价的向量点积形式,这个过程成为R1CS(rank-1 constraint system)。
对每个门电路,我们定义一组向量(a,b,c),使得s . a * s . b - s . c = 0。其中s代表全部输入的向量,也就是[C1,C2,C3,S1,S2,S3],为了让加法门也能用同样的方式表达,我们增加一个虚拟的变量成为one,s向量变成[one,C1,C2,C3,S1,S2,S3]。
对应到第一个门
a=[0,1,0,0,0,0,0]
b=[0,0,1,0,0,0,0]
c=[0,0,0,0,1,0,0]
把s,a,b和c代入s . a * s . b - s . c = 0,得到C1*C2-S1=0,即这个向量表达跟第一个门是完全等价的。
同理我们可以计算第二个门
a=[1,0,0,0,0,0,0]
b=[0,1,0,1,0,0,0]
c=[0,0,0,0,0,1,0]
第三个门
a=[0,0,0,0,1,0,0]
b=[0,0,0,0,0,1,0]
c=[0,0,0,0,0,0,1]
我们把一个计算式拍平成为门电路,接着又通过R1CS把门电路“编码”成向量的表达方式。
接下来是最重要的一步,把向量表达式表示为多项式,从而把向量的验证转化为多项式的验证,这个过程称为QAP(Quadratic Arithmetic Programs)。
具体办法是,在Fp上面选定任意三个不同的值,例如我们选定1,2,3,寻找一组多项式
使得多项式在x取值1,2,3的时候a,b,c数组的取值分别对应到前述三个门电路的向量。
问题转化为通过已知解倒推多项式定义,这部分可以使用拉格朗日插值完成,自动计算
比如
到这里,我们把原来的三个向量组表示成为一个用x表示的数组a(x),b(x),c(x)。
取多项式P(x)=s . a(x) * s . b(x) - s . c(x),根据我们原来的定义,在x取值为1,2或3的时候,P(x)=0。根据多项式特性,P(a)=0等价于P可以被(x-a)整除,P(x)一定能被(x-1)(x-2)(x-3)整除,也就是说存在H(X),使P(x)=T(x)*H(x),其中T(x)=(x-1)(x-2)(x-3)。
注意QAP这个过程把原来三个点的取值转化成为一个多项式,相当于中间插入了很多没有意义的值,这些值的取值与原公式是无关的。也就是说多项式的验证与原计算的验证本质并不等价,但验证了多项式也就验证了元计算。
最终我们把原算式的证明转化成为多项式的证明,只要证明P(x)=T(x)*H(x),即可验证原算式。
同态隐藏
说到zkSNARK,不能不提的一个概念就是同态隐藏
满足下面三个条件的函数E(x),我们称之为加法同态。
1.对于大部分的x,在给定的E(x)通常很难求解出x.
2.不同输入将会得到不同输出 - 因此如果x≠y,,则E(x)≠E(y).
3.如果某人知道了E(x)和E(y),,则他可以生成在算数运算式中的x和y.。比如,他们可以使用E(x)和E(y).来计算E(x+y)。
同理我们可以定义乘法同态甚至是全同态。我们常用的的非对称加密方式RSA和ECC都支持加法同态,计算和证明证明需要比较多的公式运算
跟RSA和ECC一样,注意这里的E(x)计算是在有限域里面进行,这个域下文称为Fp。
有了同态隐藏这个利器以后,我们就可以实现一定程度的零知识证明了。
A拥有x和y两个秘密的数字,需要向B证明这两个数字的和是7,只需要执行下面三个步骤:
1.A计算E(x),E(y),并发送给B
2.因为函数E(x)满足加法同态,B可以通过E(x),E(y)计算E(x+y)
3.B独立计算E(7),并验证E(x+y)=E(7)
KCA以及多项式盲验证
B根本没法验证A是真正利用多项式P(s)去计算结果,也就是说无法证明A真正知道这个多项式P(X)。
我们先定义一个概念:α对是指满足b=α*a的一对值(a,b)。注意这里的乘法其实是椭圆曲线(ECC)上的乘法,椭圆曲线上的运算符合两个特性:一是当α值很大的情况下,很难通过a和b倒推出α,二是加法和乘法满足可交换群的特性,也就是说加法和乘法交换律在椭圆曲线上也是成立的。只要记住椭圆函数的乘法满足同态隐藏的特性,即可完成下面的证明。
我们利用α对的特性,构建一个称为KCA(Knowledge of Coefficient Test and Assumption)的过程
1.B随机选择一个α生成α对(a,b),α自己保存,(a,b)发送给A
2.A选择γ,y就相当于多项式的系数生成(a′,b′)=(γ⋅a,γ⋅b),把(a′,b′)回传给B。利用交换律,可以证明(a′,b′)也是一个α对,b′=γ⋅b=γα⋅a=α(γ⋅a)=α⋅a′
3.B校验(a′,b′),证实是α对,就可以断言A知道γ
这个证明可以推广到多个α对的场景,称为d-KCA
1.B发送一系列的α对给A
2.A使用(a′,b′)=(c1⋅a1+c2⋅a2,c1⋅b1+c2⋅b2)生成新的α对
3.B验证通过,可以断言A知道c数组
一个完整的多项式盲验证过程如下
0因为椭圆曲线的乘法符合同态隐藏的特性,A和B可以共同选择x⋅g作为E(x)
1.B计算g,s⋅g,…,sd⋅g和α⋅g,αs⋅g,…,αsd⋅g并发送给A,实际上过程同上一章的第一步,只是把E(x)替代成乘法,增加了αs相应的多项式结果
2.A计算a=P(s)⋅g,b=αP(s)⋅g并回传
3.a值即为B所需校验的E(P(s))结果,同时KCA保证了a值必然是通过多项式生成
通过加法同态,我们可以实现加法隐藏,让B在不知道x和y的情况下,校验x+y的值。进一步,通过多项式盲验证,我们可以在不暴露多项式P(X)的情况下,让B校验任意给定s对应的P(s)。
匹诺曹协议
为了简化下文描述,我们定义s . a(x)为L(x),s . b(x)为R(x),s . c(x)为O(x),那么我们需要证明的等式就改写成L(x)*R(x)-O(x)=T(x)*H(x)。L,R和O的最高阶数是d,所以这个等式的最高阶数是2d,我们知道,两个不等价的多项式交点数量最多只有2d个,2d相较于有限域的元素个数p来说很小的情况下,我们可以采用采样的方式验证多项式相等,A随意选择多项式P(x)被校验通过的概率只有2d/p。随机采样校验的过程如下:
1.A按照上一章方法选择多项式L,R,O,H
2.B选择随机点s,计算E(T(s))
3.A计算E(L(s)),E(R(s)),E(O(s)),E(H(s)) (根据B发过来的E(s),E(s2),...)
4.B检验E(L(s)*R(s)-O(s))=E(H(s)*T(s))
这个证明过程还有三个问题需要解决:
1.保证L,R,O从同一组参数s生成
这个证明过程存在一个缺陷,正如按照我们的定义L(x)=s . a(x),R(x)=s . b(x),O(x)=s . c(x),这里隐含了一个限定条件是L,R和O必须是由同一个向量s生成,证明中忽略了这一点,也就是说A可以通过选择不符合这个限定条件的多项式来作弊。解决办法仍然是KCA,只不过这次的KCA要复杂一些。
先定义两个公式:
这个公式的含义是要把L,R,O的指数错开,如果L,R,O真是从同一组s=[s1,....sm]生成的话,必然有
换句话说,只要A能给出F和Fi的线性组合,即可证明L,R,O符合限定条件。这个限定条件的问题就转化为一个d-KCA的问题了。
1.B选择隐秘的α,计算E(α*Fi)并发送给A
2.A计算E(αF)回传给B
3.B根据本文公式自行计算E(F)并校验α对
2.防止暴力破解
在现在的流程里面,A需要把E(L(s)),E(R(s)),E(O(s)),根据同态隐藏的特性,根据这些值无法倒推原多项式。但是如果需要验证的问题,解不多的情况下,B还是可以通过穷举的方式暴力破解原问题,得到A的原始数据。例如我们已知A有两个正整数,要求盲验证这两个正整数的乘积是12,那么B完全可以穷举乘积是12的所有正整数组合,正向执行验证过程,与E(L(s)),E(R(s))和E(O(s))比对即可知道正确的答案是什么。
当然,我们也有解决办法。解决思路就是在生成L,R,O的时候引入随机偏置
因为
令
新的组合
任然可以通过多项式的校验,而因为B不知道随机数,也无法通过暴力破解的方式知晓原始参数。
5.减少交互
最后一个问题也是最关键的一个问题是,匹诺曹协议中需要A和B之间做很多的消息交互,而在区块链中,我们想要做到的是“公开认证”。最理想的情况就是只要A把证据作为一个字符串放置到链上,任何人都能验证结论。
可惜的是,实际上这种严格意义上的零交互证明已经被证明不能满足所有的证明场景。我们退而求其次,采用了一种称为CRS(COMMON REFERENCE STRING)的方式。原理很简单,实际上就是把随机数α和s内置于“系统”中。
所以终极版的zkSNARK过程就是:
0.配置α和s,以之计算 (E1(1),E1(s),…,E1(sd),E2(α),E2(αs),…,E2(αsd))E2(α),并公示
1.A使用公示参数计算验证多项式
2.B校验多项式,乘法同态部分利用椭圆曲线配对的特性完成,形如E(αx)=Tate(E1(x),E2(α))
当然CRS有一个极其严重的问题就是,“系统”内建的随机参数非常重要,知道这个秘密参数的人就拥有超级管理员的权限,可以任意制造伪币,这在一个去中心化的系统中几乎是不可接受的。