模板和组件

模板

在 Circcom 中创建通用电路的机制就是所谓的模板。

它们通常是在使用模板时必须实例化的某些值的参数。模板的实例化是一个新的电路对象,它可以用来组成其他电路,从而作为更大电路的一部分。由于模板通过实例化来定义电路,因此它们有自己的信号。

#![allow(unused)]
fn main() {
template tempid ( param_1, ... , param_n ) {
 signal input a;
 signal output b;

 .....

}
}

模板不能包含本地函数或模板定义。

在定义该值的同一模板内为输入信号赋值也会生成错误“Exception caused by invalid assignment”,如下一个示例所示。

#![allow(unused)]
fn main() {
pragma circom 2.0.0;

template wrong (N) {
 signal input a;
 signal output b;
 a <== N;
}

component main = wrong(1);

}

模板的实例化是使用关键字组件并提供必要的参数来完成的。

#![allow(unused)]
fn main() {
component c = tempid(v1,...,vn);
}

参数的值在编译时应该是已知的常量。下一个代码会生成以下编译错误消息:“Every component instantiation must be resolved during the constraint generation phase”。

#![allow(unused)]

fn main() {
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 个中间信号。此外,它还可以产生一组约束。

为了访问组件的输入或输出信号,我们将使用点表示法。组件外部看不到其他信号。

#![allow(unused)]
fn main() {
c.a <== y*z-1;
var x;
x = c.b;
}

在所有输入信号都分配给具体值之前,不会触发组件实例化。因此,实例化可能会被延迟,因此组件创建指令并不意味着组件对象的执行,而是意味着当所有输入都设置好后将完成实例化过程的创建。仅当设置了所有输入时才能使用组件的输出信号,否则会生成编译器错误。例如,下面的代码会导致错误:

#![allow(unused)]
fn main() {
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] is not assigned yet
   c.in[1] <== in[1];  // this line should be placed before calling c.out
}

component main = Main();
}

组件是不可变的(如信号)。可以首先声明组件,然后在第二步中初始化组件。如果有多个初始化指令(在不同的执行路径中),它们都需要是同一模板的实例化(可能具有不同的参数值)。

#![allow(unused)]
fn 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”。

我们可以按照之前给出的大小限制来定义组件数组。此外,不允许在组件数组的定义中进行初始化,并且只能逐个组件进行实例化,访问数组的位置。数组中的所有组件都必须是同一模板的实例,如下一个示例所示。

#![allow(unused)]
fn main() {
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,如下一行所示。

#![allow(unused)]
fn main() {
template parallel NameTemplate(...){...}
}

如果使用此标签,生成的 C++ 文件将包含计算见证的并行代码。在处理大型电路时,并行化变得尤为重要。

请注意,前面的并行性是在模板级别声明的。有时,声明每个组件的并行性可能很有用。从版本 2.0.8 开始,并行标签也可以在组件级别使用,并行标签在调用模板之前指示。

#![allow(unused)]
fn main() {
component comp = parallel NameTemplate(...){...}
}

用例的一个真实示例是汇总代码中的以下代码:

#![allow(unused)]
fn main() {
component rollupTx[nTx];
for (i = 0; i < nTx; i++) {
        rollupTx[i] = parallel RollupTx(nLevels, maxFeeTx);
}
}

需要再次强调的是,这种并行性只能在 C++ 见证生成器中利用。

自定义模板

从2.0.6版本开始,该语言允许定义一种新类型的模板,即自定义模板。这种新结构的工作方式与标准模板类似:它们的声明方式类似,只需在;custom之后的声明中添加关键字即可。template并以完全相同的方式实例化。即Example定义一个自定义模板,然后实例化如下:

#![allow(unused)]
fn main() {
pragma circom 2.0.6; // note that custom templates are only allowed since version 2.0.6
pragma custom_templates;

template custom Example() {
   // custom template's code
}

template UsingExample() {
   component example = Example(); // instantiation of the custom template
}
}

然而,它们的计算编码方式与标准模板的编码方式不同。snarkjs将在稍后阶段处理每个定义的自定义模板的使用,以生成和验证 zk 证明,而不是生成 r1cs 约束,在本例中使用 PLONK 方案(并使用自定义模板的定义作为 PLONK 的自定义门,请参阅这里如何)。有关自定义模板的定义和用法的信息将导出到文件中.r1cs(请参阅此处的第 4 节和第 5 节)。这意味着自定义模板不能在其主体内引入任何约束,也不能声明任何子组件。