区块链的发展可谓是日新月异,分布式账本,哈希函数,merkle tree,公钥算法,p2p网络,共识机制,智能合约等等很高大上的名词相信大家一定都不会很陌生。区块链像一个有机体,融合了各种不同的理论技术。零知识证明是构建信任的重要技术,也是区块链这个有机体中不可缺少的一环。
大家一定对数独游戏不陌生。数独游戏就是有一个9×9的盘面。我们要根据盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(33)内的数字均含1-9,不能重复。假设现在有一个非常难的数独,我废了千辛万苦的力气终于得到了这个数独的解。但是现在我告诉你,我会用零知识证明的方法给你证明我会这题的解,也就是说我不会把解透露给你,却能让你信服我确实有这题的解,仔细想想这是不是一件非常神奇的事情。
首先我拿出81(9x9)张空白的卡片放在桌上,在每张纸上写上1-9中的一个数字。然后我把数独题目给出的已知数字朝上放置,把我自己已经得到的解朝下放置,像下面这样:
然后我对你说,你挑一种检验方式吧,你可以按照每一行,每一列或者每一个小九宫格检验到底我的数字是不是重复的。
那具体的验证方法是什么样子的呢?很简单。我准备了9个袋子,然后按照你指定的验证方法,把每一行,每一列或者每一个小九宫的卡片分别收集到9个袋子中去。
这时候我们打开这些布袋,给你进行验证。如果我确实知道这个数独的解并且正确放置了每一张朝下的卡片的话,此时每个布袋里应该都有9张没有重复数字的,分别是数字1-9的卡片。
这种做法真的能够让人信服吗?你可能会说如果我挑了去检验每一行是不是符合要求,那我就没办法检验每一列或者每一个九宫格是不是符合要求了,你完全可以给我一个只满足行,列或者九宫格要求的错误的数独答案。
但是你不要忘记了,我事先是不会知道你是按照哪种方式去验证的,如果我真的没有数独的解的话,你至少可以以三分之一的概率抓到我在骗人。而且如果进行多次这样的重复验证,我能够瞒天过海的概率也会越来越小,假设验证次数为n,则我欺骗你的概率是3分之2的n次方,这样就可以说明我有很大的几率知道这个问题的解。
从这个小故事可以看出零知识证明的本质就是在不揭晓我所知道或拥有的某样东西的前提下,向别人证明我有很大几率(这点很重要,零知识证明说到底是一个概率上的证明)确实知道或拥有这个东西。
零知识证明(Zero-Knowledge Proof),是由S.Goldwasser、S.Micali及C.Rackoff在1985提出的。证明者能够在不向验证者提供任何有用的信息的情况下,使验证者相信某个论断是正确的。零知识证明实质上是一种涉及两方或更多方的协议,即两方或更多方完成一项任务列步骤。迄今为止,零知识证明已经是密码学的重要构建,数据的隐私保护,计算压缩与区块链扩容,端到端的通讯加密,身份认证,去中心化存储,信用存贮…….都可以看到它的身影。
其实上述的小故事就是一个简易的交互式零知识证明系统。交互式零知识证明需要验证方(你)在证明方(我)放好答案后,不断的发送随机试验。
由此可见一套完善的零知识证明体系需要以下的条件:
(1)完备性。如果证明方和验证方都是诚实的,并遵循证明过程的每一步,进行正确的计算,那么这个证明一定是成功的,验证方一定能够接受证明方。(如果我知道这个数独的解并且我和你都按照小故事里约定好的流程并且没有人作弊的话,你一定可以验证每个袋子分别装有9个不同的数字)
(2)合理性。没有人能够假冒证明方,使这个证明成功。(不知道这个数独解的人一定没办法冒充我和你进行验证并且让你相信他能知道数独的解)
(3)零知识性。证明过程执行完之后,验证方只获得了“证明方拥有这个知识”这条信息,而没有获得关于这个知识本身的任何一点信息。(这里可能需要涉及模拟器的相关概念,简单的说就是你没有办法从从知道数独的解的我这里得到任何关于数独的解的知识)
在1985年以后的很长一段时间内,零知识证明协议由于没有较好的运行效率和通用性,大部分只停留在理论。这些理论的零知识证明协议各自有不同的特点。有的协议是专职协议,只能证明某些特定的事情,例如著名的Schnorr 协议、三色图协议,有些零知识证明协议是全能的,只要你能用代码定义的问题,它都能证明(只是理论可行,不意味着有运行效率)。有些协议是交互式的,需要证明者和验证者来回发很多轮消息,有些是非交互式的,证明者只需要根据协议向验证者发一次消息。有的协议证明大小与问题规模相关,问题越复杂,证明越长。而有些协议下,无论问题多复杂,证明大小都一样。而一个全能的,非交互的,常数大小的零知识证明协议,是密码学研究者们多年奋斗的目标,在这个目标下,zk-SNARK横空出世。zk-SNARK(zero-knowledge succint non-interactive arguments of knowledge)里面的每个单词都有特定的含义:
Zero knowledge:零知识证明。
succinct:简明的,证据信息较短,方便验证。
Non-interactivity:非交互的,证明者只要提供一个字符串,可放在链上公开验证。
Arguments:证明过程是计算完好(computationally soundness)的,证明者无法在合理的时间内造出伪证(破解)。
of knowledge:对于一个证明者来说,在不知晓特定证明 (witness) 的前提下,构建一个有效的零知识证据是不可能的。
自此以后,Libra、Sonic、SuperSonic、PLONK、SLONK、Halo、Marlin、Fractal、Spartan、Succinct 、Aurora ,OpenZKP、Hodor GenSTARK、RedShift、AirAssembly……各式各样的零知识证明协议接连问世。他们都有着各自的优缺点,其中的很多协议被运用在了区块链以及加密货币中。
总之,零知识证明学习曲线陡峭,它涉及密码学,抽象代数,线性代数,数论等学科的综合应用,它引入的概念、符号很多会让人眼花缭乱。下一篇文章我们会先简单介绍一下Schnorr协议以及从完备性,合理性和零知识性去进行分析。希望各位保持耐心,由浅入深,循序渐进,揭开零知识证明的神秘面纱。
公众号“星想法”:--https://mp.weixin.qq.com/s/eU8mp81VhpV-g1x89-uZYA
公众号“安比实验室”:--https://github.com/sec-bit/learning-zkp/blob/master/zkp-intro/1/zkp-back.md
小故事的来源:--https://medium.com/qed-it/the-incredible-machine-4d1270d7363a
Schnorr协议是由德国数学家和密码学家Claus-Peter Schnorr在1991年提出,是一种基于离散对数难题的知识证明机制。Schnorr本质上是一种零知识的技术,即Prover声称知道一个密钥x的值,通过使用Schnorr加密技术,可以在不揭露x的值情况下向Verifier证明对x的知情权,即可用于证明你有一个私钥。
想要彻底了解这个协议,我们需要一些预备知识(如果你没有看懂上面的协议简介的话)。
1.公钥加密机制
在之前HIBE的文章里有涉及到,在公钥密码体中,每个通信方均拥有一个密钥对,即公钥(public key)和私钥(private key),在加密方案中分别用于加密和解密消息,与传统的非对称加密体制不同的是,公钥和私钥是不同的。
2.椭圆曲线算法
椭圆曲线并不是椭圆,而是形如以下形状的关于x轴对称的曲线:
因为涉及群,域等数学概念,我尽量描述得通俗易懂些(可能并不是特别准确,感兴趣的读者可以参考相关资料)。
假设现在随机生成一个秘密密数字a,我们可以把这个数字当作成私钥,然后把它映射到椭圆曲线群上的一个点 a*G,简写为 aG。这个点我们把它当做公钥。
于是我们有:
sk = a
PK = aG
椭圆曲线群和有限域之间存在着一种同态双射关系。什么叫双射呢?就是把一个集合X的每个元素都对应集合Y的一个元素,就像下面这样:
有限域,我们用 q这个符号表示,当q为素数的时候,我们管它叫素数域,它是指从 0, 1, 2, …, q-1这样一个整数集合。而在一条椭圆曲线上,我们通过一个基点,G,(确切的说是生成元)可以产生一个循环群,标记为 0G, G, 2G, …, (q-1)G,正好是数量为 q个曲线点的集合。
任意两个曲线点正好可以进行一种类似于加法的二元运算 ,P (假设为2G)+ Q (假设为3G)= R(就是5G),(如上图所示,找到两个相加的点的延长线与椭圆曲线的交点,就得到新的点R)并且满足交换律和结合律。于是我们就用 +这个符号来表示。我们在素数域的加法都可以在椭圆曲线上进行类比,但是椭圆曲线上好像有无穷多个点,我们需要在把椭圆曲线限制到一个有限域内,而我们之所以把这个群称为循环群,因为把群的最后一个元素 (q-1)G,再加上一个 G就回到群的第一个元素 0G。
下面是曲线
和的图像。可以发现,椭圆曲线变成了离散的点,且关于y=p/2对称。
给任意一个有限域上的整数 r,我们就可以在循环群中找到一个对应的点 rG,但是反过来给你rG让去找到r是一件很困难的,在密码学中被称为离散对数难题。
也就是说,如果任意给一个椭圆曲线循环群上的点 R,那么到底是有限域中的哪一个整数对应 R,这个计算是很难的,如果有限域足够大,比如说 256bit 这么大,我们姑且可以认为这个反向计算是不可能做到的。
Schnorr 协议充分利用了有限域和循环群之间单向映射,实现了最简单的零知识证明安全协议:证明者向验证者证明她拥有 PK 对应的私钥 sk。
我们让Alice充当证明者,Bob充当验证者,协议流程如下:
Alice:随机地选择一个标量,然后计算出(为椭圆的生成元),将发送给Bob;
Bob:回应一个随机标量;
Alice:通过计算,将标量回应给Bob;
Bob:将z转换为椭圆曲线上的点,即,然后验证
交互过程如下图:
首先我们来复习一下之前所说的零知识证明的三个特性:
(1)完备性。如果证明方和验证方都是诚实的,并遵循证明过程的每一步,进行正确的计算,那么这个证明一定是成功的,验证方一定能够接受证明方。
(2)合理性。没有人能够假冒证明方,使这个证明成功。
(3)零知识性。证明过程执行完之后,验证方只获得了“证明方拥有这个知识”这条信息,而没有获得关于这个知识本身的任何一点信息。
下面我们来一一进行比对:
1.完备性:在等式 等式边同时乘以,便得到,所以说,按照协议里的交互流程,一定能够通过验证者的验证,即验证了完备性。
2.合理性:如果Alice事先不知道私钥的话,那么他是否可以构造出 和 那么是否可以在最后一步验证成功呢?将 带入最后一个需要Bob等式 ,可得,我们在等式两边同时约去 ,可得 ,这其实就是Bob在椭圆曲线上同态地检查 。因为Alice不知道的值,所以就无法得知等式右边的值,自然也就无法找到对应的 和 了。(这里只是一个简单的阐述,更深层的需要了解抽取器的涵义)
3.零知识性:Bob 验证是在椭圆曲线群上完成。Bob知道 映射到曲线上的点,但是不知道本身;Bob 知道 映射到曲线群上的点 ,即,但是不知道 本身。Bob 可以校验 的计算过程是否正确,但是又没有暴露 和 的值(这里只是一个简单的阐述,更深层的需要了解模拟器的含义)。
交互式Schnorr 协议中,Bob 需要给出一个随机的挑战数 c,这里我们可以让 Alice 用下面这个式子来计算这个挑战数, ,因为哈希函数的单向性,虽然 c 是 Alice 计算的,但是 Alice 并没有能力实现通过挑选 c 来作弊。
这样,就把三步Schnorr协议合并为一步。Alice可直接发送(R,z),因为Bob拥有Alice的公钥PK,于是Bob可自行计算出c。然后验证。
这里运用到了Fiat-Shamir协议 ,它可以把整个协议压缩成一步交互,有兴趣的读者可以深入了解相关内容。
在这篇文章中,我们简单地介绍了一个专职零知识证明,schnorr协议。我们又离通用的零知识证明协议更近了一步。下一篇文章我们将会来一起探讨那个深奥的zk-snark,不过在这之前,我们还需要一些预备知识来进行铺垫。
参考:
公众号“安比实验室”:https://github.com/sec-bit/learning-zkp/blob/master/zkp-intro/2/zkp-simu.md
回顾一下一套完善的零知识证明体系需要以下的条件:
(1)完备性(Completeness)如果证明方和验证方都是诚实的,并遵循证明过程的每一步,进行正确的计算,那么这个证明一定是成功的,验证方一定能够接受证明方。
(2)合理性(Soundness) 没有人能够假冒证明方,使这个证明成功。
(3)零知识性(Zero-knowledge)证明过程执行完之后,验证方只获得了“证明方拥有这个知识”这条信息,而没有获得关于这个知识本身的任何一点信息。
接下来就是我们的重头戏zksanrk了。
P vs NP
克雷数学研究所官网找到了关于 P和NP问题的简单说明。
简单地翻译过来就是:
假设你正在为400名大学生组织住宿,但是空间有限只有100名学生能留在宿舍里。更复杂的是还给了你一份不相容学生的名单,并要求在你的最终选择中不要出现这份名单中的任何一对。
这是计算机科学家称之为NP问题的一个例子,因为很容易检查一个同事提出的一百个学生的给定选择是否令人满意,然而从头开始找到这100个人的任务似乎太难了以至于完全不切实际。
事实上从400名申请者中选择100名学生的方法总数比已知宇宙中的原子数量还要多!这类问题可以被快速检查,但是通过程序来解决的话则会花费时间太长以至不可接受,比如300年或者更多。
斯蒂芬·库克和列昂尼德·莱文在1971年独立地提出了P(即容易找到)和NP(即容易检查)问题。
P 问题(easy to find)
all problems solvable, deterministically, in polynomial time
多项式时间内可解决的问题(当然在多项式时间是可验证的)
NP 问题(easy to check)
non-deterministic Polynomial time
非确定性多项式时间可解决的问题
大家一定都对算法时间复杂度的大O表示法不陌生吧,简单的来说,时间复杂度并不是表示一个程序解决问题需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长得有多快。
例如上图所示,增长速度最快的左边两条线是非多项式级的,剩余的是多项式级别的。当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的时间太多,往往会超时,除非是数据规模非常小。
自然地,人们会想到一个问题:会不会所有的问题都可以找到复杂度为多项式级的算法呢?答案是否定的。有的问题甚至找不到正确的算法程序,例如大名鼎鼎的停机问题(The Halting Porblem,可以自行了解下)。
那么P问题与NP问题的关系呢?很显然,所有的P类问题都是NP问题,多项式内被求解的问题一定在多项式时间内被验证。那么所有NP类问题都是P类问题嘛,是否有P=NP这个问题已经困扰了科学家多年时间,不过大部分人还是认为两者不等。虽然无法被证明,但是正是NPC问题的存在,使人们相信P≠NP。NPC问题可以理解为NP问题中最“难”的那一类问题。一个问题A可以约化为问题B,通俗地说就是问题B比问题A难,解决了B就能解决A。举个简单的例子,比如你有了二元一次方程的通解,那你一定能得到解决一元一次方程的通解,因为一元一次方程是二元一次方程二次系数为零的特殊情况。所以说,一个问题约化成另一个问题,所对应的时间复杂度相等或者增加了。
通过不断的约化,能够找到复杂度更高,但应用范围更广的算法来代替复杂度虽低,但只能用于很小一类问题的算法。神奇的事情来了!所有NP问题都可以约化为NPC问题,即如果NPC中任何一个问题能够在多项式时间内找到最优解,则NP中的每个问题都能在多项式时间内找到最优解,即P=NP。遗憾的是,至今为止,对于NPC问题,目前没有多项式的有效算法,只能用指数级甚至阶乘级复杂度的算法。
理解了P问题,NP问题以及NPC问题以后,大家回想一下之前所说的数独游戏,本质上是一个NP问题,我们找不到一个多项式时间的算法可以算出数独,但是给我数独的解我立刻就可以判断这个数独的解对不对。同样的,schnorr协议中的离散对数难题也是一个NP问题,给任意一个有限域上的整数 r,生成元G,我们就可以在循环群中找到一个对应的点 rG,但是反过来给你rG和G让去找到r是一件很困难的事情,即相对应的,你很难找到schnorr协议里的私钥,但是能很轻松去验证如果有这个私钥到底是不是正确的。
所以来说,需要证明的问题,肯定是NP问题,如果是P问题,不存在问题解的”寻找“,也就不存在证明。zkSNARK问题处理的都是NP问题。
那么问题又来了,我们有如此繁多的NP问题以及最难的NPC问题,我们如何构造一个通用的协议呢?
答案就是我们把任何NP或者NPC问题转化为QAP的形式,而 QAP satisfy problem(二次算数程序可满足性问题)是一个NPC问题。简单解释QAP满足性问题即,给一系列的多项式以及一个目标多项式,是否能根据这一系列的多项式,求得它们的一个线性组合,刚好可以整除多项式。即如下描述:
这个问题如果给出了一个解 ,那么验证很简单,直接除 t(x) 即可验证是否满足,但是想求解就很难。
zkSNARK的思路就是,将原问题的解转化为一个QAP可满足性问题的解,然后公开QAP问题,这样的话拥有解的人用证明公开自己拥有一个解,其他人可以简单验证证明是否成立。
“V 神Vitalik Buterin,以太坊的创始人,是区块链界真正的KOL,是和中本聪同样伟大的存在,在他的博客里描述了如何一步一步构建QAP,接下来加上一些自己粗略的见解,带大家了解整个过程的全貌。
首先,我们要把整个验证过程用代码的形式写出来。比如说在数独游戏中,不允许泄露的信息(即我们自己的答案)称为私有输入(secret input,有时也称witness),而已经填好的数字我们叫公有输入(public input)。我们不妨设私有输入为 ,
共有输入为 ,我们接下来要做的就是用代码把约束写出来,也就是每一行每一列和每一个9宫格都是1-9。当然这个例子需要大量的约束条件和代码,我们不妨拿v神博客上简单一些的例子来进行说明。
问题从V神的例子开始,对于方程 ,显然其解是3,我们(证明者)要向别人(验证者)证明我们知道这个方程的解但是不能向对方透露这个解的知识。
首先,先把方程干的事情通过计算机代码写出来,就像下面这样:
def qeval(x):
y = x**3
return x + y + 5
然后,我们把代码拍平:
拍平后的代码一次只能做下面的一种事情,x=y(x可以是数字或变量)。x=y op z(其中op可以是+,-,*,/等运算)
sym_1 = x * x
y = sym_1 * x
sym_2 = y + x
~out = sym_2 + 5
在上面的过程中,我们引入了一些中间变量,但是整体跟我们的代码是等价的。
接下来,我们需要把拍平的代码写成一个叫作 R1CS(rank-1 constraint system)的东西。
R1CS 是一个由三向量组(a,b,c)组成的序列,R1CS 有个解向量 s, s必须满足运算s·a*s·b-s·c=0
s · a 表示向量的点积,向量的长度是系统里变量的个数,就像下面这样:
在本例中其解向量的结构即为
接着我们可以根据程序的第一个等式构造出:
a = [0, 1, 0, 0, 0, 0]
b = [0, 1, 0, 0, 0, 0]
c = [0, 0, 0, 1, 0, 0]
这实际上就是对程序第一行等式的另一种描述方式。
接下来是第二个等式:
a = [0, 0, 0, 1, 0, 0]
b = [0, 1, 0, 0, 0, 0]
c = [0, 0, 0, 0, 1, 0]
第三个等式:
a = [0, 1, 0, 0, 1, 0]
b = [1, 0, 0, 0, 0, 0]
c = [0, 0, 0, 0, 0, 1]
第四个等式:
a = [5, 0, 0, 0, 0, 1]
b = [1, 0, 0, 0, 0, 0]
c = [0, 0, 1, 0, 0, 0]
于是我们就得到了包含四个约束的 R1CS,本质上就是对私密输入,也就是我们的解向量 进行了约束。
把向量合在一起,我们就得到了完整的R1CS系统:
A
[0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0]
[0, 1, 0, 0, 1, 0]
[5, 0, 0, 0, 0, 1]
B
[0, 1, 0, 0, 0, 0]
[0, 1, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0]
C
[0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0]
我们可以看到矩阵 A,B,C,是一个4乘6的矩阵,我们把矩阵A的第一列拿出来用来构建多项式
选取4个点:
(1,0),(2,0),(3,0),(4,5)
然后求过这4个点的多项式,其中矩阵的值用来当成纵坐标,横坐标我们用1,2,3,4来赋值。
可以得到多项式是
构造多项式的过程可以使用拉格朗日插值法,或者FFT(快速傅里叶变换),总而言之,我们有四个点,于是可以得到含有四个系数的三次多项式。
然后我们如法炮制,可以得到剩下的多项式
A polynomials
[-5.0, 9.166, -5.0, 0.833]
[8.0, -11.333, 5.0, -0.666]
[0.0, 0.0, 0.0, 0.0]
[-6.0, 9.5, -4.0, 0.5]
[4.0, -7.0, 3.5, -0.5]
[-1.0, 1.833, -1.0, 0.166]
B polynomials
[3.0, -5.166, 2.5, -0.333]
[-2.0, 5.166, -2.5, 0.333]
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
C polynomials
[0.0, 0.0, 0.0, 0.0]
[0.0, 0.0, 0.0, 0.0]
[-1.0, 1.833, -1.0, 0.166]
[4.0, -4.333, 1.5, -0.166]
[-6.0, 9.5, -4.0, 0.5]
[4.0, -7.0, 3.5, -0.5]
这时候神奇的事情发生了!我们把得到的18个多项式按照下图的方式去排列,把 代入多项式得到的值
就是我们R1CS矩阵的第一列,
得到的值就R1CS矩阵的第二列,
得到的值就R1CS矩阵的第三列,根据R1CS的形式
,我们得到这一系列多项式的线性组合在 x = 1的时候值为 0,根据初中学的因式定理,也就是说这一系列多项式的线性组合可以提出因子 ( x -1 ),同理我们可以提出因子 ( x-2 )( x-3 )( x-4 )。
所以我们检验四个约束的方式变成检验在
是否成立,也就是检验这个线性组合的多项式有没有因子 ( x-1 )( x-2 )( x-3 )( x-4 )。
所以我们意识到了我们通过多项式方式所做的操作本质是和R1CS在做同样的事情,这二者是等价的。
这时候我们再看QAP满足问题的描述,是不是发现不知不觉中我们已经把问题转化完毕了,问题的描述如下:
其中, 对应的就是我们解向量 s 的每一个值,也就是我们的私密输入,
对应的是
对应的是我们的
,这个问题如果给出了一个解
,也就是说我们知道了解向量 s ,
也只有正确的解向量 s , 才能使以它为系数的线性组合的多项式整除目标多项式 。
至此为止,我们已经完成了构建QAP的全过程。
这篇文章我们简单阐述了计算复杂度中的 P 问题,NP 问题以及 NPC 问题与 QAP 的关系,以及我们如何把一个复杂的问题转换到 zkSNARK 能解决的形式 QAP 上,我们如何在这个基础上继续构造协议呢?我们后面继续探讨。
文章部分内容参考:
https://vitalik.ca/general/2016/12/10/qap.html
https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/