本文介绍了Circom中定义Rank 1约束系统(R1CS)的基本语法,包括模板参数的使用、循环和变量的声明与应用、以及如何在满足特定条件时生成约束。此外,还强调了在Circom中约束必须是静态的,不能依赖于信号动态改变,但变量可以作为常量参与R1CS运算,并解释了if
语句在Circom中的使用限制,着重介绍了 variables 的使用方法,以及 signals 的使用限制。
本章介绍基本的语法,你会在大多数 Circom 程序中见到这些语法。使用 Circom,我们可以使用代码定义 Rank 1 Constraint System (R1CS),而不是显式地定义每个约束。我们将在本章中探讨这些工具。
之前,我们看了一个电路 (IsBinary
),它验证提供的输入是否确实是二进制的。该电路被硬编码为仅接受 2 个输入。
template IsBinary() {
signal input in[2];
in[0] * (in[0] - 1) === 0;
in[1] * (in[1] - 1) === 0;
}
component main = IsBinary();
虽然上面的代码适用于两个输入,但修改它以支持大量的 n
输入将需要手动添加约束,这是一种糟糕的开发者体验。
因此,Circom 允许我们使用以下模式约束任意数量的信号,以自动生成约束:
template IsBinary(n) {
// n 个输入的数组
signal input in[n];
// n 个循环:n 个约束
for (var i = 0; i < n; i++) {
in[i] * (in[i] - 1) === 0;
}
}
// 使用 4 个输入和 4 个约束实例化
component main = IsBinary(4);
请注意,模板声明已更改为在括号中包含 n
。
n
被称为模板参数n
在电路中用于指定数组 in
的大小n
的值虽然约束可以通过编程方式生成,但约束的存在和配置不能有条件地依赖于信号。
虽然模板可以使用参数,但电路必须是静态的且定义明确。不支持“动态长度”的电路或约束 —— 一切都必须从一开始就固定且定义明确。
想象一下,如果一个 R1CS 约束系统的结构可以根据输入信号值而改变,会发生什么。证明者和验证者都无法操作,因为约束的数量没有确定。
n
的值必须在编译时设置。
for
、var
现在我们解释上面介绍的 for
循环。
template IsBinary(n) {
// n 个输入的数组
signal input in[n];
// n 个循环:n 个约束
for (var i = 0; i < n; i++) {
in[i] * (in[i] - 1) === 0;
}
}
// 使用 4 个输入和 4 个约束实例化
component main = IsBinary(4);
n
定义0
还是 1
我们在电路中引入了两个新的关键字:for
和 var
for
的工作方式与你习惯的方式相同。var
关键字声明一个 变量;在本例中,是 i
,如循环定义中所示。=
将右边的值赋给左边的变量。在这里,变量 i
用于以编程方式引用输入数组中的不同信号,同时为它们创建约束。能够以编程方式生成约束非常有用,因为当涉及到数百或数千个约束时,手动执行此操作将非常容易出错。
变量保存非信号数据并且是可变的。这是一个在循环之外声明变量的示例:
template VariableExample(n) {
var acc = 2;
signal s;
}
p
为模进行。完整的运算符列表在 Circom 文档中提供。这些运算符会让你感觉很熟悉,因为它们来自类似 C 的语言(例如 ++
、**
、<=
等)。但是,请记住 /
表示与乘法逆元的乘法,而 \
表示整数除法。+
、*
、===
、<--
和 <==
。我们将在后面的文章中讨论 <--
和 <==
。Circom 允许我们使用 if
语句有条件地创建约束 —— 但这些条件必须是确定性的且在编译时已知。下一个示例:
假设我们有两个数组。我们可以使用以下模板生成约束,强制偶数索引处的项目相等(而不检查奇数索引)
template EqualOnEven(n) {
signal input in1[n];
signal input in2[n];
for (var i = 0; i < n; i++) {
if (i % 2 == 0) {
in1[i] === in2[i];
}
// 否则不生成约束
}
}
请注意,变量 i
决定生成哪些约束。
以下代码是不允许的,因为信号 a
用作 if
语句的条件:
template IfStatementViolation() {
signal input a;
signal input b;
if (a == 2) {
b === 3;
}
else {
b === 4;
}
}
在 Rank 1 Constraint System 中,信号之间只能进行加法和乘法运算。Circom 只是 Rank 1 Constraint System 之上的一个薄封装。因此,它不能将 if 语句“转换”为加法和乘法。
仍然可以在 Circom 中基于信号进行条件运算(if 语句)—— 这是后面章节的主题。但现在,请考虑没有从 if
语句到单个乘法的“直接”转换。
变量可以用作约束的一部分。在下面的示例中,我们强制输入数组 in[n]
是一个斐波那契数列。请注意,变量数组语法是 var varName[size]
:
template IsFib(n) {
assert(n > 1);
signal input in[n];
// 生成斐波那契数列
var correctFibo[n];
correctFibo[0] = 0;
correctFibo[1] = 1;
for (var i = 2; i < n; i++) {
correctFibo[i] = correctFibo[i - 1] + correctFibo[i - 2];
}
// 断言输入是一个斐波那契数列
for (var i = 0; i < n; i++) {
in[i] === correctFibo[i];
}
}
值得注意的是:
assert(n > 1)
不会生成任何约束。它会阻止在不满足模板参数条件的情况下实例化模板。signal === var
来强制信号具有某个值。这与执行 signal === 5
或其他某个常量相同。相反,我们可以使用变量为幻数分配一个名称,以提高可读性。例如:
template Equality() {
signal input in[2];
var left = 0;
var right = 1;
// 要求输入
// 相等
in[left] === in[right];
}
在 Circom 中,变量可以像常量一样添加到信号或与信号相乘。在下面的示例中,我们要求 in2[]
是 in1[]
乘以其索引。
例如,如果 in1[] = [3,5,6]
,那么 in2[] = [0,5,12]
必须成立,因为 [3,5,6]
按元素与 [0,1,2]
相乘。
template IsIndexMultiplied(n) {
signal input in1[n];
signal input in2[n];
for (var i = 0; i < n; i++) {
in1[i] * i === in2[i];
}
}
component main = IsIndexMultiplied(3);
/* INPUT = {"in1": [0,1,2], "in2": [0,1,4]} */
// 接受
// in1[] = [0,1,2]
// in2[] = [0,1,4]
// 拒绝
// in1[] = [0,1,2]
// in2[] = [0,0,2]
你可以在 这里 测试代码。
尝试 ZK Puzzles 中的以下问题。运行测试以检查你的答案。
- 原文链接: rareskills.io/post/circo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!