zk-SNARK, Pinocchio
Table of Contents
1. 零知识证明简介
零知识证明(Zero-knowledge proof),由 Shafi Goldwasser(2012 年图灵奖获得者)、Silvio Micali(2012 年图灵奖获得者)及 Charles Rackoff(1993 年哥德尔奖获得者) 在 1989 年发表的论文“The Knowledge Complexity of Interactive Proof-Systems”(可简称 GMR89)中提出。它指的是 证明者(prover)能够在不向验证者(verifier)提供任何有用的信息的情况下,使验证者(verifier)相信某个论断是正确的。
顾名思义,零知识证明就是既能充分证明自己是某种权益的合法拥有者,又不把有关的信息泄露出去——即给外界的“知识”为“零”。其实,零知识证明并不是什么新东西,早在 16 世纪的文艺复兴时期,意大利有两位数学家为竞争一元三次方程求根公式发现者的桂冠,就采用了零知识证明的方法。当时,数学家塔尔塔里雅和菲奥都宣称自己掌握了这个求根公式,为了证明自己没有说谎,又不把公式的具体内容公布出来(可能在当时数学公式也是一种技术秘密),他们摆开了擂台:双方各出 30 个一元三次方程给对方解,谁能全部解出,就说明谁掌握了这个公式。比赛结果显示,塔尔塔里雅解出了菲奥出的全部 30 个方程,而菲奥一个也解不出。于是人们相信塔尔塔里雅是一元三次方程求根公式的真正发现者,虽然当时除了塔尔塔里雅外,谁也不知道这个公式到底是个什么样子。摘自:https://baike.baidu.com/item/%E9%9B%B6%E7%9F%A5%E8%AF%86%E8%AF%81%E6%98%8E
1.1. 零知识证明协议的 3 个性质
在任意的「零知识证明」系统中,都有一个证明者(prover)在不泄漏任何额外信息的前提下要让验证者(verifier)确信某些陈述(Statement)是正确的。零知识证明协议应当满足下面三个性质:
- 完备性(Completeness):只要「陈述」是正确的,prover 就可以让 verifier 确信;也就是说 verifier 无法欺骗 prover;
- 可靠性(Soundness):如果「陈述」是错误的,那么作弊的 prover 就没有办法让 verifier 相信;也就是说 prover 无法欺骗 verifier;
- 零知识(Zero-knowledge):协议的交互仅仅揭露「陈述」是否正确而不泄漏任何其它的信息。
1.2. 零知识证明应用场景
下面是零知识证明的一些应用场景。
1、证明关于隐私数据的声明:
- 一个人 A 的银行账户金额多于 X;
- 去年,一家银行未与实体 Y 进行交易;
- 在不暴露全部 DNA 数据的前提下匹配 DNA;
- 一个人的信用评分高于 Z。
2、匿名认证:
- 在不揭露身份的情况下(比如登录密码),证明请求者 R 有权访问网站的受限区域;
- 证明一个人来自一组被允许的国家/地区列表中的某个国家/地区,但不暴露具体是哪个;
- 证明一个人持有地铁月票,而不透露卡号。
3、匿名支付:
- 付款完全脱离任何一种身份;
- 纳税而不透露收入。
4、外包计算:
- 将昂贵的计算外包,并在不重新执行的情况下验证结果是否正确;它打开了一种零信任计算的类别;
- 改进区块链模型,从所有节点做同样的计算,到只需一方计算然后其它节点进行验证。
2. 证明的媒介
先不要去管零知识,非交互性,其形式和适用性等,我们从尝试证明一些简单的东西开始。
假设有个长度为 10 的位数组,现在要向 verifier 证明这样一个陈述:所有的位都被设置为 1。
verifier 只能一次检查(读取)一位。为了验证这个陈述,我们以某种任意的顺序读取元素并检查其是否确实等于 1 。如果第一次抽样检查的结果是 1,就设置「陈述」的可信度为 10%,否则,如果等于 0,就说明「陈述」是错误的。验证者继续进行下一轮验证,直到获得足够的可信度为止。假如在一些场景下要信任 prover 需要至少 50% 的可信度,那就意味着必须执行 5 次校验。但假如在其它一些场景下需要 95% 的可信度,就需要检查所有的元素。很明显这个证明协议的缺点是必须要根据元素的数量进行检查,如果我们处理数百万个元素的数组,这么做是不现实的。
2.1. 引入多项式
现在我们来看一下由数学方程式表示的多项式,它可以被画成坐标系上的一条曲线,如图 1 所示。
Figure 1:
图 1 中曲线对应的多项式为:
多项式有一个非常好的特性,就是 如果我们有两个阶为
Figure 2: 两个 3 阶多项式,它们的交点最多有 3 个
在
代数的基本原理告诉我们, 对于一个阶数为
事实上, 我们不可能找到两条不同的曲线,它们会在某段区域内重合(它们只会相交于一些点)。
2.2. 证明 prover 知道某个多项式
如果一个 prover 声称他知道 verifier 也知道的某个多项式(比如 3 阶多项式
在不泄露多项式的前提下,我们可以采用下面的简单协议:
- 在一个较大的范围内(如
到 ),verifier 选择一个随机值 并在本地计算多项式结果; - verifier 将
值给到 prover,并让他计算相关的多项式结果; - prover 代入
到多项式计算并将结果给到 verifier; - verifier 检查本地的计算结果和 prover 的计算结果是否相等,如果相等那就说明 prover 的陈述具有较高的可信度。
上面的证明过程为什么可行呢?会不会出现 prover 并不知道多项式
上一节介绍过, 如果我们有两个阶为
2.3. Schwartz-Zippel 定理
Schwartz-Zippel 定理:在有限域
3. 因式分解
如果一个 prover 声称他知道一个 3 阶多项式
我们先了解一下因式分解。代数的基本定理表明了任意的一个多项式只要它有解,就可以将它分解成线性多项式(即阶数为 1 的多项式)的乘积,因此,我们可以把任意有效的多项式看成是其因式的乘积:
例如,多项式
也就是说这个多项式的解就是
我们再回到前面的问题,prover 宣称他知道一个阶数为 3,其中两个根分别为 1 和 2 的多项式,也就是说这个多项式的形式为:
如果 prover 想要在不揭示多项式的前提下证明他的多项式确实有这两个根,那么他就需要去证明他的多项式
为了简化起见,后面我们会用多项式的字母变量来代替计算结果值,例如:
3.1. 证明 prover 知道的 d 阶多项式包含指定因子
我们可以使用下面检查协议来证明 prover 没有说谎(即 prover 知道的 3 阶多项式
- verifier 挑选一个随机值
,计算 ,将 发送给 prover; - prover 计算
,并对 和 进行求值,将计算结果 提供给 verifier; - verifier 验证
,如果这个等式成立相等,就意味着 是 的因式。
实践一下,假设 prover 声明他知道的 3 阶多项式为
- verifier 选一个随机数 23,并计算
,然后将 23 发给 prover; - prover 计算
, 并对 和 进行求值, ,将 和 提供给 verifier; - verifier 再验证
,即 ,这显然是正确的,这样陈述就被证明了。
3.2. 证明协议中存在的问题
- prover 可能并不知道他所声称的
,他可以先算一下 ,然后随便选择一个数 ,由此计算出 。因为等式是成立的,所以也能通过 verifier 的校验。比如,prover 计算出来 后,随便选择一个数,如 ,由此计算出 ,将 和 提供给 verifier,这也会通过 verifier 的验证。 - 因为 prover 知道随机点
,他可以构造出一个任意的多项式,这个任意多项式与 在 处有共同点。比如,prover 知道 verifier 发送给他的值为 23 后,随便找一个包含 因子的多项式,例如 , 去验证上面协议,照样可以通过验证。 - 在前面的「陈述」中,prover 声称他知道一个特定阶数的多项式,但现在的协议对阶数并没有明确的要求。因而 prover 完全可以拿一个满足因式校验的更高阶数的多项式来欺骗 verifier。比如,尽管要证明 prover 声称知道的
,但在上面协议中,prover 使用另外一个更高阶数多项式,如 去验证上面协议,照样可以通过验证。
4. 模糊计算
在节 3.2 中提到的前 2 个问题是由于暴露了原始值(例子中的随机数 23)而导致的,也就是 prover 知道了
如何在不暴露原始值前提下,还进行验证呢?请看下文。
4.1. 同态加密
同态加密(Homomorphic encryption)可以解决前面提到的“原始值暴露”的问题。 同态加密允许在密文上进行算术运算,进行算术运算时不用先解密出原文。
同态加密关注的是数据处理安全。同态加密提供了一种对加密数据进行处理的功能。我们举个实际生活中的例子(这个例子摘自 https://www.zhihu.com/question/27645858/answer/37598506 )。有个叫 Alice 的用户买到了一大块金子,她想让工人把这块金子打造成一个项链。但是工人在打造的过程中有可能会偷金子啊,毕竟就是一克金子也值很多钱。因此能不能有一种方法,让工人可以对金块进行加工,但是不能得到任何金子?
当然有办法啦,Alice 可以这么做:
- Alice 将金子锁在一个密闭的盒子里面,这个盒子安装了一个手套。
- 工人可以带着这个手套,对盒子内部的金子进行处理。但是盒子是锁着的,所以工人不仅拿不到金块,连处理过程中掉下的任何金子都拿不到。
- 加工完成后。Alice 拿回这个盒子,把锁打开,就得到了金子。
这个过程如图 3 所示。
Figure 3: 同态加密类比:工人不直接接触金子的情况下加工金子
同态加密的思路是选择一个基础的(基数需要具有某些特定的属性)的自然数
这里 125 就是 3 对应的密文。如果我们想要对原数据(被加密值)乘 2,我们可以以 2 为指数来对这个密文进行计算:
我们不仅可以用 2 来乘以一个未知的值并保持密文的有效性,还可以通过密文相乘来使两个原数据相加,例如原数据上的
不过由于基数 5 是公开的,很容易就可以找到被加密的数字。只要将密文一直除以 5,直到结果为 1,那么做除法的次数也就是被加密值的数。如密文
先看几个模运算的例子(对 7 取模):
引入模运算后,告诉你加密后的值,就很难知道指数(原值)是多少了。比如,告诉你加密后的值为 6,那么 3,9,15 都满足条件,就无法知道原值到底是多少了。
引入模运算后,前面介绍的性质没变:
上面第 2/3 个式子的含义是: 对加密后的值取
我们来明确地说明一下加密函数:
这里
4.1.1. 同态加密的限制
对于上面介绍的同态加密,有个限制: 给你两个加密值,我们没有办法通过加密值上的运算来实现“原数据的相乘”。
前面介绍的
在节 6.1 中将介绍如何处理这个问题。
4.2. 新的验证协议
有了同态加密后,对于在节 3 中提到的证明问题,可以为它重新设计一个验证协议了,在这个验证协议中,不会暴露原始的随机数了。
在介绍新验证协议之前,先介绍一下已知在
现在我们更新一下节 3 中设计的证明协议。
prover 声称他知道一个
验证过程如下(在下面的写法中把
第一步,verifier 进行下面操作:
- 取一个随机数
,也称为秘密值; - 分别计算
取值为 时 的加密结果,即 ; - 代入
计算未加密的目标多项式 ; - 将第 2 步计算出来的加密值
提供给 prover。
第二步,prover 进行下面操作:
- 计算多项式
; - 使用加密值
和 prover 所已知的 的系数 计算出 的加密值,采用式子 即可,其结果记为 ; - 使用和上一步相同的方法计算出
的加密值 ,记为 ; - 把上两步的结果
和 提供给 verifier。
最后,verifier 进行下面验证:
- 在加密空间中,校验
是否成立。如果成立,则说明 ,从而 prover 所知道的多项式 有很大的可能性确实具有因子 。
在这个证明协议中,prover 并不知道跟秘密值
尽管这个协议中对 prover 作弊的手段进行了限制,但 prover 依然可以在实际根本不使用 verifier 所提供的加密值进行计算,而是通过其它的方式来伪造证明。也就是说,在 prover 提交给 verifier 的两个值
4.2.1. 具体实例
我们以节 3 提到的证明问题为例,即 prover 声称他知道一个 3 阶多项式(假设为
设加密函数为
第一步,verifier 进行下面操作:
- 取一个随机数
,也称为秘密值; - 分别计算
取值为 时 的加密结果,即 ; - 代入
计算未加密的目标多项式 ; - 将第 2 步计算出来的加密值
提供给 prover。
第二步,prover 进行下面操作:
- 计算多项式
; - 使用加密值
和 prover 所已知的 的系数 计算出 的加密值,采用式子 即可,其结果记为 ; - 使用和上一步相同的方法计算出
的加密值 ,记为 ; - 把上两步的结果
和 提供给 verifier。
最后,verifier 验证
上面过程中涉及了分数取模,即
https://stackoverflow.com/questions/32418693/modular-arithmetic-using-fractions
https://math.stackexchange.com/questions/586595/finding-modular-of-a-fraction
4.3. 限制多项式(Knowledge-of-Exponent Assumption)
上一节的验证协议中有个漏洞:prover 可能使用另外的值
我们看一个由一个变量和一个系数组成的一阶多项式的简单例子,对应的
通过 Knowledge-of-Exponent Assumption(简称 KEA)方法可以实现上面的目标。 后文将描述 KEA。
4.3.1. 限制对某个值只能进行指数取模运算
Alice 有一个值
1、为了确保上面这一点,Alice 进行下面操作:
1.1、选择一个随机数
1.2、计算
1.3、提供一个元组
2、因为 Bob 无法从元组
2.1、选择一个值
2.2、计算
2.3、回复
3、有了回复的元组
最后一步,如果 Alice 验证
注:在上面协议中,随机数
上面介绍的 KEA 方法可表示为:
Alice Bob (a, a^\alpha mod n) ----希望 Bob 对这两个值只进行指数取模运算----> 黑盒子 <----- 运算后返回 (b, b') ------- 如果 Alice 验证 b^\alpha = b' mod n 成立,则数 b 确实是由 Bob 对 a 进行「指数取模」运算而来。
我们把
在同态加密中,求幂是对被加密值进行乘法运算。我们可以应用这个结构到一个简单的系数多项式
- verifier 选择随机数
,把值 提供给 prover; - prover 代入系数
计算 ,把结果 返回给 verifier; - verifier 验证
,如果成立说明 prover 是对 (而不是其它值)进行指数取模运算。
4.3.2. 扩展 KEA 到多项式
现在我们可以扩展这种单项式上的 KEA 方法到多项式上。对于阶数为
1、verifier 选择随机数
2、prover 进行下面操作:
2.1、使用 verifer 提供的加密值,计算
2.2、使用 verifer 提供的加密值的
2.3、把上面两步的结果作为
3、验证
以前面介绍过的多项式
1、verifier 选择随机数
2、prover 进行下面计算,把结果返回给 verifier:
3、verifier 验证
现在我们就可以确保 prover 是用 verifier 提供的加密值(而不是其它值)做指数取模运算(而不是其它运算)了,因为别的方法不能够保证 prover 返回给 verifier 的两个值
把上面的 KEA 过程结合到节 4.2 介绍的协议中,我们就有了一个比较健壮的协议了。
5. 零知识
KEA 过程结合到节 4.2 介绍的协议后(参见上节),prover 提供了 3 个值
如果上面第一个式子通过验证说明
现在的问题是,我们担心 verifier 能够从 prover 提供的数据
如何使得两个校验依然有效,同时又保证没有知识能被提取?我们可以使用随机值
verifier 收到这 3 个值后,同样可以采用前面的方式进行验证:
由于这种方式实现零知识是如此简单,这种方式也被称为“无成本的”零知识。
借助这个“无成本的”技巧,就可以轻松实现零知识了。但是这里实现零知识的方法和实际中的 Pinocchio 协议(参考节 7.12),还有 Groth16 方案略有不同。
6. 非交互式(Non-Interactivity)
到现在为止,我们已经讲完了一个交互式的零知识方案。但为什么我们还需要有非交互式(Non-Interactive Zero-Knowledge, NIZK)呢?因为交互式证明只对原始的 verifier 有效,其他任何人(其他的 verifier)都不能够信任这个证明,因为:
- verifier 可以和 prover 串通,告诉他秘密参数
,有了这些参数 prover 就可以伪造证明; - verifier 也可以使用同样的方法自己伪造证明;
- verifier 必须保存
和 直到所有相关证明被验证完毕,这就带来了一个可能造成秘密参数泄漏的额外攻击面。
我们需要一个可以被重复使用,公开,可信,又不会被滥用的秘密参数。
我们先来思考一下在构造出秘密值
下一节将解决这个问题。
6.1. 加密空间中实现原数据相乘(Elliptic Curve Pairings)
Cryptographic pairings (bilinear map) 是一个数学结构,记为函数
Figure 4: Cryptographic pairings
因为源数据集和输出数据集是不同的,所以一个配对的结果不能用做其他配对计算的输入。我们可以将输出集(也称为“目标集”)视为“不同的宇宙”。因而我们不能用另一个加密值乘以结果,而且配对这个名称本身也表明了,我们一次只能将两个加密值相乘。 注:乍一眼看过去,这个限制可能会阻碍相关功能的实现,但在 zk-SNARK 中这反而是保证安全模式的最重要性质,参见节 6.4。
在某种意义上,这个类似于一个哈希函数,他将所有可能的输入值映射到输出值的集合中的一个元素上,而且这个过程是不可逆的。
配对函数
因为基数在“转换”中被修改了,所以在另一个配对中不能再使用这个结果
配对的核心性质可以表示成下面的等式:
严格来说,一个 Cryptographic pairing 的结果是在目标集的一个不同生成元
Cryptographic pairing 利用“椭圆曲线”来实现上面性质,也可称“Elliptic Curve Pairings”。
关于 Cryptographic pairings 的更多知识,可参考 Pairings for beginners, by Craig Costello 。
6.2. 可信任参与方的 Setup
有了 Cryptographic pairing,我们现在就准备去设置安全公开且可复用的参数了。假定一下我们让一个诚实的参与方来生成秘密值
注:Zcash 团队在论文 A multi-party protocol for constructing the public parameters of the Pinocchio zk-SNARK 中提出了 MPC 协议来进行“可信任参与方的 Setup”。
这些参数通常被称为 common reference string 或者 CRS。CRS 生成后,任何的 prover 和任何的 verifier 都可以使用它来构造非交互式的零知识证明协议。CRS 的优化版本将包含目标多项式的加密值
CRS 可分成两组(
- proving key(也被称为 evaluation key):
- verification key:
只要能够乘以加密值,verifier 就可以在协议的最后一步验证多项式了。有了 verification key,verifier 就可以处理从 prover 那里得到的加密多项式的值
- 在加密空间中校验
,即校验 :
- 校验多项式的限制:
6.3. 信任任意一个参与者
可信任参与方的 Setup 很有效率,但众多 CRS 用户也必须要相信生成者确实删除了
一种解决办法就是由多个参与方使用前面小节中介绍的数学工具来生成一个组合式 CRS,这样这些参与方就都不知道“秘密”了。 下面是一个实现方案,我们假设有三个参与者 Alice,Bob 和 Carol ,对应为 A,B 和 C,其中
- Alice 选择随机数
和 ,然后公开她的 CRS: - Bob 选择他的随机数
和 ,然后通过同态乘法结合 Alice 的 CRS: 然后公开两方 Alice-Bob 的 CRS 结果: - Carol 用她的随机数
和 做同样的事: 然后公开 Alice-Bob-Carol 的 CRS 结果:
这个协议最后我们就获得了一个混合的
有一个问题是如何验证参与者在生成 CRS 时用的随机数值是一致的(有效的),因为攻击者可以生成多个不同的随机数
庆幸的是,因为我们可以使用配对来乘以加密值,所以我们就可以从第一个参数开始逐一执行一致性校验,并且确保了每个参数都源于前一个。
- 我们用
的 1 次幂作为标准来校验每一个其它次幂的值与之是否保持一致: 。例如:对于 2 次幂这样验证: ;对于 3 次幂这样验证: - 我们现在再验证一下前面步骤中
变换后的值是否正确: 。记号 表示 ,后文将大量使用这个记号。例如:对于 3 次幂这样验证:
当我们在验证每一个参与者秘密参数的一致性时,要注意参与者生成 CRS 的过程并没有强制后一个参与者(就是我们例子中的 Bob 和 Carol)都要使用前面已经公开的 CRS。因而如果一个攻击者是链上的最后一个参与者,他可以像链上的第一个参与者一样忽略前面的 CRS 随便构造一个有效的 CRS,这样他就变成了唯一一个知道秘密
为了解决这个问题,我们可以额外再要求除了第一个以外的每一个参与者去加密然后公开他的参数。例如,Bob 同时还要公开:
这样,为了确认 Bob 的 CRS 确实是乘以了 Alice 的参数后获得的,验证下面式子即可:
类似的方法,Carol 也可证明她的 CRS 是乘以了 Alice-Bob 的 CRS 后正常获得的。
这是一个健壮的 CRS 设置模式,它并不完全依赖于单个参与者。事实上,即使其它所有的参与者都串谋了,只要有一个参与者是诚实的,他能够删除并且永远不共享它的秘密参数,这个 CRS 就是有效的。
6.4. 多项式的 SNARK
到目前为止,我们已经介绍了多项式的 SNARK (Succinct Non-Interactive Argument of Knowledge) 协议。
为简洁起见,我们将使用大括号来表示由旁边的下标填充的一组元素,例如:
这里再重复一下所要证明的问题。 prover 声称他知道一个
- Setup
- 挑选随机值
- 计算加密值
- 生成 proving key:
- 生成 verification key:
- 挑选随机值
- Proving
- 求多项式
- 使用
计算多项式 和 的值 - 使用
计算多项式 的值 - 选择随机数
- 构造
,它们被称为“提供的证明”,简记为
- 求多项式
- Verification
- 验证多项式约束:
- 验证多项式系数:
- 验证多项式约束:
6.4.1. 安全的关键:Cryptographic pairing 结果不能复用
如果 Cryptographic pairing 的结果有可能在其它类似的乘法协议中被复用,那么这里就完全没有安全性可言了,因为这样的话 prover 就可以构造:
这样也可以通过“多项式约束”的检查:
6.5. 结论
我们用 zk-SNARK 协议来解决多项式问题的知识,不过这是一个有局限的例子。因为大家可以说 prover 只要用另外一个有界的多项式去乘以
verifier 知道 prover 有一个有效的多项式,但是并不知道是哪一个。我们可以利用多项式的其他性质添加额外的证明,如:被多个多项式整除,是某个多项式的平方等等。
下面总结一下这篇文章中是如何一步一步解决了下面的几个问题:
- 保证 prover 的证明是按照规则正确构造的 ——> KEA
- 保证知识的零知性 ——> “无成本的”
变换 - 可复用证明 ——> 非交互式
- 非交互中如何设置安全公开且可复用的参数 ——> 参数加密,verifier 借助密码配对进行验证
- 保证参数的生成者不泄密 ——> 多方的 Setup
至此,一个用来证明多项式知识的完整的 zk-SNARK 协议就构造出来了,不过现在的协议在通用性上依然还有很多限制,后面的文章将继续介绍如何构造通用的 zk-SNARK。
7. 通用的零知识证明
前面我们已经理顺了一个 zk-SNARK 简单方案的思路,它包含了 zk-SNARK 大部分内在机制,现在我们继续完善方案,使它能执行零知识程序。
7.1. 计算
Algorithm 1: Operation depends on an input ———————————————————————————————————————————————————————————————————— function calc(w, a, b) if w then return a × b else return a + b end if end function
看起来这段程序和我们协议里面的多项式并没有什么关系。我们现在就 需要找到一种方式来将程序转换成多项式。 第一步是将程序转换成数学语言,这个相对简单一些,声明可以表达成下面的形式(假定
执行
接下来(上面的例子)需要我们证明的是,对于表达式
7.2. 单个运算符
尽管已经将程序转换成了由数学语言表达的一般计算形式,我们还是需要再把它翻译成多项式。我们先来仔细研究一下计算的本质。任何计算的内核都是由以下形式的基本运算组成的:
两个操作数(即值)与一个运算符(即 +,-,×,÷)放在一起执行运算。例如操作数是 2 和 3,运算符为乘,那么运算出来就是
7.2.1. 关联多项式和算术运算
我们先来看一下如何将多项式和算术运算关联起来。例如有两个多项式
Figure 5:
当
Figure 6:
同样地,如果我们将
Figure 7:
如果我们可以将操作数的值表示为多项式(我们也确实可以这么做),那么利用算术属性,我们就能够得到操作数的计算结果了。
7.3. 限制运算
如果一个 prover 声称知道某两个数字的乘积,verifier 要怎样去验证呢?为了证明单个计算的正确性,我们就 必须首先确保所提供的操作数的输出(结果)的正确性。 我们再来看一下运算的形式:
类似地,我们也可以将其表示为一个运算多项式:
在一些选定的取值
:表示(运算结果为)左操作数 :表示(运算结果为)右操作数 :表示运算结果(输出)
因而在计算过程中如果操作数和结果都可以用多项式的形式正确地表示出来,那么
例如,我们来看一个运算:
Figure 8:
因而如果 prover 用
Figure 9:
图 9 中这个多项式并没有
7.4. 运算的证明
现在我们来修改一下最新协议使它支持单个乘法计算的证明。在节 6.4 中,我们已经有证明多项式
所以 prover 必须要分别提供多项式
保持 setup 阶段(参见节 6.4)不变,协议更新为:
- Proving
- 计算多项式
- 使用
计算多项式 的值 - 使用
计算多项式 的值 - 构造
,它们被称为“提供的证明”,简记为
- 计算多项式
- Verification
- 验证多项式约束:
- 验证运算的有效性:
- 验证多项式约束:
这个协议就能够证明两个值相乘的计算结果是正确的了。
你可能注意到了在这个新的协议中我们放弃了零知识部分(即没有引入节 6.4 中用于实现零知识的随机数
7.5. 多个运算
现在我们已经能够证明单个算术运算了,但是要怎么扩展到多个运算上呢(这是我们的最终目标)?我们来尝试一下增加一个基本运算。想一想计算
前面的讨论中我们通过对运算符多项式在任意
Figure 10: 一次同时执行两个运算
这种独立性允许我们一次同时执行两个运算并且又不会把这两者搞乱。这个多项式算术的结果就变成了图 11 所示。
Figure 11:
可以看出这个多项式有两个根
我们再来看一个有三个乘法运算的例子
我们要把它们表示为操作数多项式, 对于由
现在是问题是, 我们要怎么找到经过这些点的多项式呢? 对于任何包含超过一个点的例子,就必须要使用特定的数学方法了。
7.5.1. 多项式插值
为了构造操作数和输出多项式,我们需要一种方法来用给定的一组点去构造一个能经过所有这些点的弯曲多项式,这叫插值。有几种不同的方法可以算出这个多项式:
- 一组未知方程
- 牛顿多项式
- Neville 算法
- 拉格朗日多项式
- 快速傅里叶变换
我们以第一个方法为例。这个方法的思路是存在一个系数未知,阶数至多为
我们先来看一下如何求得
因而“左操作数多项式”就是:
我们用相同的方式可以计算得到:
7.5.2. 含有多个运算的多项式
现在我们有了代表三个运算的操作数多项式,再进一步看一下怎么去校验每个计算的正确性。回到 verifier 寻找等式
Figure 12:
结合上一节得到的
把
7.6. 变量多项式
有了前文介绍的方法来解决证明多个计算多项式的问题,我们就可以一次证明多个运算(如上百万个甚至更多)了,但是这个方案还有一个关键的缺点。
如果证明中执行的“程序”在不同运算中使用了相同的变量作为操作数或输出, 例如:
这里
Figure 13:
然而,因为我们的协议中是允许 prover 为多项式设置任何系数的,所以他可以不受限制地为不同计算(即,用一些
Figure 14:
这个自由打破了一致性,有空间允许 prover 能去证明了一些并非 verifier 感兴趣的其它无关的程序执行。因而我们 必须要确保每一个变量(assignment)在所有运算中出现的地方都只有一个取值。
注意:文中的“变量”与常规的计算机科学中变量的定义不同,这里的变量是不可改变的而且每次执行都只赋值一次。请务必注意这里“变量”的含义,它是程序中的变量,但是却又不能改,这不是很矛盾吗?其实,这里变量是指本文开头的示例伪代码中的那些不会被修改的变量。在 zkSNARK 论文中,这个“变量”其实有一个对应的名词叫做 assignment,是算术电路的“赋值”。而所有的 assignments 是一个算术电路可满足性问题的解,包含了算术电路的输入值以及电路运算过程中输出的中间结果值。
7.6.1. 单个变量的操作数多项式
我们来看一个简单的例子(就是当前的例子),在左操作符多项式
我们来仔细看看包含相等值的多项式。例如检查一下图 15 中的两个多项式,他们分别都表示了有两个相等值对应的运算(即在
Figure 15:
注意每个多项式中相应的系数是成比例的,也就是第二个多项式的系数是第一个的两倍大,即:
那么由于多项式的算术性质,如果我们想要同时地改变多项式中所有的值我们就需要改变它的比例,如果我们用一个数字乘以多项式,那么在每一个可能的
因此, 如果 verifier 需要在所有计算中强制 prover 设置相同的值,他就要限制 prover 只能修改比例而不是单个系数。
所以怎么保持系数比例不变呢?对于这个问题我们可以先思考一下在左运算多项式中我们提供的证明是什么。是
和限制单个求幂值相似,verfier 可以一次限制完整的多项式。我们知道,限制单个求幂值时,提供单独的加密
- Setup
- 使用对应的系数构造相应的操作符多项式
- 选择随机数
和 - 使用加密的
和它的“变换” 来设置 proving key - 设置 verification key:
- 使用对应的系数构造相应的操作符多项式
- Proving
- 对于操作数值
- 乘以操作数多项式:
- 乘以变换后的操作数多项式:
- 乘以操作数多项式:
- 根据上面的计算,构造
,它们被称为“提供的证明”,简记为
- 对于操作数值
- Verification
- 验证比例:
- 验证比例:
prover 需要返回同样的
有了这个协议后,怎么去构造满足要求(即经过点
Figure 16:
然后再让 prover 在其上”分配“一个值
Figure 17:
7.6.1.1. 存在的问题
上节的协议中,由于 verification key 包含了加密了的
因而可以修改多项式使其超出 verifier 的预期以及证明一个不同的声明,在节 7.9.3 我们将会解决掉这个问题。
7.6.2. 多变量的操作数多项式
通过前面的介绍,我们已经解决了“证明中执行的程序在不同运算中使用了相同的变量
Figure 18: 左操作数为
假如我们还使用相同的方法,我们无法分别地为每一个变量设置值,并且让这些变量乘到一起去。也就是说这个受限的多项式只能支持一个变量的情况。我们可以把操作数多项式
这与上一节的方法一致,变量
有了这个算术性质我们就可以逐一构造操作数变量多项式了,这样如果变量多项式在一个对应运算中被用做操作数,那么这一项就置为 1,否则就置为 0。0 跟任何值相乘结果都是零,当把他们相加在一起的时候也就可以忽略掉这一项。在我们的例子中这些变量多项式必须满足以下计算:
如图 19 所示。
Figure 19:
于是我们就可以将每个变量分开设置值,然后把他们加在一起来计算出操作数多项式,例如当
Figure 20:
注意:我们在一个值的后面使用下标表示它代表的变量,如,
现在起我们用大写字母来表示这个复杂的操作符多项式,即:
计算结果为
- Setup
- 构造
使得它能够在对应的 “计算 x” 处为 1,在其他地方为 0 - 选择随机数
- 计算并加密未赋值的变量多项式:
- 计算变换后的这些多项式:
- 上面得到的值组成 proving key:
- 设置 verification key:
- 构造
- Proving
- 为变量多项式赋值
和 : - 对变换后的变量多项式做同样的赋值:
- 把所有赋值好的变量多项式加起来变成操作数多项式的形式:
- 和上类似,计算一个
变换版本: - 提供左操作数的有效证明:
,简记为
- 为变量多项式赋值
- Verification
- 验证提供的多项式是否是最初提供的多个未赋值的变量多项式的和:
,这里也就是验证了
- 验证提供的多项式是否是最初提供的多个未赋值的变量多项式的和:
注意:
结果,prover :
- 除了“分配”值外,不能再修改它们的系数进而来修改变量多项式 了,因为 prover 只拿到了这些多项式的加密值,也因为 s 必要次幂的加密值不能与它们的
变换值一起使用。 - 不能通过另一个多项式相加去提供一个结果因为这样 α-比例关系将会被破坏掉。
- 不能通过与其他的一些多项式
相乘来修改操作数多项式,这样可能会使得修改后的值不成比例因为在预配对空间中无法进行加密乘法。
不过尽管 prover 被限制了多项式的使用,他还有拥有一些可允许范围内的自由度:
- 当 prover 决定不加入一些变量多项式
来构造操作符多项式 时依然是可以接受的,因为这和为它分配值为 0 是一样的: - 如果 prover 添加同一个变量多项式多次也是可以接受的因为这和一次分配多个值的和是一样的:
这些方法在右操作数和输出操作数
7.7. Construction Properties(结构性质)
上文中的修改额外带来了一些其它有用的性质。
7.7.1. 常量系数
在上文的构造中,我们通过对“未赋值的变量多项式”的计算得到 0 或者 1 ,以此表示在运算中是否要用到这个变量。自然地想,我们也可以使用其它系数值,包括负数值,因为我们可以插值计算出经过任何必要的点(前提是没有两个计算使用了同一个
现在我们的程序就可以使用常量系数了,例如:
Algorithm 2: Constant coefficients ————————————————————————————————————————————————————— function calc(w, a, b) if w then return 3a × b else return 5a × 2b end if end function
在 setup 阶段这些系数类似于 0 或者 1 将被“硬编码”进去,之后就不能再修改了。现在我们将运算形式修改为:
或者更正式地,对于变量
其中下标
注意:在不同的运算和操作数/输出中,同一个变量的常量系数可以是不同的。
7.7.2. 没有成本的做加法
看一下这个新结构,很显然在多项式的表示中,每一个不同
Figure 21: 加法
我们可以利用这一个结构,加任何数量必要的变量 到每一个运算的操作符/输出中。例如在第一个运算中,我们可以首先做加法
Figure 22: 先加后乘
因而也可以将这些变量中任意个,对它们先乘以任意的系数再一并加入到一起作为单个操作数中,以此来根据相应程序的需要构造一个操作数值。这个特性实际上就允许将运算结构改为:
或者使用更正式一些的表示,使用变量
7.7.3. 加法,减法和除法
到目前为止,我们一直专注于乘法操作。但是为了能够执行通用计算,真实环境下的程序也需要加法,减法和除法。
7.7.3.1. 加法
在前面的章节中,我们已经确定了可以在单个操作数的内容中将变量加起来,然后和另一个操作数相乘,即
7.7.3.2. 减法
减法与加法几乎一致,唯一的不同就是负系数,
7.7.3.3. 除法
如果我们检查除法运算
7.7.3.4. 总结
运算的结构也称为“约束” ,因为多项式结构代表的运算,并非是为了计算出结果,而是在 prover 已经知晓的变量赋值的情况下,检验这个运算的过程是否正确。
算术电路的目的是为了实现“计算的验证”,而非“计算的过程”。
7.8. 计算示例
有了前面的基础后,我们可以将节 7.1 的程序转换成一组运算,然后再转换成多项式的形式。我们先来考虑一下算法的数学形式(用变量
这里有三个乘法,但是由于运算结构只支持一个乘法操作,所以这里至少就要做三次运算。但是,我们可以将它简化为:
进行简化后,现在只需要两个乘法。这种运算的完整形式就是:
我们还要再增加一条约束使
上面限制了
现在一共有 5 个变量,其中 2 个左操作符(
我们把多项式里变量的系数当作是另一个多项式的函数值,为了方便,假设这些系数是取点
也就是上面约束可写为:
如果这个多项式在计算的操作数或者输出中没有被用到的话系数就置为 0:
显然进行这样的赋值可以满足前面的要求。
从而
接下来,根据前面的等式我们利用多项式插值法来找到每个变量多项式:
绘制出来如图 23 所示。
Figure 23: 示意图
我们准备通过多项式去证明计算,首先,选择函数的输入值,例如:
操作符多项式为:
然后,我们把所有计算结果中的值赋值到“变量多项式”中,然后相加得到操作数或者输出多项式的形式:
如图 24 所示。
Figure 24: 前一个示意图的实例化
把它们相加成对应运算中的操作数和输出值,如图 25 所示。
Figure 25:
我们需要去证明
Figure 26:
这里很明显多项式
这就是一组能够正确计算的变量值,如何在多项式层面上证明出来的。下面 prover 还要再继续处理协议的密码学部分。
7.9. 可验证的计算协议
我们基于节 6.4 中介绍的协议做了很多重要的修改使它变得更通用,我们再来看一下它现在的定义。假定函数
- Setup
- 为左操作数
构造变量多项式,然后对于所有 的运算都算出其对应的系数,即 ,对右操作数和输出也做同样的事情。 - 挑选随机值
- 计算
及其结果 - 计算 proving key:
- 计算 verification key:
- 为左操作数
- Proving
- 计算函数
以此计算相应的变量 - 计算
,其中 , 与之相似。 - 给变量赋值并对操作数多项式求和:
- 对
变换后的多项式赋值: - 使用
的幂加密值: 计算加密值 - 生成证明:
,简记为
- 计算函数
- Verification
- 检查变量多项式约束:
- 验证计算有效性:
- 检查变量多项式约束:
对变量多项式
虽然协议足够健壮,可以进行常规的计算验证,但这里依然还有两个安全考虑需要去解决。
7.9.1. 操作数和输出的不可交换性(单独进行 KEA 检查)
因为在“变量多项式约束检查”中的所有操作数上我们使用了同一个
- 使用其它的操作数中的变量多项式,如
- 完全交换操作数多项式,如换成
- 复用相同的操作数多项式,即
可交换性就是指 prover 可以修改计算过程,并有效证明一些其它无关的计算结果。 防止这种行为的一个很显然的方式就是在不同的操作数上使用不同的
- Setup
- ......
- 选择随机数
代替 - 计算 proving key:
- 计算 verification key:
- ......
- Proving
- ......
- 对
变换后的多项式赋值:
- ......
- Verification
- ......
- 检查变量多项式约束:
- ......
7.9.2. 跨操作数的变量一致性
对于任意的变量
因为每一个操作数运算符的有效性是分开校验的,并不强制要求我们在对应的变量多项式中使用相同的变量值。这就意味着在左操作数中变量
我们可以通过熟悉的限制多项式的方法(也就是限制变量多项式的方法)在操作数之间强制变量值相等。如果我们能够在所有的操作数之间创造一个作为“变换的校验和”的变量多项式,那么就可以限制 prover 使其只能够赋予相同的值。verifier 可以将这些每个变量的多项式加起来,即:
然后加密
则下面等式会成立:
尽管这个一致性校验很有用,但还是存在一定的概率
假如,一个以
对于任意的
上式是“一定成立的”,也就是说对于任意的
缓解这种情况的一种方法是对每个操作数都使用不同的
- Setup
- ... 随机数
- 对变量一致性多项式进行计算,加密并添加到 proving key 中:
- 把 3 个随机数加密后添加到 verification key 中:
- ... 随机数
- Proving
- ... 将变量值赋给变量一致性多项式:
- 增加分配的多项式到加密空间中:
- 再在证明中加入:
,简记为
- ... 将变量值赋给变量一致性多项式:
- Verification
- ... 校验提供的操作数多项式和“校验和”多项式之间的一致性:
,这相当于:
- ... 校验提供的操作数多项式和“校验和”多项式之间的一致性:
这个构造中同一个变量值就无法乱用了,因为不同的
不过,这个协议存在与节 7.6.1.1 类似的缺陷,由于
7.9.3. 变量非延展性(Non-Malleability)和变量一致性多项式
7.9.3.1. 变量多项式的延展性
举一个和节 7.6.1.1 有关的例子,看一下下面的两个运算:
预期的结果
但是因为 prover 已经拿到了
- Proving
- 用分配不成比例的变量
来建立左操作数多项式: - 按照常规的方式构造右操作数多项式和输出多项式:
- 计算除数:
- 计算加密值:
,并按照常规方式计算 - 计算
变换后的加密值: ,并按照常规方式计算 - 计算变量一致性多项式:
- 设置证明:
,简记为
- 用分配不成比例的变量
- Verification
- 变量多项式约束检查:
,和以前一样的方式检查 - 变量值一致性检查:
- 验证计算有效性:
- 变量多项式约束检查:
7.9.3.2. 变量一致性多项式的延展性
除了上面的问题外,
可以用变量多项式表示:
尽管我们期待的输出是
- Proving
- ... 用
设置左操作数多项式: - 用
设置右操作数多项式: - 用
设置输出多项式: - ... 计算加密值:
- 计算变量一致性多项式:
- ... 用
- Verification
- ... 变量值的一致性检查,会满足:
- ... 变量值的一致性检查,会满足:
注意:多项式
这种行为会危害到协议的可靠性。很显然,加密的
7.9.3.3. 非延展性
解决延展性问题的一个方法就是,在 setup 阶段将 verification key 中加密的
更新后的协议为:
- Setup
- ... 随机数
- ... 设置 verification key:
- ... 随机数
- Proving ...
- Verification
- ... 变量值一致性检查应满足:
- ... 变量值一致性检查应满足:
这里很重要的一点是我们排除了变量多项式为 0-阶的例子(即
我们同样也可以通过类似
7.9.4. 变量值一致性检查的优化
现在变量值一致性检查是有效的,但是这里在 verification key 中增加了 4 个昂贵的配对操作和 4 个新的项。文献 PGHR13 中的 Pinocchio 协议用了一个很聪明的方法优化,通过选择不同的生成元
- Setup
- ... 选择随机数
,设置 - 设置生成元
- 设置 proving key:
- 设置 verification key:
- ... 选择随机数
- Proving
- ... 设置变量值
- ... 设置变量值
- Verification
- 变量多项式约束检查:
,对 的检查也类似 - 变量值一致性检查:
- 验证计算有效性:
- 变量多项式约束检查:
生成元的这种随机化进一步增加了安全性,使得如节 7.6.1.1 中描述的可变多项式延展性无效。因为对于故意的修改,它必须要么是
这个优化使得 verification key 减少了两个项,并且去除了 verification 步骤中的两个配对运算。
注意:Jens Groth 在 paper Gro16 中有更进一步的改进。
7.10. 约束(R1CS)
我们的分析主要集中在“运算”的概念上。但是,协议实际上不是去做“计算”,而是“检验”输出值是否是操作数正确运算得到的结果。所以我们称之为“约束”,即 一个 verifier 约束 prover 去为预定义的“程序”提供有效值,而无论这个“程序”是什么。 多个约束组成的系统被称为“约束系统”,在我们的例子中这是一个一阶约束系统,或被称为(Rank 1 Constraint System, R1CS)。
我们也可以使用约束来确保其它的关系。例如,如果我们想要确认变量 a 的值只能为 0 或 1(即二进制数),我们可以用一个简单的约束去做这件事:
我们也可以去约束
一个更复杂的例子是确保数字
因而如果
并且为了确保
更复杂的约束也可以用这种方式表示,以此来确保使用的值满足规则。
看一个通用的约束形式:
需要注意的一点是上面的约束结构不能表示前面例子中的第 1 约束(也就是
7.11. 公共输入和 1
如果不能根据 verifier 的输入对其进行检查,那么证明的可用性将受到限制。比如说,虽然知道 prover 将两个值相乘但是并不知道这些值或计算结果。尽管有可能在 proving key 中通过“硬编码”来进行验证一些特定的值(如,乘法运算的结果必须为 12),但这就需要针对每一个想要的“verifier 的输入”生成单独的密钥对。
因而,如果可以由 verifier 而不是 prover 为计算指定一些值(输入或者输出),包括
首先,我们看一下要证明的值
这样如果我们能够在提供给 prover 的变量多项式中排除必要的一项,verifier 就可以在这一项变量多项式上设置他自己的值,并且使得检查依然能够通过。
因为 verifier 已经可以通过加入
协议更新为:
- Setup
- ... 将
个可变多项式全部分为两组:
- verifier 的
项: ,对 和 也做同样的计算。这里指数为 0 项为 而保留。 - prover 的
项: ,对 和 也做同样的计算。
- verifier 的
- 设置 proving key:
- 添加到 verification key:
- ... 将
- Proving
- ... 计算
, 其中 , 也类似。 - 设置证明:
- ... 计算
- Verification
- 为 verifier 的变量多项式赋值,并加 1:
,对 做同样的计算 - 变量多项式约束检查:
,对 做同样的检查 - 变量值一致性检查:
- 有效计算检查:
- 为 verifier 的变量多项式赋值,并加 1:
这实际上是 把一些变量从 prover 手中拿到 verifier 的手中,并同时保持等式相等。
7.12. 零知识计算
自从引入通用计算协议(节 7.4),我们一直放弃了零知识的性质,这是为了让协议的改进变得更简单。至此,我们已经构建了可验证的计算协议。
以前我们使用随机数
通过计算我们证明了:
尽管我们可以通过用相同
但是问题是使用相同的
- 其他人可以很容易得辨认出两个不同的多项式值是否相同,如:
,以此来获取一些知识 和 的不同值之间潜在的微小关系可能会通过暴力破解来区分开来,例如如果 ,就可以对 取值反复校验 ,只需要执行 5 步就可以揭示出两者 5 倍区别的关系。同样的暴力破解也可以用在破解加密值的加法运算上,如:- 证明元素之间的其它关系也可能会被发现,例如,如果
,那么也就表示 。
从而,我们需要对每一个多项式的值“乘”上不同的随机数,例如:
为了解决等式右边不相等的问题,我们不必改变协议,只要修改证明的值
设置
但是如前文所述,这个妨碍了零知识的性质,更重要的是这个结构也不再适合 verifier 的输入多项式,因为它们必须是某些
我们可以尝试把随机数“加”到变量上,即:
从而:
但是随机数是不可除尽的。尽管我们可以用
于是,我们应该用加法(即
从而:
由于分子中的每一项都包含某个
从而:
这样就可以在加密的空间中进行“有效计算检查”了:
于是既隐藏了加密值,又使得等式可以通过有效计算检查。
这个结构就是统计学上的零知识,因为增加了随机数
为了使得“变量多项式限制”和“变量值一致性”检查与零知识的修改一致,就有必要去增加以下的参数到 proving key 中:
7.13. zk-SNARK(Pinocchio)协议总结
在这一步步的改进之后,我们得到了最终版本的 zk-SNARK (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge),这就是 Pinocchio 协议(参见 PGHR13),零知识部分是可选的,并用不同的颜色标注出来了。
zk-SNARK 协议就是:
- Setup
- 选择生成元
和加密配对 - 对于函数
,假设它有 个变量,其中 个是输入或输出变量。把它转换为阶数为 ( 就是 operation 的个数)的多项式形式(即 QAP): - 选择随机数
- 设置
和操作数生成元 - 设置 proving key:
- 设置 verification key:
- 选择生成元
- Proving
- 代入输入值
,执行 计算获取所有的中间变量值 - 把所有未加密的变量多项式赋值给
,并对 和 做同样的计算 - 选择随机数
- 计算
- 将 prover 的变量值赋值给加密的可变多项式,并进行零知识的
转换 ,再用同样的方式计算 - 为上面的
变换赋值 ,再用同样的方式计算 - 为变量值一致性多项式赋值
- 设置证明
- 代入输入值
- Verification
- 把提供的证明简记为
- 将输入/输出值赋值给 verifier 的加密多项式并加 1:
,并对 做同样的计算 - 可变多项式约束检查:
,并对 做同样的检查 - 变量值一致性检查:
- 有效计算的检查:
- 把提供的证明简记为
上面三个过程可以分别用图 27,28,29(摘自:https://medium.com/coinmonks/hands-on-your-first-zk-application-70fe3a0c0d82 )表示。
Figure 27: 第一步:Setup 过程,r 是一些随机数
Figure 28: 第二步:Prover 生成 Proof 的过程,s 是 private input(也称 witness),x 是公开的输入
Figure 29: 第三步:Verifier 进行 Verification 的过程
8. 结论
我们最终完成了一个允许证明计算的有效协议:
- 简明(Succinctly)—— 独立于计算量,证明是恒定的,小尺寸的;
- 非交互性(Non-interactive)—— 证明只要一经计算就可以在不直接与 prover 交互的前提下使任意数量的 verifier 确信;
- 可论证的知识(with Argument of Knowledge)—— 对于陈述是正确的这点有不可忽略的概率,即无法构造假证据;并且 prover 知道正确陈述的对应值(即:证据);
- 陈述有不可忽略的概率是正确的(这里指 Soundness,可靠性),即,构造假证据是不可行的;
- 零知识(zero-knowledge)—— 很“难”从证明中提取任何知识,即,它与随机数无法区分。
Pinocchio 的可靠性是 Computational Soundness。所谓的 Computational Soundness 暗含了这样的事实:如果 Prover 计算能力足够强大的话,可以破坏可靠性。
基于多项式的特殊性质,模运算,同态加密,椭圆曲线密码学,加密配对和发明者的聪明才智才使得这个协议得以实现。
这个协议证明了一个特殊有限执行机制的计算,即在一次运算中可以将几乎任意数量的变量加在一起但是只能执行一次乘法,因而就有机会优化程序以有效地利用这种特性的同时也使用这个结构最大限度地减少计算次数。
为了验证一个证明, verifier 并不需要知道所有的秘密数据,这一点很关键,这就使得任何人都可以以非交互式方式发布和使用正确构造的 verification key。这一点与只能让一个参与者确信证明的“指定 verifier”方案相反,因而它的信任是不可转移的。在 zk-SNARK 中,如果不可信或由单方生成密钥对,则可以实现这个属性。
零知识证明构造领域正在不断发展,包括引入了优化(Ben+13, Gro16, GM17),改进可更新的 proving key 和 verification key(Gro+18),以及新的构造方法(Bulletproofs Bün+17, zk-STARK Ben+18, Sonic Mal+19)。
8.1. Trusted Setup
zk-SNARK 最大的缺点在于它需要可信初始化(Trusted Setup)。可信初始化会生成参考字符串(Reference String),如果 Reference String 被泄露,那么任何人都可以生成虚假的证明。此外,Reference String 还只能被用于指定的程序,对于其他的程序,需要另外的可信初始化过程。
有两种方案来缓解上面的问题:
- Transparent Setup:可信初始化生成公共参考字符串(Common Reference String)。而 CRS 是公开的,不需要保密。不过这种方案其证明比较大,一般是 kB 量级。如 SuperSonic 和 Halo 等是采取了这种方案。
- Universal Setup:可信初始化生成了结构化参考字符串(Structure Reference String),尽管它也是需要保密的,但它有个好处是:一次生成后,所有程序都可以使用,这样不用为每个程序都进行一次 Setup 了。如 Plonk 等采取了这种方案。
参考:https://en.wikipedia.org/wiki/Zero-knowledge_proof#Zero-Knowledge_Proof_protocols
9. 参考
- Pinocchio 原始论文:Pinocchio: Nearly Practical Verifiable Computation
- Why and How zk-SNARK Works ,本文是它的中文改编(近似于翻译,但有少量取舍)
- 网上已有的 Why and How zk-SNARK Works 的中文翻译:https://learnblockchain.cn/article/287 (笔者大量参阅了该文,不过该文中的公式存在很多拼写错误,请对照原始英文论文阅读)