ZKP应用实例之哈希与其原像知识

本实例,将实现一个在区块链用例中非常典型的操作:证明给定哈希摘要的原像知识。 特别是,假设证明者Peggy)向验证者Victor)毫无疑问地证明她知道摘要的哈希原像 ,但没有透露原像是什么。

  1. 本实例以zokrates为ZKP工具(iden3亦可)。

1. 计算hash

我们将通过使用 ZoKrates 计算任意选择的原像的哈希值来开始本教程,在本例中,原像内容为数字 5

首先,我们创建一个名为 hashexample.zok 的新文件,内容如下:

1
2
3
4
5
6
import "hashes/sha256/512bitPacked" as sha256packed;

def main(private field a, private field b, private field c, private field d) -> field[2] {
field[2] h = sha256packed([a, b, c, d]);
return h;
}

第一行从 ZoKrates 标准库导入 sha256packed 函数。

sha256packed 是一个 SHA256 的实现。它是这样工作的:我们想将 512 位的输入传递给 SHA256。 但是,由于其背后使用的基础字段的原因,Zokrates中字段值只能容纳 254 位,无法容纳256位的输入。因此,我们使用四个字段元素,每个元素编码 128 位,来表示我们的输入。然后这四个元素在 ZoKrates 中连接起来并传递给 SHA256。 鉴于生成的散列长度为 256 位,我们将其一分为二并将每个值作为 128 位数字返回。

如上,代码实际上只是使用 sha256packed,返回计算出的哈希值。

1.1 首先,我们使用编译命令将程序编译成一个运算电路。

1
zokrates compile -i hashexample.zok

1.2 第二步,我们可以使用以下命令创建见证文件

1
zokrates compute-witness -a 0 0 0 5

使用标志 -a 我们将参数传递给程序。 回想一下,我们的目标是计算数字 5 的哈希值。因此我们将 a、b 和 c 设置为 0,将 d 设置为 5

此时,我们可以检查 witness 文件中的返回值:

1
2
3
grep '~out' witness
~out_0 263561599766550617289250058199814760685
~out_1 65303172752238645975888084098459749904

因此,通过将输出连接为 128 位数字,我们得到以下值作为我们选择的原像的哈希值:

1
2
3
4
5
6
7
8
9
10
11
// 计算5的sha256
import hashlib

preimage = bytes.fromhex('00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05')

bin(int(preimage.hex(), 16)) //binary representation of pre-image
//output is '0b101'

hashlib.sha256(preimage).hexdigest() //compute hash
//output is
//'c6481e22c5ff4164af680b8cfaa5e8ed3120eeff89c4f307c4a6faaae059ce10'

此处,愿像的hash值‘c6481e22c5ff4164af680b8cfaa5e8ed3120eeff89c4f307c4a6faaae059ce10’ 与 witness中out_0 out_1拼接后的hash内容是一致的。

2. 证明原像知识

现在,我们已经看到我们可以使用 ZoKrates 计算哈希值。

让我们回顾一下我们的目标:Peggy 想证明她知道 Victor 选择的摘要的原像,但不透露原像是什么。 现在让我们假设 Victor 选择摘要作为我们在上面的示例中找到的摘要。

为了让它发挥作用,双方必须遵循他们在协议中的角色:

2.1 Victor 必须指定他感兴趣的哈希

因此,我们必须调整由 ZoKrates 编译的 zkSNARK 电路,这样除了计算摘要外,它还根据 Victor 提供的感兴趣的摘要对其进行验证。 这导致 hashexample.zok 的以下更新:

1
2
3
4
5
6
7
8
import "hashes/sha256/512bitPacked" as sha256packed;

def main(private field a, private field b, private field c, private field d) {
field[2] h = sha256packed([a, b, c, d]);
assert(h[0] == 263561599766550617289250058199814760685);
assert(h[1] == 65303172752238645975888084098459749904);
return;
}

请注意,现在将 sha256packed 的结果与 Victor 定义的硬编码正确解决方案进行比较。 我们添加的行被视为断言:验证者不会接受不满足这些约束的证明。 显然,如果所有计算的位都相等,则此程序仅返回 1。

2.2 Victor 现在可以编译代码

1
2
3
4
5
zokrates compile -i hashexample.zok

zokrates setup

zokrates export-verifier

setup创建一个 verification.key 文件和一个 proving.key 文件。 Victor把证明钥匙给了Peggy

2.3 Peggy 提供正确的原像作为程序的参数

1
zokrates compute-witness -a 0 0 0 5

2.4 Peggy 可以运行命令来构建证明

1
zokrates generate-proof

由于输入在程序中被声明为私有,因此由于协议的零知识属性,它们不会出现在证明中。

ZoKrates 创建一个文件 proof.json,由构成 zkSNARKs 证明的三个椭圆曲线点组成。 Victor 部署的智能合约中的 verifyTx 函数接受这三个值以及一系列公共输入。公共输入数组包括:

  • main 函数的任何公共输入,声明时没有使用 private 关键字
  • ZoKrates 函数的返回值

在本示例中,所有输入都是私有的,并且只有一个返回值 1。

2.5 然后 Peggy 可以通过调用 verifyTx 提交她的证明

2.6 Victor 监控验证智能合约以获取 Peggy 交易的返回值

一旦Victor观察到来自 Peggy 的公共地址的具有真实返回值的交易,Victor就可以确定Peggy拥有他在智能合约中设置的哈希值的有效原像。

3.延伸

在此示例中只涉及两方。这种特殊情况使得处理 zkSNARKs 的信任假设变得容易:只有 Victor 对验证 Peggy 的声明感兴趣,因此他可以信任他对设置阶段的执行。

一般来说,多方可能有兴趣验证Peggy声明的正确性。例如,在基于零知识的加密货币 Zcash 中,每个节点都需要能够验证交易的正确性。为了将设置阶段推广到这些多方用例,需要执行一个通常称为“可信设置”或“仪式”的过程。

即可以实现 - https://willzhuang.github.io/2020/06/23/%E4%BD%93%E9%AA%8Czksync-2/

4.结论

这可能算做一个知识证明,但是题目中的应用实例,是什么呢?

参考链接 - circom - https://willzhuang.github.io/2021/05/06/circom%E8%AF%95%E7%94%A8/