Circom模板参数、变量、循环、If语句、断言

本文介绍了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 的值

Circom 中的电路和约束必须具有固定的、已知的结构

虽然约束可以通过编程方式生成,但约束的存在和配置不能有条件地依赖于信号。

虽然模板可以使用参数,但电路必须是静态的且定义明确。不支持“动态长度”的电路或约束 —— 一切都必须从一开始就固定且定义明确。

想象一下,如果一个 R1CS 约束系统的结构可以根据输入信号值而改变,会发生什么。证明者和验证者都无法操作,因为约束的数量没有确定。

n 的值必须在编译时设置。

For 循环和变量:forvar

现在我们解释上面介绍的 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

我们在电路中引入了两个新的关键字:forvar

  • for 的工作方式与你习惯的方式相同。
  • var 关键字声明一个 变量;在本例中,是 i,如循环定义中所示。
  • 等号 = 将右边的值赋给左边的变量。

在这里,变量 i 用于以编程方式引用输入数组中的不同信号,同时为它们创建约束。能够以编程方式生成约束非常有用,因为当涉及到数百或数千个约束时,手动执行此操作将非常容易出错。

变量

变量保存非信号数据并且是可变的。这是一个在循环之外声明变量的示例:

template VariableExample(n) {
  var acc = 2;
  signal s;
}
  • 默认情况下,变量是 R1CS 约束系统的一部分。
    • 我们很快就会看到,变量可以用作 R1CS 内的加法或乘法常量。
  • 变量用于计算 R1CS 之外的值,以帮助定义 R1CS。
  • 当使用变量时,Circom 的行为类似于一种正常的编程语言。
  • 数学运算以 p 为模进行。完整的运算符列表在 Circom 文档中提供。这些运算符会让你感觉很熟悉,因为它们来自类似 C 的语言(例如 ++**<= 等)。但是,请记住 / 表示与乘法逆元的乘法,而 \ 表示整数除法。
  • 但是,对于信号,唯一有效的运算符是 +*===<--<==。我们将在后面的文章中讨论 <--<==

If 语句

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 决定生成哪些约束。

信号不能用于 if 语句或 for 循环中的分支条件

以下代码是不允许的,因为信号 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 或其他某个常量相同。

Circom 没有常量关键字

相反,我们可以使用变量为幻数分配一个名称,以提高可读性。例如:

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]

你可以在 这里 测试代码。

主要收获

  • 在幕后,如果变量与信号相加或相乘,则该变量会被编译为 R1CS 中的常量。
  • 对于信号,不允许进行加法、减法或乘法以外的运算,因为 R1CS 只能进行与常量的加法或乘法。幕后的减法只是与加法逆元的加法。
  • 如果一个信号除以一个常量(或一个持有常量的变量),它会将该信号乘以该常量的乘法逆元,除非该常量是 0,在这种情况下,代码将无法编译。

练习题

尝试 ZK Puzzles 中的以下问题。运行测试以检查你的答案。

  • 原文链接: rareskills.io/post/circo...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/