在 Circom 中,公共输入是 witness 中的一个信号,它将被透露给验证者。
例如,假设我们想要创建一个 ZK 证明,声明:“我们知道生成 0x492c…9254 的哈希的输入。”为了使这个声明有意义,值 0x492c…9254(目标哈希输出)必须是公开的。否则,我们在语义上声称 “我们哈希了一些东西”,这没有那么有用。
以下电路声明,“我将两个数字相乘得到第三个数字:”
template Main() {
signal input a;
signal input b;
signal input c;
a * b === c;
}
component main = Main();
下一个 circut 做了一个类似的声明,但是把结果改成了公开的:“我将两个数字相乘得到第三个数字,它的值是公开已知的:”
template Main() {
signal input a;
signal input b;
signal input c;
a * b === c;
}
component main {public [c]} = Main();
component main {public [c]}
语法明确地将它们公开。主组件是唯一可以定义哪些输入是公开的地方。[c]
是要公开的信号的列表。如果我们也想公开 a
,它可以包含更多的信号,例如 [a,c]
。上面的模板编译成一个秩 -1 约束系统 (R1CS),与以下内容相同,我们在主组件中引入 output
关键字:
template Main() {
signal input a;
signal input b;
signal output c;
a * b ==> c;
}
component main = Main();
在上面的两个模板中,c
是公开的,并且被约束为 a
和 b
的乘积。因此,底层的 R1CS 是相同的。但是,第二个版本更 “方便”,因为我们不必显式地提供 c
。在使用 component main {public [c]}
的第一个电路中,如果我们提供的 c
不遵守约束,则不会生成 witness。但是,在使用 c
作为输出的第二个电路中,witness 生成器会自动计算出 c
的正确值,从而消除了手动输入。
由于 c
完全由 a
和 b
决定,因此没有理由显式地为 c
提供一个值,因此应该首选 output
表示法。
请注意,输出是公开的。
在输入的情况下,如果我们想公开一些输入,那么这意味着我们有一个信号,其值不完全由其他信号值决定。在这种情况下,我们必须使用 public
修饰符方法。例如,如果我们声明 “我将 a
、b
和 c
相乘得到 d
,a
和 d
是公开的,但 b
和 c
是私有的”,我们将把该电路构造为:
template Main() {
signal input a; // 显式公开
signal input b;
signal input c;
signal output d; // 隐式公开
signal s <== a * b; // 中间信号
d <== c * s;
}
component main{public [a]} = Main();
以下是如何理解 output
信号:
output
是一个信号,它将被赋予来自其他输入的值,并且可能会被实例化该子组件的组件稍后使用。output
是 witness 中的一个公共信号,其值应完全由其他输入信号决定。声明一个输出信号而不给它赋值可能会产生漏洞,因为证明者可以给它赋任何他们想要的值。我们将在接下来的章节中展示这种漏洞的机制。尽管名称是 “输出”,但没有从主组件获取 “输出” 的机制 —— Circom 无法返回任何内容。没有办法让其他代码库读取 “输出” 的值。
它只生成一个 R1CS,帮助计算 R1CS 的 witness。然后,Snarkjs 使用 Circom 代码生成一个 ZK 证明,证明 witness 满足 R1CS。
Circom 没有被 “执行”,这就是为什么它不 “返回” 任何东西。你不是在 “运行” Circom,你只是在描述一个抽象电路,它被转换成两个部分:R1CS 和一个 witness 生成器,它们被分别使用。
主组件中的输出信号可以被认为是对验证者公开的中间信号。
Circom 按以下方式排列 witness 向量:
[constant, public signals, private signals]
让我们以 “我将隐藏值 a
、b
与公共值 c
相乘,得到公共值 d
” 为例:
// 断言 a*b === c*d
template Example() {
signal input a;
signal input b;
signal input c;
signal input d;
signal s;
s <== a * b;
d === s * c;
}
component main {public [c, d]} = Example();
请注意,我们可以通过将 d
设置为输出来节省一些代码,但我们这里不这样做,以使即将到来的演示更清楚。
要查看 witness 是如何构建的:
Example.circom
circom Example.circom --sym --r1cs --wasm
编译它input.json
:echo '{"a": "3", "b": "4", "c":"2", "d":"24"}' > input.json
cd example_js
node generate_witness.js example.wasm ../input.json witness.wtns
snarkjs wej witness.wtns && cat witness.json
我们应该得到以下结果。请注意,这与我们为 input.json
提供的值相匹配:
[
"1", // 常数
"2", // c ( 公共信号 )
"24", // d ( 公共信号 )
"3", // a
"4", // b
"12" // s
]
因此,我们可以看到 witness 布局总是:
c
, d
)a
, b
)s
)。component main {public [in1, in2]} = Main();
语法将输入设置为公开
原文链接:https://www.rareskills.io/post/circom-public-private-inputs 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
登链社区是一个 Web3 开发者社区,通过构建高质量技术内容平台和线下空间,助力开发者成为更好的 Web3 Builder。
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。