ZK-SNARK 是一种零知识证明协议,在该协议中,人们可以证明他们拥有某些信息,而无需透露这些信息,也无需证明者(Prover)和验证者(Verifier)之间进行任何交互来证明和验证信息。
术语“ZK-SNARK”是一个首字母缩写词,代表“Zero-Knowledge Succinct Non-Interactive Argument of Knowledge”。名称的每个部分都指代 ZK-SNARK 的一个特征:
Zero-Knowledge:证明者可以向验证者证明他们拥有一条信息,而无需提供信息本身。
Succinct:证明(Proof)可以在几毫秒内被验证,而且证明的长度一般只有几百到几千字节。
Non-Interactive:证明是由从证明者到验证者的单个消息组成。
Argument:论证是用于这些证明的术语。
Knowledge:知识是指证明者所拥有的信息。
零知识证明(Zero Knowledge Proof)最初是在 1985 年由 Shafi Goldwasser、Silvio Micali 和 Charles Rackoff 撰写的一篇论文中引入的。 零知识证明是一种方法,它允许一方仅声明他们拥有一条信息,而不需要透露信息本身或任何其他信息。
早期的零知识协议要求证明者和验证者来回发送消息。 Nir Bitansky、Ran Canetti、Alessandro Chiesa 和 Eran Tromer 在 2012 年的一篇论文中创造了术语“ZK-SNARK”来描述一种新的零知识协议。 与以前的方法不同,它不需要证明者和验证者来回发送消息。
假设我知道某个消息 m 可以使得“SHA256(m) = 0”。我想向你证明我知道这样一个 m。一个简单证明方法就是我将消息 m 发送给你,然后你可以验证 m 的 SHA256 是否等于 0。如果消息 m 是千兆字节长,首先消息的传送消耗带宽和内存,其次验证者需要散列整个消息以验证它是否等于零。如果我不想透露 m 的原始值,上诉方法也不能解决。
我们的目标是建立一个非常短的证明,它只有几百到几千字节,而且验证也会非常快,验证它只需几毫秒验证并且验证者无需知道 m 就可以确信我知道这样的消息 m。这就是 ZK-SNARK 所要解决的问题。
在深入解释 ZK-SNARK 之前,先来了解一个零知识证明(Zero Knowledge Proof)的经典游戏,Waldo 在哪里?
给你一张有很多人的图片,如上图,你的工作是找到 Waldo。可以注意到 Waldo 就在图片的中心。想象一下,你想向某些人证明你知道 Waldo 在哪里,而不向他们透露 Waldo 在哪里,可以使用零知识向他们证明。
你走进一个什么都没有的房间,验证者会把上面的图片交给你并且给你一把剪刀。你在图片里面找到 Waldo,然后你会从中剪下 Waldo 的图片,然后破坏那张原始图片,例如撕掉它,然后把仅有 Waldo 的图片交给验证者。
为什么这是零知识证明?验证者已经知道 Waldo 的样子,因此你给它的 Waldo 图片并没有教给验证者任何它不知道的东西,这个想法正是零知识证明定义背后的想法,因为证明除了验证者已经知道的之外,没有向验证者透露任何其他信息。
假设Alice 知道计算散列值 H 的原始值 s,并且他希望向 Bob 证明他知道 s 。通常 Alice 会通过将 s 发送给 Bob 来证明这一点,之后 Bob 将计算 s 的散列值并检查它是否等于 H。
但是,假设 Alice 不想向 Bob 透露值 s,而只是想证明她知道该值。她可以为此使用 ZK-SNARK。
我们可以使用以下程序来描述 Alice 的场景:
function C(x, w) {
return ( sha256(w) == x );
}
这是一个 Arithmetic Circuits 抽象出的函数,记为 C,接受两个输入参数:C(x, w)。参数 x 是公开的声明 (statement),证明者和验证者都知悉的值,w(witness)是只有证明者知道的保密值。程序的输出是布尔值,即 true 或 false。然后给目标函数 一个特定的输入 x,使得 C(x,w) == true,证明证明者(prover)知道保密的输入 w。
换句话说:程序接受一个公开的参数散列值 x 和一个保密值 w,如果 w 的 SHA256 散列值等于 x,则返回 true。
使用函数 C(x,w) 转换 Alice 的问题,我们看到 Alice 需要证明她拥有 s 使得 C(H, s) == true,而不必揭示 s。这就是 ZK-SNARK 解决的一般问题。
ZK-SNARK 由三个算法 G、P、V 组成,定义如下:
G 采用保密随机数 lambda 和程序 C,生成两个公开可用的密钥,一个证明密钥(proving key) pk 和一个验证密钥(verification key) vk。这些密钥是公共参数,只需为给定程序 C 生成一次。
(pk, vk) = G(C, lambda)
我们把生成 pk,vk 的算法 G 称为预处理程序,此设置过程接受 C 并产生一些公共参数,我们将 pk 称为证明者的公共参数,将 vk 作为验证者的公共参数。
证明者 P(Prover) 将证明密钥 pk、公共参数 x 和保密值 w 作为输入。该算法生成证明 prf(proof)
prf = P(pk, x, w)
验证者计算 V(vk, x, prf),如果 proof 正确则返回 true,否则返回 false。因此,如果证明者知道正确的 w 则满足 C(x,w) == true,则此函数返回 true。
bool = V(vk, x, prf)
该算法之所以被称为非交互式(Non-Interactive)的系统是因为证明者仅需一次性将证明发送给验证者,过程中没有任何交互式对话,验证者收到证明以后就可以马上确定证明者的证明是否为真。
请注意此处生成算法中使用的保密参数 lambda。这个参数有时会让在实际应用中使用 ZK-SNARK 变得很棘手。原因是任何知道这个参数的人都可以生成假证明。具体来说,给定任何程序 C 和公共输入 x,知道 lambda 的人可以生成假的 proof 即 fake_prf,使得 V(vk, x, fake_prf) 在不知道 w 的情况下也返回 true。
因此,实际运行生成算法需要一个非常安全的过程,以确保没有人知道 lambda 和它保存的地方。
Alice 和 Bob 在实践中如何使用 ZK-SNARK 来让 Alice 证明她知道上面示例中的保密值 w?
首先,如上所述,我们将使用由以下函数定义的 C:
function C(x, w) { return ( sha256(w) == x ); }
第一步是让 Bob 运行生成算法 G,以创建证明密钥 pk 和验证密钥 vk。首先,随机生成 lambda 并将其用作输入:
(pk, vk) = G(C, lambda)
小心处理参数 lambda,因为如果 Alice 知道 lambda 的值,她将能够创建假证明。
Bob 将与 Alice 共享 pk 和 vk。Alice 现在将扮演证明者的角色。她需要证明她知道计算散列值 H 的原值 s。她使用输入 pk、H 和 s 运行证明算法 P 以生成证明 prf:
prf = P(pk, H, s)
接下来,Alice 将证明 prf 提供给 Bob,Bob 运行验证函数 V(vk, H, prf),在这种情况下,由于 Alice 正确地知道保密的值 s,它将返回 true。 Bob 可以确信 Alice 知道这个保密值 s,但 Alice 不需要向 Bob 透露这个这个保密值 s。
在一个交易数据可以被任何人查看的公共区块链上,任何人都可以验证交易是否遵循协议规则,但是可以使用 ZK-SNARK 隐藏交易的具体内容。
某些场景希望在不公开内部数据的前提下满足合规性检查,例如交易所可能想要证明他们的偿付能力,他们有足够的资金来履行对客户的义务,但他们希望以零知识的方式进行,以便他们可以发布非常简短的证明,然后任何人都可以验证证明是有效的,证明交易所是有偿付能力的,而且这样做不会透露有关其内部运营的任何信息,因此不透露他们拥有多少资产、他们拥有多少客户等等。
具有有效性证明的汇总系统,上千或上万笔交易将被分批处理,所有交易都在链下计算,然后将它们汇总为单个事务,接着产生一个简短的证明,证明所有这些交易都是有效的,并且只有这个简短的证明会被发布到区块链上,链上的验证者只会验证该证明是否正确,而实际上不必一个接一个地验证所有交易,这大大减少了链上的工作量。