diff --git a/ZK DSL/Circom/2_Templates.md b/ZK DSL/Circom/2_Templates.md index f84d711..db1becd 100644 --- a/ZK DSL/Circom/2_Templates.md +++ b/ZK DSL/Circom/2_Templates.md @@ -1,6 +1,321 @@ -# Templates - ## Templates & Components - ## Pragma - ## Functions - ## Include - ## The main component \ No newline at end of file +## 模板与组件 + +### 模板 + +在 Circom 中创建通用电路的机制称为模板。它们通常是基于一些在使用模板时必须实例化的参数。模板的实例化是一个新的电路对象,可以用来组合其他电路,作为更大电路的一部分。由于模板通过实例化定义电路,它们有自己的信号。 + +``` +template tempid (param_1, ..., param_n) { + signal input a; + signal output b; + + ..... +} +``` + +模板不能包含局部函数或模板定义。 + +在定义模板的同一模板中给输入信号赋值会产生 "Exception caused by invalid assignment" 错误,如下例所示。 + +``` +pragma circom 2.0.0; + +template wrong (N) { + signal input a; + signal output b; + a <== N; +} + +component main = wrong(1); +``` + +模板的实例化使用关键字 `component` 并提供必要的参数。 + +``` +component c = tempid(v1, ..., vn); +``` + +参数的值应在编译时已知。以下代码会产生编译错误消息:"Every component instantiation must be resolved during the constraint generation phase"。 + +``` +pragma circom 2.0.0; + +template A(N1, N2) { + signal input in; + signal output out; + out <== N1 * in * N2; +} + +template wrong (N) { + signal input a; + signal output b; + component c = A(a, N); +} + +component main {public [a]} = wrong(1); +``` + +### 组件 + +组件定义了一个算术电路,它接收 N 个输入信号并产生 M 个输出信号和 K 个中间信号。此外,它可以产生一组约束。 + +为了访问组件的输入或输出信号,我们将使用点符号。没有其他信号在组件外部可见。 + +``` +c.a <== y*z-1; +var x; +x = c.b; +``` + +组件实例化在其所有输入信号分配为具体值之前不会触发。因此,实例化可能会延迟,因此组件创建指令并不意味着组件对象的执行,而是创建实例化过程,该过程将在所有输入设置后完成。只有在设置了所有输入后,组件的输出信号才能使用,否则会产生编译错误。例如,以下代码会导致错误: + +``` +pragma circom 2.0.0; + +template Internal() { + signal input in[2]; + signal output out; + out <== in[0] * in[1]; +} + +template Main() { + signal input in[2]; + signal output out; + component c = Internal(); + c.in[0] <== in[0]; + c.out ==> out; // c.in[1] 尚未分配 + c.in[1] <== in[1]; // 这行代码应在调用 c.out 之前 +} + +component main = Main(); +``` + +组件是不可变的(如信号)。组件可以先声明,再在第二步初始化。如果有多个初始化指令(在不同的执行路径中),它们都需要是同一模板的实例化(也许参数值不同)。 + +``` +template A(N) { + signal input in; + signal output out; + out <== in; +} + +template C(N) { + signal output out; + out <== N; +} + +template B(N) { + signal output out; + component a; + if (N > 0) { + a = A(N); + } else { + a = A(0); + } +} + +component main = B(1); +``` + +如果指令 `a = A(0);` 被替换为 `a = C(0);`,编译将失败并显示错误消息:"Assignee and assigned types do not match"。 + +我们可以按照前述对大小的限制来定义组件数组。此外,不允许在数组组件的定义中进行初始化,实例化只能逐个组件进行,访问数组的位置。数组中的所有组件必须是同一模板的实例,如下例所示。 + +``` +template MultiAND(n) { + signal input in[n]; + signal output out; + component and; + component ands[2]; + var i; + if (n == 1) { + out <== in[0]; + } else if (n == 2) { + and = AND(); + and.a <== in[0]; + and.b <== in[1]; + out <== and.out; + } else { + and = AND(); + var n1 = n \ 2; + var n2 = n - n \ 2; + ands[0] = MultiAND(n1); + ands[1] = MultiAND(n2); + for (i = 0; i < n1; i++) ands[0].in[i] <== in[i]; + for (i = 0; i < n2; i++) ands[1].in[i] <== in[n1 + i]; + and.a <== ands[0].out; + and.b <== ands[1].out; + out <== and.out; + } +} +``` + +当组件是独立的(输入不依赖其他输出)时,可以使用 `parallel` 标签并行计算这些部分,如下所示。 + +``` +template parallel NameTemplate(...){...} +``` + +如果使用此标签,生成的 C++ 文件将包含用于计算见证的并行化代码。在处理大型电路时,并行化变得尤为重要。 + +注意,前述并行性是在模板级别声明的。有时,可以为每个组件声明并行性。从版本 2.0.8 开始,`parallel` 标签也可以在组件级别使用,标签在调用模板之前标明。 + +``` +component comp = parallel NameTemplate(...){...} +``` + +一个实际使用示例如下代码中的片段: + +``` +component rollupTx[nTx]; +for (i = 0; i < nTx; i++) { + rollupTx[i] = parallel RollupTx(nLevels, maxFeeTx); +} +``` + +需要再次强调的是,此并行性只能在 C++ 见证生成器中利用。 + +### 自定义模板 + +从版本 2.0.6 开始,该语言允许定义一种新类型的模板,即自定义模板。这种新结构类似于标准模板:它们以相同的方式声明,只需在声明后添加关键字 `custom`;并以完全相同的方式实例化。即,一个自定义模板 `Example` 如下定义和实例化: + +``` +pragma circom 2.0.6; // 注意,自定义模板仅在版本 2.0.6 及之后允许 +pragma custom_templates; + +template custom Example() { + // 自定义模板代码 +} + +template UsingExample() { + component example = Example(); // 自定义模板的实例化 +} +``` + +然而,它们的计算编码方式不同于标准模板。每个自定义模板的使用将由 `snarkjs` 在生成和验证 zk 证明的后续阶段处理,在此情况下使用 PLONK 方案(并将自定义模板的定义用作 PLONK 的自定义门)。有关定义和使用自定义模板的信息将导出到 `.r1cs` 文件中。这意味着自定义模板不能在其主体内引入任何约束,也不能声明任何子组件。 + +## Pragma + +### 版本 Pragma + +所有以 .circom 为扩展名的文件都应该以第一个 `pragma` 指令开始,指定编译器版本,如下所示: + +```plaintext +pragma circom xx.yy.zz; +``` + +这是为了确保电路与 `pragma` 指令之后所指示的编译器版本兼容。否则,编译器会抛出一个警告。 + +如果文件不包含此指令,则假定代码与最新版本的编译器兼容,并会抛出一个警告。 + +### 自定义模板 Pragma + +自 circom 2.0.6 版本起,语言允许定义自定义模板(更多信息请参见[这里](../circom-language/templates-and-components.md#custom-templates))。此 `pragma` 允许 circom 程序员轻松判断是否使用了自定义模板:如果任何声明自定义模板的文件或包含声明任何自定义模板文件的文件没有使用此 `pragma`,编译器将产生错误。此外,它将告知程序员哪些文件应包含此 pragma。 + +要使用它,只需在需要它的 .circom 文件开头(在版本 `pragma` 之后)添加以下指令: + +```plaintext +pragma custom_templates; +``` + +## 函数 + +在 circom 中,函数定义了一些可以执行计算以获得返回值或表达式的通用抽象代码片段。 + +```plaintext +function funid (param1, ..., paramn) { + + ..... + + return x; +} +``` + +函数计算数值(或数值数组)或表达式。函数可以是递归的。请参考 circom 库中的[下一个函数](https://github.com/iden3/circomlib/blob/master/circuits/binsum.circom)。 + +```plaintext +/* + 此函数计算输出中的额外位数 + 以进行完整求和。 + */ + +function nbits(a) { + var n = 1; + var r = 0; + while (n-1 < a) { + r++; + n *= 2; + } + return r; +} +``` + +函数不能声明信号或生成约束(如有需要,请使用模板)。以下函数会产生错误消息:"Template operator found"。 + +```plaintext +function nbits(a) { + signal input in; // 这是不允许的。 + var n = 1; + var r = 0; + while (n-1 < a) { + r++; + n *= 2; + } + r === a; // 这也是不允许的。 + return r; +} +``` + +与往常一样,可以有多个 return 语句,但每个执行路径必须以 return 语句结束(否则会产生编译错误)。return 语句的执行将控制权返回给函数的调用者。 + +```plaintext +function example(N) { + if (N >= 0) { return 1; } +// else { return 0; } +} +``` + +函数 `example` 的编译会产生以下错误消息:"In example there are paths without return"。 + +## Include + +模板和其他代码一样,可以在其他文件(如库)中找到。为了在我们的程序中使用其他文件中的代码,我们必须使用关键字 `include` 来包含它们,并带有相应的文件名(默认扩展名为 .circom)。 + +```plaintext +include "montgomery.circom"; +include "mux3.circom"; +include "babyjub.circom"; +``` + +这段代码包括了 circom 库中的 `montgomery.circom`、`mux3.circom` 和 `babyjub.circom` 文件。 + +自 circom 2.0.8 版本起,可以使用 `-l` 选项来指定搜索要包含文件的路径。 + +## 主组件 + +为了开始执行,必须提供一个初始组件。默认情况下,该组件的名称是 "main",因此需要使用某个模板实例化组件 main。 + +这是一个创建电路所需的特殊初始组件,它定义了电路的全局输入和输出信号。因此,与其他组件相比,它有一个特殊属性:公共输入信号列表。创建主组件的语法如下: + +```plaintext +component main {public [signal_list]} = tempid(v1,...,vn); +``` + +其中 `{public [signal_list]}` 是可选的。模板中未包含在列表中的任何输入信号都被视为私有信号。 + +```plaintext +pragma circom 2.0.0; + +template A(){ + signal input in1; + signal input in2; + signal output out; + out <== in1 * in2; +} + +component main {public [in1]} = A(); +``` + +在这个示例中,我们有两个输入信号 `in1` 和 `in2`。注意 `in1` 被声明为电路的公共信号,而 `in2` 被视为私有信号,因为它没有出现在列表中。最后,输出信号始终被视为公共信号。 + +只能定义一个主组件,不仅在编译的文件中,而且在程序中包含的任何其他 circom 文件中也是如此。否则,编译会失败,并显示以下消息:_"Multiple main components in the project structure"_。 \ No newline at end of file diff --git a/ZK DSL/Circom/3_Syntax.md b/ZK DSL/Circom/3_Syntax.md index ee6fdf3..b4b2c77 100644 --- a/ZK DSL/Circom/3_Syntax.md +++ b/ZK DSL/Circom/3_Syntax.md @@ -1,4 +1,64 @@ -# Syntax - ## Comment lines - ## Identifiers - ## Reserved Keywords \ No newline at end of file +## 注释行 + +在 circom 中,你可以在源代码中添加注释。这些注释行将被编译器忽略。注释帮助程序员更好地理解你的源代码。建议在代码中添加注释。 + +circom 2.0 中允许的注释行与其他编程语言(如 C 或 C++)类似。 + +你可以使用 `//` 在单行上写注释: + +```plaintext +// 使用这种方式,我们可以注释一行。 +``` + +你也可以在代码行的末尾使用 `//` 写注释: + +```plaintext +template example(){ + signal input in; // 这是一个输入信号。 + signal output out; // 这是一个输出信号。 +} +``` + +最后,你可以使用 `/*` 和 `*/` 写多行注释: + +```plaintext +/* +所有这些行将被 +编译器忽略。 +*/ +``` + +## 标识符 + +任何以任意数量的 “\__” 开头,后跟 ASCII 字母字符,再后跟任意数量的字母或数字字符、“\_” 或 “$” 的非保留关键字都可以用作标识符。以下是一些标识符的示例: + +```plaintext +signal input _in; +var o_u_t; +var o$o; +``` + +## 保留关键字 + +保留关键字列表如下: + +* **signal:** 声明一个新的信号。 +* **input:** 声明信号为输入。 +* **output:** 声明信号为输出。 +* **public:** 声明信号为公共。 +* **template:** 定义一个新的电路。 +* **component:** 实例化一个模板。 +* **var:** 声明一个新的整数变量。 +* **function:** 定义一个新函数。 +* **return:** 从函数返回。 +* **if:** 基于条件表达式的结果进行分支。 +* **else:** `if` 控制流结构的备用选项。 +* **for:** 基于表达式的结果进行条件循环。 +* **while:** 基于表达式的结果进行条件循环。 +* **do:** 基于表达式的结果进行条件循环。 +* **log:** 打印评估结果。 +* **assert:** 在构建时检查条件。 +* **include:** 包含指定文件的代码。 +* **parallel:** 生成带有并行组件或模板的 C 代码。 +* **pragma circom:** 检查编译器版本的指令。 +* **pragma custom_templates:** 指示使用自定义模板的指令。 \ No newline at end of file diff --git a/ZK DSL/Circom/4_Basic_Operators.md b/ZK DSL/Circom/4_Basic_Operators.md index fd4a460..6e78a8c 100644 --- a/ZK DSL/Circom/4_Basic_Operators.md +++ b/ZK DSL/Circom/4_Basic_Operators.md @@ -1 +1,172 @@ -# Basic Operators \ No newline at end of file +# 基本运算符 + +Circom 提供了布尔运算符、算术运算符和按位运算符。它们具有标准语义,但应用于数值的算术运算符在 p 模数下工作。 + +运算符的优先级和关联性类似于 Rust(在[这里](https://doc.rust-lang.org/1.22.1/reference/expressions/operator-expr.html#operator-precedence)定义)。 + +表达式可以使用以下运算符构建,但条件运算符 `?_:_` 只能出现在顶层。 + +## 域元素 + +域元素是 Z/pZ 范围内的值,其中 p 是默认设置的素数: + +`p = 21888242871839275222246405745257275088548364400416034343698204186575808495617.` + +因此,域元素在算术上是模 p 进行操作的。 + +Circom 语言对这个数字是参数化的,可以在不影响语言其他部分的情况下更改它(使用 `GLOBAL_FIELD_P`)。 + +## 条件表达式 + +**Boolean\_condition ? true\_value : false\_value** + +```plaintext +var z = x > y ? x : y; +``` + +这种条件表达式不允许嵌套形式,因此只能在顶层使用。 + +## 布尔运算符 + +允许使用以下布尔运算符: + +| 运算符 | 示例 | 解释 | +| :----- | :------- | :------------------ | +| && | a && b | 布尔运算符 AND | +| \|\| | a \|\| b | 布尔运算符 OR | +| ! | ! a | 布尔运算符 NEGATION | + +## 关系运算符 + +关系运算符 **`< , > , <= , >= , == , !=`** 的定义取决于数学函数 `val(x)`,该函数定义如下: + +```plaintext +val(z) = z-p if p/2 +1 <= z < p + +val(z) = z, otherwise. +``` + +根据这个函数,关系运算符的定义如下: + +```plaintext +x < y 定义为 val(x % p) < val(y % p) + +x > y 定义为 val(x % p) > val(y % p) + +x <= y 定义为 val(x % p) <= val(y % p) + +x >= y 定义为 val(x % p) >= val(y % p) +``` + +其中 `<, >, <=, >=` 是整数比较。 + +## 算术运算符 + +所有算术运算在模 p 下工作。我们有以下运算符: + +| 运算符 | 示例 | 解释 | +| :----: | :------: | :------------: | +| + | a + b | 算术加法模 p | +| - | a - b | 算术减法模 p | +| \* | a \* b | 算术乘法模 p | +| \*\* | a \*\* b | 幂运算模 p | +| / | a / b | 乘以逆元素模 p | +| \ | a \ b | 整数除法的商 | +| % | a % b | 整数除法的余数 | + +有些运算符将算术运算与最终赋值相结合。 + +| 运算符 | 示例 | 解释 | +| :----: | :------: | :-------------------------: | +| += | a += b | 算术加法模 p 并赋值 | +| -= | a -= b | 算术减法模 p 并赋值 | +| \*= | a \*= b | 算术乘法模 p 并赋值 | +| \*\*= | a \*\* b | 幂运算模 p 并赋值 | +| /= | a /= b | 乘以逆元素模 p 并赋值 | +| \= | a \= b | 整数除法的商并赋值 | +| %= | a %= b | 整数除法的余数并赋值 | +| ++ | a++ | 单位增量。语法糖用于 a += 1 | +| -- | a-- | 单位减量。语法糖用于 a -= 1 | + +## 按位运算符 + +所有按位运算都是模 p 进行的。 + +| 运算符 | 示例 | 解释 | +| :------- | :----------- | :-------------- | +| & | a & b | 按位 AND | +| \| | a \| b | 按位 OR | +| ~ | ~a | 补码 254 位 | +| ^ | a ^ b | 按位异或 254 位 | +| >> | a >> 4 | 右移运算符 | +| << | a << 4 | 左移运算符 | + +移位运算也在模 p 下工作,定义如下(假设 p >= 7)。 + +对于所有 `0=< k <= p/2`(整数除法)我们有: + +* `x >> k = x/(2**k)` +* `x << k = (x*(2**k) & mask) % p` + +其中 b 是 p 的有效位数,mask 是 `2**b - 1`。 + +对于所有 `p/2 +1 <= k < p` 我们有: + +* `x >> k = x << (p-k)` +* `x << k = x >> (p-k)` + +注意,k 也是负数 `k-p`。 + +有些运算符将按位运算与最终赋值相结合。 + +| 运算符 | 示例 | 解释 | +| :-------- | :------------ | :-------------------- | +| &= | a &= b | 按位 AND 并赋值 | +| \|= | a \|= b | 按位 OR 并赋值 | +| ~= | ~=a | 补码 254 位并赋值 | +| ^= | a ^= b | 按位异或 254 位并赋值 | +| >>= | a >>= 4 | 右移运算符并赋值 | +| <<= | a <<= 4 | 左移运算符并赋值 | + +## 使用 Circom 库中运算符的示例 + +以下是一些使用上述运算符组合的示例。 + +```plaintext +pragma circom 2.0.0; + +template IsZero() { + signal input in; + signal output out; + signal inv; + inv <-- in!=0 ? 1/in : 0; + out <== -in*inv +1; + in*out === 0; +} + +component main {public [in]}= IsZero(); +``` + +此模板检查输入信号 `in` 是否为 `0`。如果是,输出信号 `out` 的值为 `1`。否则为 `0`。请注意,我们使用中间信号 `inv` 来计算 `in` 的逆元素或不存在时的 `0`。如果 `in` 为 0,那么 `in*inv` 为 0,`out` 的值为 `1`。否则,`in*inv` 始终为 `1`,则 `out` 为 `0`。 + +```plaintext +pragma circom 2.0.0; + +template Num2Bits(n) { + signal input in; + signal output out[n]; + var lc1=0; + var e2=1; + for (var i = 0; i> i) & 1; + out[i] * (out[i] -1 ) === 0; + lc1 += out[i] * e2; + e2 = e2+e2; + } + lc1 === in; +} + +component main {public [in]}= Num2Bits(3); +``` + +此模板返回一个 n 维数组,包含 `in` 的二进制值。第 7 行使用右移 `>>` 和 `&` 运算符在每次迭代中获取数组的第 `i` 个组件。最后,第 12 行添加约束 `lc1 = in` 以保证转换正确。 \ No newline at end of file diff --git a/ZK DSL/Circom/5_Constraint_Generation.md b/ZK DSL/Circom/5_Constraint_Generation.md index eed0a0c..7bcfdb4 100644 --- a/ZK DSL/Circom/5_Constraint_Generation.md +++ b/ZK DSL/Circom/5_Constraint_Generation.md @@ -1 +1,70 @@ -# Constraint Generation \ No newline at end of file +# 约束生成 + +为了理解 Circom 的构建部分,我们需要考虑以下类型的表达式: + +* **常量值**:仅允许常量值。 +* **线性表达式**:仅使用加法的表达式。也可以使用变量乘以常数来表示。例如,表达式 `2*x + 3*y + 2` 是允许的,因为它等效于 `x + x + y + y + y + 2`。 +* **二次表达式**:通过允许两个线性表达式相乘和线性表达式相加得到的表达式:A\*B - C,其中 A、B 和 C 是线性表达式。例如,`(2*x + 3*y + 2) * (x+y) + 6*x + y – 2`。 +* **非二次表达式**:任何不属于上述类型的算术表达式。 + +Circom 允许程序员定义约束来定义算术电路。所有约束必须是 A\*B + C = 0 形式的二次约束,其中 A、B 和 C 是信号的线性组合。Circom 会对定义的约束进行一些小的变换以符合 A\*B + C = 0 的格式: + +* 将等式的一侧移到另一侧。 +* 应用加法的交换律。 +* 乘(或除)常数。 + +使用运算符 `===` 来施加约束,它会创建给定等式约束的简化形式。 + +```plaintext +a*(a-1) === 0; +``` + +添加这样的约束还意味着在见证代码生成中添加一个 `assert` 语句。 + +约束生成可以与信号赋值结合使用,运算符 `<==` 的左侧是要赋值的信号。 + +```plaintext +out <== 1 - a*b; +``` + +等价于: + +```plaintext +out <-- 1 - a*b; +out === 1 - a*b; +``` + +如前所述,使用 `<--` 和 `-->` 运算符为信号赋值被认为是危险的,通常应该与 `===` 运算符结合使用,以通过约束描述所赋值的含义。例如: + +```plaintext +a <-- b/c; +a*c === b; +``` + +在构建阶段,变量可以包含使用乘法、加法和其他变量或信号以及字段值构建的算术表达式。只有二次表达式允许包含在约束中。超过二次的其他算术表达式或使用其他算术运算符(如除法或幂运算)不允许作为约束。 + +```plaintext +template multi3() { + signal input in; + signal input in2; + signal input in3; + signal output out; + out <== in*in2*in3; +} +``` + +此模板会产生错误 "Non quadratic constraints are not allowed!",因为它引入了约束 `out === in*in2*in3`,这不是二次约束。 + +以下示例展示了表达式的生成: + +```plaintext + signal input a; + signal output b; + var x = a*a; + x += 3; + b <== x; +``` + +最后一条指令产生约束 `b === a * a + 3`。 + +最后,程序员有时在开始使用 Circom 时会误用运算符 `<--`。他们通常会使用该运算符赋值一个二次表达式,结果没有添加约束。在这种情况下,需要同时执行赋值和添加约束的运算符是 `<==`。自 2.0.8 版本以来,如果出现这种情况,我们会发出警告。 \ No newline at end of file diff --git a/ZK DSL/Circom/6_Control_Flow.md b/ZK DSL/Circom/6_Control_Flow.md index 386281f..17b521e 100644 --- a/ZK DSL/Circom/6_Control_Flow.md +++ b/ZK DSL/Circom/6_Control_Flow.md @@ -1 +1,135 @@ -# Control Flow \ No newline at end of file +# 控制流 + +我们有标准的构造来定义程序的控制流。 + +## 条件语句: if-then-else + +**if ( boolean_condition ) block_of_code else block_of_code** + +else 部分是可选的。如果省略,则意味着“else 不做任何事情”。 + +```plaintext +var x = 0; +var y = 1; +if (x >= 0) { + x = y + 1; + y += 1; +} else { + y = x; +} +``` + +## 循环语句: for + +**for ( initialization_code ; boolean_condition ; step_code ) block_of_code** + +如果 initialization_code 包含 var 声明,则其作用域仅限于 for 语句,因此稍后使用它(不重新定义)会产生编译错误。 + +```plaintext +var y = 0; +for(var i = 0; i < 100; i++){ + y++; +} +``` + +## 循环语句: while + +**while ( boolean_condition ) block_of_code** + +当条件成立时执行代码块。每次执行代码块之前都会检查条件。 + +```plaintext +var y = 0; +var i = 0; +while(i < 100){ + i++; + y += y; +} +``` + +**重要**:当在 if-then-else 或循环语句中的任何块中生成约束时,条件不能是未知的(请参见 [Unknowns](../circom-insight/unknowns))。这是因为约束生成必须是唯一的,不能依赖于未知的输入信号。 + +如果条件中的表达式未知并且生成了一些约束,编译器将生成以下错误消息:“_There are constraints depending on the value of the condition and it can be unknown during the constraint generation phase_”。 + +```plaintext +pragma circom 2.0.0; + +template A(){} +template wrong(N1){ + signal input in; + component c; + if(in > N1){ + c = A(); + } +} +component main {public [in]} = wrong(1); +``` + +在此示例中,条件取决于编译时未知值的输入信号 `in`。 + +还需要注意的是,如果语句体不涉及任何信号或组件,或者约束不依赖于涉及未知值的值,则编译将成功,如下例所示。 + +```plaintext +template right(N){ + signal input in; + var x = 2; + var t = 5; + if(in > N){ + t = 2; + } +} +``` + +此模板是正确的,因为没有约束依赖于 `in` 的未知值。 + +```plaintext +template right(N1,N2){ + signal input in; + var x = 2; + var t = 5; + if(N1 > N2){ + t = 2; + } + x === t; +} +``` + +此模板是正确的,因为约束中涉及的变量值仅依赖于参数 `N1` 和 `N2` 的已知值。 + +**重要**:当 var 的内容取决于某些未知条件时会产生另一个编译错误:即当 var 在具有未知条件的 if-then-else 或循环语句中取值时。然后,变量的内容是非二次表达式,因此不能用于生成约束。 + +```plaintext +template wrong(){ + signal input in; + var x; + var t = 5; + if(in > 3){ + t = 2; + } + x === t; +} +``` + +此模板产生编译错误,因为最后一个约束中涉及的变量 `t` 的值取决于变量 `in` 的未知值。 + +计算的控制流类似于其他命令式语言,但[组件的实例化](../templates-and-components)可能不遵循代码的顺序结构,因为组件实例化不会被触发,直到所有输入信号都分配了具体值。 + +```plaintext +template mult(){ + signal input in[2]; + signal output out; + out <== in[0] * in[1]; +} + +template mult4(){ + signal input in[4]; + component comp1 = mult(); + component comp2 = mult(); + comp1.in[0] = in[0]; + comp2.in[0] = in[1]; + comp2.in[1] = in[2]; + comp1.in[1] = in[3]; +} +``` + +在这个例子中,`comp2` 在 `comp1` 之前实例化,因为 `comp2` 的输入信号在 `comp1` 的输入信号之前有具体值。因此,`comp2.out` 在执行第 13 行后获得值,而 `comp1.out` 在执行第 14 行后获得值。 \ No newline at end of file diff --git a/ZK DSL/Circom/7_Data types.md b/ZK DSL/Circom/7_Data types.md index c0d6b94..20237a8 100644 --- a/ZK DSL/Circom/7_Data types.md +++ b/ZK DSL/Circom/7_Data types.md @@ -1 +1,79 @@ -# Data types \ No newline at end of file +# 数据类型 + +Circom 中的基本变量类型有: + +* **域元素值**:模素数 _p_ 的整数值(见 [信号](../signals))。这是所有信号和基本变量的默认类型。 +* **数组**:它们可以包含相同类型(信号、var 或相同类型的组件或数组)的有限数量元素(在编译时已知)。元素从零开始编号,可以使用其位置的相应索引进行访问。数组访问使用方括号。声明给定类型的数组通过在变量标识符旁边添加 \[\] 并在括号之间包含大小(应使用常量值和/或模板的数字参数定义)来实现。 + +访问和声明应与其类型一致,因此我们用 m\[i\]\[j\] 进行访问和声明,因为 m\[i\] 是一个数组。以下是带有和不带有初始化的声明示例: + +```plaintext +var x[3] = [2,8,4]; +var z[n+1]; // 其中 n 是模板的参数 +var dbl[16][2] = base; +var y[5] = someFunction(n); +``` + +不允许使用 m\[i,j\] 表示数组的数组(矩阵)。 + +另一方面,以下情况将产生编译错误,因为数组的大小应显式给出: + +```plaintext +var z = [2,8,4]; +``` + +最后,信号的类型需要声明,因为它们不能全局分配为数组。它们按位置分配。 + +```plaintext + signal input in[3]; + signal output out[2]; + signal intermediate[4]; +``` + +组件数组必须使用相同的模板实例化(可以选择不同的参数)。 + +```plaintext +pragma circom 2.0.0; + +template fun(N){ + signal output out; + out <== N; +} + +template all(N){ + component c[N]; + for(var i = 0; i < N; i++){ + c[i] = fun(i); + } +} + +component main = all(5); +``` + +因此,以下代码将产生编译错误:"c\[i\] = fun(i) -> Assignee and assigned types do not match"。 + +```plaintext +pragma circom 2.0.0; + +template fun(N){ + signal output out; + out <== N; +} + +template fun2(N){ + signal output out; + out <== N; +} + +template all(N){ + component c[N]; + for(var i = 0; i < N; i++){ + if(i < N) + c[i] = fun(i); + else + c[i] = fun2(i); + } +} + +component main = all(5); +``` \ No newline at end of file