Polygon zkEVM zkASM编译器——zkasmcom

Posted mutourend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Polygon zkEVM zkASM编译器——zkasmcom相关的知识,希望对你有一定的参考价值。

1. 引言

Polygon zkEVM采用zkASM(zero-knowledge Assembly language)语言来解析EVM bytecode。

zkASM编译器代码见:

本文重点关注zkasm代码,其主要依赖3个库:

  • yargs:交互式命令行工具,负责参数解析。
  • ffjavascript:Finite Field Library in Javascript。
  • jison:一个用JavaScript语言实现的一个语法分析器生成器。
"build_parser_zkasm": "mkdir -p build; ./node_modules/.bin/jison src/zkasm_parser.jison -o build/zkasm_parser.js",
"build_parser_command": "mkdir -p build; ./node_modules/.bin/jison src/command_parser.jison -o build/command_parser.js",
"build": "npm run build_parser_zkasm && npm run build_parser_command"

npm run build会生成2个解析器文件:

  • 1)zkasm_parser.js:compile.js中调用const lines = zkasm_parser.parse(src); 对 *.zkasm文件进行编译。
  • 2)command_parser.js:当为main入口时,调用cmdList[i] = command_parser.parse(cmdList[i]); 对command进行解析。

zkasm中的常量参数有:【STEP和ROTL_C为只读寄存器。】

const maxConst = (1n << 32n) - 1n;
const minConst = -(1n << 31n);
const maxConstl = (1n << 256n) - 1n;
const minConstl = -(1n << 255n);
const readOnlyRegisters = ['STEP', 'ROTL_C'];

以arrays.zkasm为例:【冒号右侧为OPCODE,对应的相应常量多项式设置见zkasm_parser.jison中的op内相应操作码的设置,如ARITH操作码对应$$ = arith: 1, arithEq0: 1,表示会设置arith和arithEq0常量多项式在该行的值为1。

VAR GLOBAL a[100] # 以type、scope、name、count来描述。
VAR GLOBAL b
VAR GLOBAL c[300]
VAR GLOBAL d

start: # 对应type为“label”,identifier为“start”,line为所在代码行,此处为6。里面的每行type为“step”。
        STEP => A
        0   :ASSERT

        1   :MSTORE(a)
        2   :MSTORE(b)
        3   :MSTORE(c)
        4   :MSTORE(d)
        @a => A
        @b => A
        @c => A
        @d => A

end:
       0 => A,B,C,D,E,CTX, SP, PC, GAS, MAXMEM, SR

finalWait:
        $beforeLast()  : JMPN(finalWait)

                         : JMP(start)
opINVALID:

执行node src/zkasm.js test/arrays.zkasm -o arrays.json进行编译。

其中,const lines = zkasm_parser.parse(src);解析后的结果为:

[
  # VAR GLOBAL a[100]
  "type": "var",
  "scope": "GLOBAL",
  "name": "a",
  "count": 100
 ,
  # VAR GLOBAL b
  "type": "var",
  "scope": "GLOBAL",
  "name": "b",
  "count": 1
 ,
  # VAR GLOBAL c[300]
  "type": "var",
  "scope": "GLOBAL",
  "name": "c",
  "count": 300
 ,
  # VAR GLOBAL d
  "type": "var",
  "scope": "GLOBAL",
  "name": "d",
  "count": 1
 ,
  # start:
  "type": "label",
  "identifier": "start",
  "line": 6
 ,
  # STEP => A,将STEP寄存器的值直接赋值给A寄存器。
  "type": "step",
  "assignment": 
   "in": 
    "type": "REG",
    "reg": "STEP"
   ,
   "out": [
    "A"
   ]
  ,
  "ops": [],
  "line": 7
 ,
  # 0   :ASSERT,assert常量0"type": "step",
  "assignment": 
   "in": 
    "type": "CONST",
    "const": 0
   ,
   "out": []
  ,
  "ops": [
   
    "assert": 1
   
  ],
  "line": 8
 ,
  # 1   :MSTORE(a),将常量1存入a数组中的第一位置。
  "type": "step",
  "assignment": 
   "in": 
    "type": "CONST",
    "const": 1
   ,
   "out": []
  ,
  "ops": [
   
    "offset": "a",
    "mOp": 1,
    "mWR": 1
   
  ],
  "line": 10
 ,
  # 2   :MSTORE(b),将常量2存入b中。
  "type": "step",
  "assignment": 
   "in": 
    "type": "CONST",
    "const": 2
   ,
   "out": []
  ,
  "ops": [
   
    "offset": "b",
    "mOp": 1,
    "mWR": 1
   
  ],
  "line": 11
 ,
  # 3   :MSTORE(c),将常量3存入数组c中。
  "type": "step",
  "assignment": 
   "in": 
    "type": "CONST",
    "const": 3
   ,
   "out": []
  ,
  "ops": [
   
    "offset": "c",
    "mOp": 1,
    "mWR": 1
   
  ],
  "line": 12
 ,
  # 4   :MSTORE(d),将常量4存入d中。
  "type": "step",
  "assignment": 
   "in": 
    "type": "CONST",
    "const": 4
   ,
   "out": []
  ,
  "ops": [
   
    "offset": "d",
    "mOp": 1,
    "mWR": 1
   
  ],
  "line": 13
 ,
  # @a => A,将a的索引赋值给A"type": "step",
  "assignment": 
   "in": 
    "type": "reference",
    "identifier": "a"
   ,
   "out": [
    "A"
   ]
  ,
  "ops": [],
  "line": 14
 ,
  # @b => A,将b的索引赋值给A"type": "step",
  "assignment": 
   "in": 
    "type": "reference",
    "identifier": "b"
   ,
   "out": [
    "A"
   ]
  ,
  "ops": [],
  "line": 15
 ,
  # @c => A,将c的索引赋值给A"type": "step",
  "assignment": 
   "in": 
    "type": "reference",
    "identifier": "c"
   ,
   "out": [
    "A"
   ]
  ,
  "ops": [],
  "line": 16
 ,
  # @d => A,将d的索引赋值给A"type": "step",
  "assignment": 
   "in": 
    "type": "reference",
    "identifier": "d"
   ,
   "out": [
    "A"
   ]
  ,
  "ops": [],
  "line": 17
 ,
  # end:
  "type": "label",
  "identifier": "end",
  "line": 19
 ,
  # 0 => A,B,C,D,E,CTX, SP, PC, GAS, MAXMEM, SR,将这些寄存器清零。
  "type": "step",
  "assignment": 
   "in": 
    "type": "CONST",
    "const": 0
   ,
   "out": [
    "A",
    "B",
    "C",
    "D",
    "E",
    "CTX",
    "SP",
    "PC",
    "GAS",
    "MAXMEM",
    "SR"
   ]
  ,
  "ops": [],
  "line": 20
 ,
  # finalWait:
  "type": "label",
  "identifier": "finalWait",
  "line": 22
 ,
  # $beforeLast()  : JMPN(finalWait)
  "type": "step",
  "assignment": 
   "in": 
    "type": "TAG",
    "tag": "beforeLast()" # 为标签。
   ,
   "out": []
  ,
  "ops": [
   
    "JMPC": 0,
    "JMPN": 1,
    "offset": "finalWait"
   
  ],
  "line": 23
 ,
  # : JMP(start)
  "type": "step",
  "assignment": null,
  "ops": [
   
    "JMP": 1,
    "JMPC": 0,
    "JMPN": 0,
    "offset": "start"
   
  ],
  "line": 25
 ,
  # opINVALID:
  "type": "label",
  "identifier": "opINVALID",
  "line": 26
 
]

然后对以上内容逐行处理:

	for (let i=0; i<lines.length; i++) 
        const l = lines[i];
        ctx.currentLine = l;
        l.fileName = relativeFileName;
        if (l.type == "include") 
            const fullFileNameI = path.resolve(fileDir, l.file);
            await compile(fullFileNameI, ctx);
            if (pendingCommands.length>0) error(l, "command not allowed before include");
            lastLineAllowsCommand = false;
         else if (l.type == "var") 
            if (typeof ctx.vars[l.name] !== "undefined") error(l, `Variable $l.name already defined`);
            if (l.scope == "GLOBAL")  // 给全局变量根据名称分配,不允许有重名情况。
                ctx.vars[l.name] = 
                    scope: "GLOBAL",
                    offset: ctx.lastGlobalVarAssigned + 1
                
                ctx.lastGlobalVarAssigned += l.count; // 适于按数组分配。
             else if (l.scope == "CTX") 
                ctx.vars[l.name] = 
                    scope: "CTX",
                    offset: ctx.lastLocalVarCtxAssigned + 1
                
                ctx.lastLocalVarCtxAssigned += l.count;
             else 
                throw error(l, `Invalid scope $l.scope`);
            
            if (pendingCommands.length>0) error(l, "command not allowed before var");
            lastLineAllowsCommand = false;
         else if (l.type == 'constdef' || l.type == 'constldef' ) 
            const value = evaluateExpression(ctx, l.value);
            let ctype = l.type == 'constldef' ? 'CONSTL':'CONST';
            defineConstant(ctx, l.name, ctype, value);
         else if (l.type == "step")  // start/end等标签下的实际执行语句
            const traceStep =  // traceStep内map:step[key]=op[key]
                // type: "step"
            ;
            try 
                for (let j=0; j< l.ops.length; j++)  //过滤校验下规则,不能同时定义2个assignement。
                    if (!l.ops[j].assignment) continue;
                    if (l.assignment) 
                        error(l, "not allowed assignments with this operation");
                    
                    l.assignment = l.ops[j].assignment;
                    delete l.ops[j].assignment;
                
				
				/*function appendOp(step, op) 
				    Object.keys(op).forEach(function(key) 
				        if (typeof step[key] !== "undefined") throw new Error(`Var $key already defined`);
				        step[key] = op[key];
				    );
				*/
                if (l.assignment)  //处理assignment中的in和out内容。
                    appendOp(traceStep, processAssignmentIn(ctx, l.assignment.in, ctx.out.length));
                    appendOp(traceStep, processAssignmentOut(ctx, l.assignment.out));
                
                for (let j=0; j< l.ops.length; j++)  //将每个ops元素存入step map中。
                    appendOp(traceStep, l.ops[j])
                

                if (traceStep.JMPC && !traceStep.bin) 
                    error(l, "JMPC must go together with a binary op");
                
             catch (err) 
                error(l, err);
            
            // traceStep.lineNum = ctx.out.length;
            traceStep.line = l;
            ctx.out.push(traceStep); //将traceStep放入ctx.out数组中。
            if (pendingCommands.length>0) 
                traceStep.cmdBefore = pendingCommands;
                pendingCommands = [];
            
            lastLineAllowsCommand = !(traceStep.JMP || traceStep.JMPC || traceStep.JMPN);
         else if (l.type == "label")  // start/end等标识符,不允许有重名情况。
            const id = l.identifier
            if (ctx.definedLabels[id]) error(l, `RedefinedLabel: $id` );
            ctx.definedLabels[id] = ctx.out.length;
            if (pend

以上是关于Polygon zkEVM zkASM编译器——zkasmcom的主要内容,如果未能解决你的问题,请参考以下文章

Polygon zkEVM zkASM中的函数集合

Polygon zkEVM zkASM 与 以太坊虚拟机opcode 对应集合

Polygon zkEVM交易解析

Polygon zkEVM PIL编译器——pilcom 代码解析

Polygon zkEVM Prover

Polygon zkEVM网络节点