美文网首页
Circom 介绍

Circom 介绍

作者: 雪落无留痕 | 来源:发表于2023-09-25 14:51 被阅读0次

    Circom 是基于Rust开发的编译器,主要编译circom 语言开发的电路,输出约束系统的表示,由snarkjs 生成证明。

    Rank-1 constraint system

    R1CS 约束系统具有如下形式:
    (a_1*s_1+\cdots + a_n*s_n) * (b_1*s_1+\cdots + b_n*s_n) + (c_1*s_1+\cdots + c_n*s_n) = 0
    证明者需要提供有效的witness (s_1, \cdots, s_n) 生成零知识证明。

    示例

    电路编译

    pragma circom 2.0.0;
    
    template Multiplier2() {
        signal input a;
        signal input b;
        signal output c;
        c <== a*b;
     }
    
     component main = Multiplier2();
    

    然后对电路编译:

    circom multiplier2.circom --r1cs --wasm --sym --c
    
    • --r1cs: 生成multiplier2.r1cs , 包含约束系统的描述;
    • --wasm: 生成 Wasm 代码,用于生成witness ;
    • --sym: 生成·multiplier2.sys 符号文件用于调试;
    • --c: 生成C代码用于生成witness.

    生成witness

    构建输入input.json 为:

    {"a": 3, "b": 11}
    

    然后调用Wasm 生成witness 为:

    node generate_witness.js multiplier2.wasm input.json witness.wtns
    

    生成证明

    开启Powers of tau

    snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
    

    参与Powers of tau

    snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
    

    开始阶段2

    阶段2与电路相关,通过以下命令开始启动阶段2:

    snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v
    

    然后生成.zkey 文件包含证明和验证密钥,执行如下命令:

    snarkjs groth16 setup multiplier2.r1cs pot12_final.ptau multiplier2_0000.zkey
    

    参与阶段2:

    snarkjs zkey contribute multiplier2_0000.zkey multiplier2_0001.zkey --name="1st Contributor Name" -v
    

    导出验证密钥:

    snarkjs zkey export verificationkey multiplier2_0001.zkey verification_key.json
    

    生成的验证密钥verification_key.json为:

    {
     "pi_a": [
      "12760910679062051146028245764180144304135176860171829439423433793987947801318",
      "15627351643566791700410887806223057411723190268680338266476116334981291160699",
      "1"
     ],
     "pi_b": [
      [
       "1765658782548091050330101314873972200746475466904382545933668714427728146407",
       "8831055067363820406189809670502192437243044552509000960737961795818836572464"
      ],
      [
       "17406360157052120611680078243109373801402264858772382543913010037295998951887",
       "13607675671111808291939915735439749555363789088081636489573695410874630259559"
      ],
      [
       "1",
       "0"
      ]
     ],
     "pi_c": [
      "2792640172422648679222543320314624195856069203876481047009502575189585240826",
      "5913917177125152097186179448098822785808387757499429789332191237284364705015",
      "1"
     ],
     "protocol": "groth16",
     "curve": "bn128"
    }
    

    生成证明

    snarkjs groth16 prove multiplier2_0001.zkey witness.wtns proof.json public.json
    

    其中public.json 为:

    [
     "33"
    ]
    

    proof.json 为:

    {
     "pi_a": [
      "12760910679062051146028245764180144304135176860171829439423433793987947801318",
      "15627351643566791700410887806223057411723190268680338266476116334981291160699",
      "1"
     ],
     "pi_b": [
      [
       "1765658782548091050330101314873972200746475466904382545933668714427728146407",
       "8831055067363820406189809670502192437243044552509000960737961795818836572464"
      ],
      [
       "17406360157052120611680078243109373801402264858772382543913010037295998951887",
       "13607675671111808291939915735439749555363789088081636489573695410874630259559"
      ],
      [
       "1",
       "0"
      ]
     ],
     "pi_c": [
      "2792640172422648679222543320314624195856069203876481047009502575189585240826",
      "5913917177125152097186179448098822785808387757499429789332191237284364705015",
      "1"
     ],
     "protocol": "groth16",
     "curve": "bn128"
    }
    

    生成验证合约

    可以生成Solidity 验证合约,如下所示:

    snarkjs zkey export solidityverifier multiplier2_0001.zkey verifier.sol
    

    生成合约调用参数:

    snarkjs generatecall
    

    Circom 语法

    Signals

    算术电路主要在有限域上Z/pZ的元素, 通过标识符答命名,用关键字signal 声明,如下所示, signal 可分为: 输入,输出,和中间信号。

    signal input in;
    signal output out[N];
    signal inter;
    

    signals 总是私有的,除非直接声明,如下所示:

    pragma circom 2.0.0;
    
    template Multiplier2(){
       //Declaration of signals
       signal input in1;
       signal input in2;
       signal output out;
       out <== in1 * in2;
    }
    
    component main {public [in1,in2]} = Multiplier2();
    

    在 Circom2.0.4之后,对于中间和输出信号,可以在声明之后直接初始化,如下:

    pragma circom 2.0.0;
    
    template Multiplier2(){
       //Declaration of signals
       signal input in1;
       signal input in2;
       signal output out <== in1 * in2;
    }
    
    component main {public [in1,in2]} = Multiplier2();
    

    所有的输出信号都是公开的,输入信号是私有的,除非直接用public 声明;其它的信号都是私有的。

    从开发者的观点,只有公开输入和输出信号是可见的,中间的信号是无法访问,如下会报错:

    pragma circom 2.0.0;
    
    template A(){
       signal input in;
       signal outA; //We do not declare it as output.
       outA <== in;
    }
    
    template B(){
       //Declaration of signals
       signal output out;
       component comp = A();
       out <== comp.outA;
    }
    
    component main = B();
    

    信号是不可变的,即无法多次赋值,如下 out 被赋值2次, 会产生编译错误:

    pragma circom 2.0.0;
    
    template A(){
       signal input in;
       signal output outA; 
       outA <== in;
    }
    
    template B(){
       //Declaration of signals
       signal output out;
       out <== 0;
       component comp = A();
       comp.in <== 0;
       out <== comp.outA;
    }
    
    component main = B();
    

    变量

    var x;
    x = 234556;
    var y = 0;
    var z[3] = [1,2,3]
    

    模块

    模板

    Circom 使用模板(templates)来生成通用电路。模板可以使用参数进行实例化,使用模块可以组合较大规模的电路,模板具有输入和输出信号:

    template tempid ( param_1, ... , param_n ) {
     signal input a;
     signal output b;
    
     .....
    
    }
    

    不能对输入信号进行赋值,如下会报错:

    pragma circom 2.0.0;
    
    template wrong (N) {
     signal input a;
     signal output b;
     a <== N;
    }
    
    component main = wrong(1);
    

    通过提供必要的参数,对模板实例化,如下所示:

    pragma circom 2.0.0;
    
    template wrong (N) {
     signal input a;
     signal output b;
     a <== N;
    }
    
    component main = wrong(1);
    

    模板的参数必须在编译时赋予常量值,否则报错:

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

    组件也是不可变的,并且对于不同的初始化路径,需要是同种类型。当组件相互独立,可以使用关键字parallet 进行并行计算,如下所示:

    template parallel NameTemplate(...){...}
    

    然后生成的 C++ 文件包含并行处理的代码,计算witness

    component comp = parallel NameTemplate(...){...}
    

    实例代码如下所示:

    component rollupTx[nTx];
    for (i = 0; i < nTx; i++) {
            rollupTx[i] = parallel RollupTx(nLevels, maxFeeTx);
    }
    

    2.0.6 版本后,引入定制模板,使用关键字custom , 如下所示:

    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
    }
    

    和标准模板的差别在于,定制模板不会生成r1cs 约束,主要用于PLONK 方案中。

    Pragma

    使用pragma 指令指明编译器版本,如下:

    pragma circom xx.yy.zz;
    

    对于定制模板,使用如下指令:

    pragma custom_templates;
    
    Functions

    circom 中有函数定义,如下所示:

    function funid ( param1, ... , paramn ) {
    
     .....
    
     return x;
    }
    

    函数可以递归定义,但是函数无法声明信号或生成约束。

    Include

    可以使用include 包含其它 circom 文件作为库,如下所示:

    include "montgomery.circom";
    include "mux3.circom";
    include "babyjub.circom";
    
    Main Component

    为了执行circom, 需要定义一个 main 组件,需要定义全部的输入和输出信号:

    component main {public [signal_list]} = tempid(v1,...,vn);
    

    其中{public [signal_list]} 是可选的,模板中没有包含进列表的输入信号都是私有的,如有所示:

    pragma circom 2.0.0;
    
    template A(){
        signal input in1;
        signal input in2;
        signal output out;
        out <== in1 * in2;
    }
    
    component main {public [in1]}= A();
    

    上例中有2个输入信号,in1 是公开的,in2 是私有信号,输出信号部是公开的。

    有且仅有一个main component , 否则会报错。

    语法

    注释

    可以写以下形式的注释:

    //Using this, we can comment a line.
    
    template example(){
        signal input in;   //This is an input signal.
        signal output out; //This is an output signal.
    }
    
    /*
    All these lines will be 
    ignored by the compiler.
    */
    
    标识符
    signal input _in; 
    var o_u_t;
    var o$o;
    
    保留关键字

    Circom 具有以下保留关键字:

    signal input output public template component var function return if else for while do log assert include 
    pragma circom
    pragam custom_templates
    

    基本运算符

    Circom算术化运算定义在域 Z/pZ上, p 的值为:

    p = 21888242871839275222246405745257275088548364400416034343698204186575808495617.
    

    p 使用 GLOBAL_FIELD_P 定义。

    条件表达式

    条件表达式定义为:

    var z = x>y? x : y;
    

    布尔运算符

    且: &&, 或: ||, 非: !.

    关系运算符

    关系运算有:<, > , <=, >=, ==, != 依赖于数学函数 val(x), 定义如下:

           val(z) = z-p  if p/2 +1 <= z < p
    
           val(z) = z,    otherwise.
    

    算术化运算符

    算术化运算符有:+, -, *, **, /(乘逆), \(商), %

    算术化运算符可以和赋值结合: +=, -=, *=, **=, /=, \=, %=, ++, --.

    按位运算符

    位操作的运算符有:&, |, ~, ^, >>, <<

    位操作运算符和赋值结合:&=, |=, ~=, ^=, >>=, <<=

    约束生成

    Circom 通常需要考虑以下几种表达式:

    • Constant values: 仅允许常量值;
    • Linear expresion: 例如 2*x + 3*y + 2
    • Quadratic expression: 二次表达式为 A*B-C 的形式,例如 (2*x + 3*y + 2) * (x+y) + 6*x + y – 2;
    • Non quadratic expression: 其它的算术化表达式。

    约束通过运算符=== 添加,创建简化的恒等约束:

    a*(a-1) === 0;
    

    <== 运算符能同时赋值和生成约束,例如:

    out <== 1 - a*b;
    

    等价于:

    out === 1 – a*b;
    out <-- 1 - a*b;
    

    注: <-- 仅赋值,不添加约束。

    只有二次表达式允许包含进约束中,例如下面的代码会报错;

    template multi3() {
         signal input in;
         signal input in2;
         signal input in3;
         signal output out;
         out <== in*in2*in3;  // Not quadratic
    }
    

    控制流

    条件表达式

    if ( boolean_condition ) block_of_code else block_of_code

    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

    var y = 0;
    for(var i = 0; i < 100; i++){
        y++;
    }
    
    Loop 循环表达式

    while ( boolean_condition ) block_of_code

    var y = 0;
    var i = 0;
    while(i < 100){
        i++;
        y += y;
    }
    

    数据类型

    circom 基本的数据类型有:

    • Field element values: 模 p 的域元素值
    • Arrays: 类型相同的有限的元素数组。
    var x[3] = [2,8,4];
    var z[n+1];  // where n is a parameter of a template
    var dbl[16][2] = base;
    var y[5] = someFunction(n);
    

    Scoping

    Circom 的signalscomponents 必须有全局的范围,需要在最顶层定义。

    如下代码会报错:

    pragma circom 2.0.0;
    
    template Multiplier2 (N) {
       //Declaration of signals.
       signal input in;
       signal output out;
    
       //Statements.
       out <== in;
       signal input x;
       if(N > 0){
        signal output out2;
        out2 <== x;
       }
    }
    
    component main = Multiplier2(5);
    

    关于可见性,可以访问组件的的信号,但是无法嵌套访问,即子组件的信号。

    Code Quality

    assert(bool_expression);

    assert 检查是在执行的时候,若条件失败,则witness 生成会中断。

    template Translate(n) {
    assert(n<=254);
    …..
    }
    

    当约束通过 === 引入时,会自动添加asset。

    为了便于调试,引入 log, 具有非条件的表达式,如下所示:

    log(135);
    log(c.b);
    log(x==y);
    

    2.0.6 之后,允许输入表达式列表:

    log("The expected result is ",135," but the value of a is",a);
    

    Circom Insight

    circom 具有两个编译阶段:

    • 构建: 生成约束条件;
    • 代码生成: 生成计算witness 的代码。

    参考

    https://docs.circom.io/

    https://github.com/iden3/circom

    https://github.com/iden3/snarkjs

    https://github.com/iden3/circomlib

    https://github.com/iden3/wasmsnark

    https://github.com/iden3/rapidsnark.git

    https://github.com/iden3/ffjavascript.git

    相关文章

      网友评论

          本文标题:Circom 介绍

          本文链接:https://www.haomeiwen.com/subject/xeqcbdtx.html