Polygon zkEVM zkASM语法

1. 引言

前序博客有:

  • Polygon zkEVM中的常量多项式
  • Polygon zkEVM zkASM编译器——zkasmcom

zkASM语法重点参看:

  • https://github.com/0xPolygonHermez/zkasmcom/blob/main/src/zkasm_parser.jison(zkASM Compiler项目)
  • https://github.com/0xPolygonHermez/zkasmcom/blob/main/src/command_parser.jison(zkASM Compiler项目)
  • https://github.com/0xPolygonHermez/zkevm-storage-rom/blob/main/src/zkasm_parser.jison(zkEVM-Storage-Rom项目)
  • https://github.com/0xPolygonHermez/zkevm-storage-rom/blob/main/src/command_parser.jison(zkEVM-Storage-Rom项目)

zkASM程序的基本结构为:

start(或其它任意label名,序号为0。为主业务流程):
	assignment : opcode

; 以分号来表示注释。
; 以上主业务流程处理完之后,必须将相关寄存器清零,以防止重入问题。
; 该模块的作用是给相关寄存器清零,zkevm-proverjs中的sm_main_exec.js中会`checkFinalState`检查。
end(或其它任意label名,但必须在后面的补零操作label之前): 
	0 => A,B,C,D,E,CTX, SP, PC, GAS, MAXMEM, SR

; 补零操作必须紧跟上面的清零操作。
padZeros(因execution trace或者说多项式的degree size是固定的,因此需要做补零操作,补零到倒数第二行):
	notLastTwo : padZerosOp
               : JMP(start) ;  上面之所以补零到倒数第二行,是因为倒数第一行预留给本指令,以实现跳转到主业务流程功能。

; 以上操作将整个execution trace表已填满,后续的都是无效指令。
opInvalid(无效指令,没有意义):

以zkevm-proverjs中的arith.zkasm为例:

start:

        STEP => A
        0 :ASSERT

        0 => A
        CNT_ARITH :ASSERT
        CNT_BINARY :ASSERT
        CNT_KECCAK_F: ASSERT
        CNT_MEM_ALIGN :ASSERT
        CNT_POSEIDON_G :ASSERT
        CNT_PADDING_PG :ASSERT

        0 => A,B,C,D    :ARITH

        CNT_ARITH => A
        1               :ASSERT

        CNT_ARITH => A
        1               :ASSERT

        0x2000000000000000000000000000000000000000000000000000000000000001n => A
        0x100    => B
        0x73     => C
        0x20    => D
        0x173   :ARITH


        2 => A
        CNT_ARITH :ASSERT

        0 => A
        CNT_KECCAK_F: ASSERT
        CNT_MEM_ALIGN :ASSERT
        CNT_POSEIDON_G :ASSERT
        CNT_PADDING_PG :ASSERT

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

finalWait:
        ${beforeLast()}  : JMPN(finalWait)

                         : JMP(start)
opINVALID:

其经zkASM compiler编译后的结果为:【注意,labels中的数值对应上面program数组中的index,其中的JMP/JMPC/JMPN等与offset等配合进行跳转。】

{
 "program": [
  {
   "inSTEP": "1",
   "setA": 1,
   "line": 3,
   "fileName": "arith.zkasm",
   "lineStr": "        STEP => A"
  },
  {
   "CONST": "0",
   "assert": 1,
   "line": 4,
   "fileName": "arith.zkasm",
   "lineStr": "        0 :ASSERT"
  },
  {
   "CONST": "0",
   "setA": 1,
   "line": 6,
   "fileName": "arith.zkasm",
   "lineStr": "        0 => A"
  },
  {
   "inCntArith": "1",
   "assert": 1,
   "line": 7,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_ARITH :ASSERT"
  },
  {
   "inCntBinary": "1",
   "assert": 1,
   "line": 8,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_BINARY :ASSERT"
  },
  {
   "inCntKeccakF": "1",
   "assert": 1,
   "line": 9,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_KECCAK_F: ASSERT"
  },
  {
   "inCntMemAlign": "1",
   "assert": 1,
   "line": 10,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_MEM_ALIGN :ASSERT"
  },
  {
   "inCntPoseidonG": "1",
   "assert": 1,
   "line": 11,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_POSEIDON_G :ASSERT"
  },
  {
   "inCntPaddingPG": "1",
   "assert": 1,
   "line": 12,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_PADDING_PG :ASSERT"
  },
  {
   "CONST": "0",
   "setA": 1,
   "setB": 1,
   "setC": 1,
   "setD": 1,
   "arith": 1,
   "arithEq0": 1,
   "line": 14,
   "fileName": "arith.zkasm",
   "lineStr": "        0 => A,B,C,D    :ARITH"
  },
  {
   "inCntArith": "1",
   "setA": 1,
   "line": 16,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_ARITH => A"
  },
  {
   "CONST": "1",
   "assert": 1,
   "line": 17,
   "fileName": "arith.zkasm",
   "lineStr": "        1               :ASSERT"
  },
  {
   "inCntArith": "1",
   "setA": 1,
   "line": 19,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_ARITH => A"
  },
  {
   "CONST": "1",
   "assert": 1,
   "line": 20,
   "fileName": "arith.zkasm",
   "lineStr": "        1               :ASSERT"
  },
  {
   "CONSTL": "14474011154664524427946373126085988481658748083205070504932198000989141204993",
   "setA": 1,
   "line": 22,
   "fileName": "arith.zkasm",
   "lineStr": "        0x2000000000000000000000000000000000000000000000000000000000000001n => A"
  },
  {
   "CONST": "256",
   "setB": 1,
   "line": 23,
   "fileName": "arith.zkasm",
   "lineStr": "        0x100    => B"
  },
  {
   "CONST": "115",
   "setC": 1,
   "line": 24,
   "fileName": "arith.zkasm",
   "lineStr": "        0x73     => C"
  },
  {
   "CONST": "32",
   "setD": 1,
   "line": 25,
   "fileName": "arith.zkasm",
   "lineStr": "        0x20    => D"
  },
  {
   "CONST": "371",
   "arith": 1,
   "arithEq0": 1,
   "line": 26,
   "fileName": "arith.zkasm",
   "lineStr": "        0x173   :ARITH"
  },
  {
   "CONST": "2",
   "setA": 1,
   "line": 29,
   "fileName": "arith.zkasm",
   "lineStr": "        2 => A"
  },
  {
   "inCntArith": "1",
   "assert": 1,
   "line": 30,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_ARITH :ASSERT"
  },
  {
   "CONST": "0",
   "setA": 1,
   "line": 32,
   "fileName": "arith.zkasm",
   "lineStr": "        0 => A"
  },
  {
   "inCntKeccakF": "1",
   "assert": 1,
   "line": 33,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_KECCAK_F: ASSERT"
  },
  {
   "inCntMemAlign": "1",
   "assert": 1,
   "line": 34,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_MEM_ALIGN :ASSERT"
  },
  {
   "inCntPoseidonG": "1",
   "assert": 1,
   "line": 35,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_POSEIDON_G :ASSERT"
  },
  {
   "inCntPaddingPG": "1",
   "assert": 1,
   "line": 36,
   "fileName": "arith.zkasm",
   "lineStr": "        CNT_PADDING_PG :ASSERT"
  },
  {
   "CONST": "0",
   "setA": 1,
   "setB": 1,
   "setC": 1,
   "setD": 1,
   "setE": 1,
   "setCTX": 1,
   "setSP": 1,
   "setPC": 1,
   "setGAS": 1,
   "setMAXMEM": 1,
   "setSR": 1,
   "line": 39,
   "fileName": "arith.zkasm",
   "lineStr": "       0 => A,B,C,D,E,CTX, SP, PC, GAS, MAXMEM, SR"
  },
  {
   "freeInTag": {
    "op": "functionCall",
    "funcName": "beforeLast",
    "params": []
   },
   "inFREE": "1",
   "JMPC": 0,
   "JMPN": 1,
   "offset": 27,
   "line": 42,
   "offsetLabel": "finalWait",
   "fileName": "arith.zkasm",
   "lineStr": "        ${beforeLast()}  : JMPN(finalWait)"
  },
  {
   "JMP": 1,
   "JMPC": 0,
   "JMPN": 0,
   "offset": 0,
   "line": 44,
   "offsetLabel": "start",
   "fileName": "arith.zkasm",
   "lineStr": "                         : JMP(start)"
  }
 ],
 "labels": {
  "start": 0,
  "end": 26,
  "finalWait": 27,
  "opINVALID": 29
 }
}

1.1 zkASM计数器

zkASM语言支持的计数器counter有:

计数器名 说明 备注
CNT_ARITH { $$ = ‘cntArith’ } 为调用Arithmetic状态机计数器。针对的操作符有:ARITH、ARITH_ECADD_DIFFERENT、ARITH_ECADD_SAME。
CNT_BINARY { $$ = ‘cntBinary’ } 为调用Binary状态机计数器。针对的操作符有:ADD、SUB、LT、SLT、EQ、AND、OR、XOR。
CNT_KECCAK_F { $$ = ‘cntKeccakF’ } 为调用Keccak哈希状态机计数器。针对的操作符有:HASHKDIGEST。计数单位为incCounter
CNT_MEM_ALIGN { $$ = ‘cntMemAlign’ } 为调用Memory Align状态机计数器。针对的操作符有:MEM_ALIGN_RD、MEM_ALIGN_WR、MEM_ALIGN_WR8。
CNT_PADDING_PG { $$ = ‘cntPaddingPG’ } 为调用Poseidon Padding_pg状态机计数器。针对的操作符有:HASHPDIGEST。计数单位为incCounter
CNT_POSEIDON_G { $$ = ‘cntPoseidonG’ } 为调用Poseidon Poseidong状态机计数器。针对的操作符有:HASHDIGEST、SLOAD、SSTORE。计数单位为incCounter

1.2 zkASM scope

zkASM语言支持2种scope

scope标志 说明 备注
GLOBAL
CTX

2. zkASM statement基本类型定义

  • IDENTIFIER格式为:[a-zA-Z_][a-zA-Z$_0-9]* ,表示由字母和数字组成。
  • setLine函数定义为:
    function setLine(dst, first) {
        dst.line = first.first_line;
    }
    
  • NUMBERL:为BigInt,对应CONSTL。
  • NUMBER:为NUMBER,对应CONST。

zkASM语言支持的statement类型有:

statement类型 说明 备注
step {
$$ = $1;
}
label {
$$ = $1;
}
varDef {
$$ = $1;
}
constDef {
$$ = $1;
}
include {
$$ = $1;
}
command {
$$ = $1;
}
LF {
$$ = null;
}

基于statement构建的statementList为:

statementList类型 说明 备注
statmentList statment {
if ($2) $1.push($2);
$$ = $1;
}
statment {
if ($1) {
$$ = [$1];
} else {
$$=[];
}
}

基于statementList构建的allStatments为:

allStatments类型 说明 备注
statmentList EOF {
// console.log($1);
$$ = $1;
return $$;
}

2.1 zkASM statement step类型

zkASM语言支持的statement step类型有:

step标志 说明 备注
assignment ‘:’ opList LF {
$$ = {type: "step", assignment: $1, ops: $3};
setLine($$, @1)
}
assignment LF {
$$ = {type: "step", assignment: $1, ops: []};
setLine($$, @1)
}
‘:’ opList LF {
$$ = {type: "step", assignment: null, ops: $2}
setLine($$, @1)
}

2.2 zkASM statement label类型

zkASM语言支持的statement label类型有:

label标志 说明 备注
IDENTIFIER ‘:’ {
$$ = {type: "label", identifier: $1};
setLine($$, @1)
}

2.3 zkASM statement varDef类型

zkASM语言支持的statement varDef类型有:

varDef标志 说明 备注
VAR scope IDENTIFIER {
$$ = {type: “var”, scope: $2, name: $3, count: 1 }
}
VAR scope IDENTIFIER ‘[’ NUMBER ‘]’ {
$$ = {type: “var”, scope: $2, name: $3, count: $5 }
}

2.4 zkASM statement constDef类型

zkASM语言支持的statement constDef类型有:

constDef标志 说明 备注
‘CONST’ CONSTID ‘=’ nexpr %prec ‘=’ {
$$ = {type: "constdef", name: $2, value: $4}
setLine($$, @1);
}
‘CONSTL’ CONSTID ‘=’ nexpr %prec ‘=’ {
$$ = {type: "constldef", name: $2, value: $4}
setLine($$, @1);
}

其中的nexpr类型有:

nexpr标志 说明 备注
NUMBER {
$$ = {type: ‘CONSTL’ , value: $1}
}
NUMBERL {
$$ = {type: ‘CONSTL’ , value: $1}
}
CONSTID {
$$ = {type: ‘CONSTID’ , identifier: $1}
}
CONSTID ‘??’ nexpr {
$$ = {type: $2, values: [$3] , identifier: $1}
}
nexpr ‘+’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘-’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘*’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘**’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘%’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘/’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
‘-’ nexpr {
$$ = {type: $1, values: [$2]}
}
nexpr ‘<<’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘>>’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘|’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘&’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘^’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘<’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘>’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘<=’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘>=’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘==’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘!=’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘&&’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
nexpr ‘||’ nexpr {
$$ = {type: $2, values: [$1, $3]}
}
‘!’ nexpr {
$$ = {type: $1, values: [$2]}
}
nexpr ‘?’ nexpr ‘:’ nexpr {
$$ = {type: $2, values: [$1, $3, $5]}
}
‘(’ nexpr ‘)’ {
$$ = $2
}

2.5 zkASM statement include类型

zkASM语言支持的statement include类型有:

include标志 说明 备注
INCLUDE STRING {
$$ = {type: “include”, file: $2}
}

2.6 zkASM statement command类型

zkASM语言支持的statement command类型有:

command标志 说明 备注
COMMAND {
$$ = {type: “command”, cmd: $1}
}

2.6.1 zkASM command中的leftExpression

zkASM语言command中支持的leftExpression有:

leftExpression标志 说明 备注
VAR IDENTIFIER {
$$ = {op: “declareVar”, varName: $2}
}
IDENTIFIER {
$$ = {op: “getVar”, varName: $1}
}

2.6.2 zkASM command中的e0

zkASM语言command中支持的e0有:

e0标志 说明 备注
leftExpression {
$$ = $1
}
NUMBER {
$$ = {op: “number”, num: $1 }
}
reg {
$$ = {op: “getReg”, regName: $1}
}
counter {
$$ = {op: “getReg”, regName: $1}
}
‘(’ expression ‘)’ {
$$ = $2;
}
IDENTIFIER ‘.’ IDENTIFIER {
$$ = {op: “getData”, module: $1, offset: $3}
}

2.6.3 zkASM command中的e1

zkASM语言command中支持的e1有:

e1标志 说明 备注
functionCall {
$$ = $1;
}
e0 {
$$ = $1
}

2.6.4 zkASM command中的e2

zkASM语言command中支持的e2有:

e2标志 说明 备注
‘+’ e2 %prec UPLUS {
$$ = $2;
}
‘-’ e2 %prec UMINUS {
$$ = { op: “neg”, values: [$2] };
}
‘~’ e2 %prec NOT {
$$ = { op: “bitnot”, values: [$2] };
}
‘!’ e2 %prec NOT {
$$ = { op: “not”, values: [$2] };
}
e1 %prec EMPTY {
$$ = $1;
}

2.6.5 zkASM command中的e3

zkASM语言command中支持的e3有:

e3标志 说明 备注
e3 ‘*’ e2 {
$$ = { op: “mul”, values: [$1, $3] };
}
e3 ‘/’ e2 {
$$ = { op: “div”, values: [$1, $3] };
}
e3 ‘%’ e2 {
$$ = { op: “mod”, values: [$1, $3] };
}
e3 ‘&’ e2 {
$$ = { op: “bitand”, values: [$1, $3] };
}
e3 ‘|’ e2 {
$$ = { op: “bitor”, values: [$1, $3] };
}
e3 ‘^’ e2 {
$$ = { op: “bitxor”, values: [$1, $3] };
}
e3 SHL e2 {
$$ = { op: “shl”, values: [$1, $3] };
}
e3 SHR e2 {
$$ = { op: “shr”, values: [$1, $3] };
}
e3 L_OR e2 {
$$ = { op: “or”, values: [$1, $3] };
}
e3 L_AND e2 {
$$ = { op: “and”, values: [$1, $3] };
}
e3 EXP e2 {
$$ = { op: “exp”, values: [$1, $3] };
}
e3 EQ e2 {
$$ = { op: “eq”, values: [$1, $3] };
}
e3 NE e2 {
$$ = { op: “ne”, values: [$1, $3] };
}
e3 LT e2 {
$$ = { op: “lt”, values: [$1, $3] };
}
e3 LE e2 {
$$ = { op: “le”, values: [$1, $3] };
}
e3 GT e2 {
$$ = { op: “gt”, values: [$1, $3] };
}
e3 GE e2 {
$$ = { op: “ge”, values: [$1, $3] };
}
e3 ‘?’ e2 ‘:’ e2 {
$$ = { op: “if”, values: [$1, $3, $5] };
}
e2 %prec EMPTY {
$$ = $1;
}

2.6.6 zkASM command中的e4

zkASM语言command中支持的e4有:

e4标志 说明 备注
e4 ‘+’ e3 {
$$ = { op: “add”, values: [$1, $3] };
}
e4 ‘-’ e3 {
$$ = { op: “sub”, values: [$1, $3] };
}
e3 %prec EMPTY {
$$ = $1;
}

2.6.7 zkASM command中的e5

zkASM语言command中支持的e5有:

e5标志 说明 备注
leftExpression ‘=’ e5 {
$$ = { op: “setVar”, values: [$1, $3] };
}
e4 %prec EMPTY {
$$ = $1;
}

2.6.8 zkASM command中的expression

zkASM语言command中支持的expression有:

expression标志 说明 备注
e5 %prec EMPTY {
$$ = $1;
}

基于expression构建的tag为:

tag标志 说明 备注
expression EOF {
// console.log($1);
$$ = $1;
return $$;
}

基于expression构建的expressionList为:

expressionList标志 说明 备注
expressionList ‘,’ expression {
$1.push($3);
}
expression %prec ‘,’ {
$$ = [$1];
}

基于expressionList构建的functionCall为:

functionCall标志 说明 备注
IDENTIFIER ‘(’ expressionList ‘)’ {
$$ = {op: “functionCall”, funcName: $1, params: $3}
}
IDENTIFIER ‘(’ ‘)’ {
$$ = {op: “functionCall”, funcName: $1, params: []}
}

3. zkASM寄存器

zkASM语言支持的寄存器reg有:

寄存器名 说明 备注
A ASSERT操作符仅作用于A寄存器 B : ASSERT,表示断言A寄存器中的值与B寄存器中的值相等
B
C
D
E
SR Storage Merkle tree State Root寄存器 SLOAD和SSTORE会对该寄存器代表的sparse merkle tree进行读写。
CTX
SP Stack pointer
PC Program counter 为实现有条件跳转,引入PC(Program Counter),记录了程序执行的当前指令的位置。
GAS
RR 与zkPC寄存器配合使用,控制子程序跳转和返回。如zkPC+1 => RR :JMP(subroutine) ; 等效为CALL(subroutime):RETURN ; 等效为 :JMP(RR)
zkPC Zero-knowledge program counter
STEP 只读寄存器。number of polynomial evaluation
MAXMEM
HASHPOS
ROTL_C 只读寄存器,仅作用于C寄存器,将其4个字节左移 举例为:
0x101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2Fn => C
ROTL_C => A
0x1415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F10111213n: ASSERT

zkASM语言的寄存器列表regsList表示为:

寄存器列表标志 说明 备注
regsList ‘,’ reg {
$1.push($3)
}
reg {
$$ = [$1]
}

3.1 zkASM 寄存器赋值

zkASM语言支持的寄存器赋值操作assignment有:

assignment标志 说明 备注
inRegsSum ‘=>’ regsList {
$$ = {in: $1, out: $3}
}
inRegsSum {
$$ = {in: $1, out: []} }

其中inRegsSum中支持的运算类型有:

inRegsSum运算标志 说明 备注
inRegsSum ‘+’ inRegP {
$$ = {type: ‘add’, values: [$1, $3]}
}
inRegsSum ‘-’ inRegP {
$$ = {type: ‘sub’, values: [$1, $3]}
}
‘-’ inRegP {
$$ = {type: ‘neg’, values: [$2]}
}
inRegP {
$$ = $1
}

其中inRegP中支持的运算类型有:

inRegP运算标志 说明 备注
inRegP ‘*’ inReg {
$$ = {type: ‘mul’, values: [$1, $3]}
}
inReg {
$$ = $1
}

其中inReg中支持的运算类型有:

inReg运算标志 说明 备注
TAG {
$$ = {type: ‘TAG’ , tag: $1}
}
reg {
$$ = {type: ‘REG’ , reg: $1}
}
counter {
$$ = {type: ‘COUNTER’, counter: $1}
}
NUMBER ‘**’ NUMBER {
$$ = {type: “exp”, values: [$1, $3]}
}
NUMBER {
$$ = {type: ‘CONST’ , const: $1}
}
NUMBERL {
$$ = {type: ‘CONSTL’ , const: $1}
}
CONSTID {
$$ = {type: ‘CONSTID’ , identifier: $1}
}
REFERENCE {
$$ = {type: ‘reference’, identifier: $1}
}

4. zkASM操作符

Rom状态机内包含了如下所有操作符的相应常量多项式。

zkASM语言支持的操作符op有:

操作符名 对应列说明 备注
MLOAD ‘(’ addr ‘)’ {
$$ = $3;
$$.mOp = 1;
$$.mWR = 0;
}
使用Memory状态机。为32-byte word内存读操作。
MSTORE ‘(’ addr ‘)’ {
$$ = $3;
$$.mOp = 1;
$$.mWR = 1;
}
使用Memory状态机。为32-byte word内存写操作。
HASHK ‘(’ hashId ‘)’ {
$$ = $3;
$$.hashK = 1;
}
若hashId为Number,则addr=Number;若为E,则addr=E(0)。将A寄存器中ctx.D[0]个字节附加到 ctx.hashK[addr].data 中ctx.HASHPOS位置之后(若 ctx.hashK[addr].data相应位置有值,则不覆盖,仅在无值位置附加)。若inFree为1,则取ctx.hashK[addr].data中ctx.HASHPOS往后的ctx.D[0]个字节。
HASHKLEN ‘(’ hashId ‘)’ {
$$ = $3;
$$.hashKLen = 1;
}
若hashId为Number,则addr=Number;若为E,则addr=E(0)。HASHKLEN为对ctx.hashK[addr].data进行keccak256哈希运算,结果存在ctx.hashK[addr].digest中。
HASHKDIGEST ‘(’ hashId ‘)’ {
$$ = $3;
$$.hashKDigest = 1;
}
若hashId为Number,则addr=Number;若为E,则addr=E(0)。返回ctx.hashK[addr].digest哈希值。
HASHP ‘(’ hashId ‘)’ {
$$ = $3;
$$.hashP = 1;
}
HASHPLEN ‘(’ hashId ‘)’ {
$$ = $3;
$$.hashPLen = 1;
}
HASHPDIGEST ‘(’ hashId ‘)’ {
$$ = $3;
$$.hashPDigest = 1;
}
JMP ‘(’ IDENTIFIER ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, offset: $3}
}
JMP ‘(’ RR ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, ind: 0, indRR: 1, offset: 0}
}
JMP ‘(’ E ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, ind: 1, indRR: 0, offset: 0}
}
JMP ‘(’ REFERENCE ‘+’ RR ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, ind: 0, indRR: 1, offset: $3}
}
JMP ‘(’ REFERENCE ‘+’ E ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, ind: 1, indRR: 0, offset: $3}
}
JMPC ‘(’ IDENTIFIER ‘)’ {
$$ = {JMPC: 1, JMPN: 0, offset: $3}
}
JMPN ‘(’ IDENTIFIER ‘)’ {
$$ = {JMPC: 0, JMPN: 1, offset: $3}
}
CALL ‘(’ IDENTIFIER ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, offset: $3, assignment: { in: {type: ‘add’, values: [{type: ‘REG’, reg: ‘zkPC’}, {type: ‘CONST’, const: 1}] }, out:[‘RR’]}}
}
CALL(subroutine)等效为zkPC+1 => RR :JMP(subroutine)
CALL ‘(’ REFERENCE ‘+’ RR ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, offset: $3, ind: 0, indRR: 1, assignment: { in: {type: ‘add’, values: [{type: ‘REG’, reg: ‘zkPC’}, {type: ‘CONST’, const: 1}] }, out:[‘RR’]}}
}
CALL ‘(’ REFERENCE ‘+’ E ‘)’ {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, offset: $3, ind: 1, indRR: 0, assignment: { in: {type: ‘add’, values: [{type: ‘REG’, reg: ‘zkPC’}, {type: ‘CONST’, const: 1}] }, out:[‘RR’]}}
}
JMPC ‘(’ RR ‘)’ {
$$ = {JMP: 0, JMPC: 1, JMPN: 0, ind: 0, indRR: 1, offset: 0}
}
JMPC ‘(’ E ‘)’ {
$$ = {JMP: 0, JMPC: 1, JMPN: 0, ind: 1, indRR: 0, offset: 0}
}
JMPC ‘(’ REFERENCE ‘+’ RR ‘)’ {
$$ = {JMP: 0, JMPC: 1, JMPN: 0, ind: 0, indRR: 1, offset: $3}
}
JMPC ‘(’ REFERENCE ‘+’ E ‘)’ {
$$ = {JMP: 0, JMPC: 1, JMPN: 0, ind: 1, indRR: 0, offset: $3}
}
JMPN ‘(’ RR ‘)’ {
$$ = {JMP: 0, JMPC: 0, JMPN: 1, ind: 0, indRR: 1, offset: 0}
}
JMPN ‘(’ E ‘)’ {
$$ = {JMP: 0, JMPC: 0, JMPN: 1, ind: 1, indRR: 0, offset: 0}
}
JMPN ‘(’ REFERENCE ‘+’ RR ‘)’ {
$$ = {JMP: 0, JMPC: 0, JMPN: 1, ind: 0, indRR: 1, offset: $3}
}
JMPN ‘(’ REFERENCE ‘+’ E ‘)’ {
$$ = {JMP: 0, JMPC: 0, JMPN: 1, ind: 1, indRR: 0, offset: $3}
}
RETURN {
$$ = {JMP: 1, JMPC: 0, JMPN: 0, ind: 0, indRR: 1, offset: 0}
}
ASSERT {
$$ = {assert: 1}
}
ECRECOVER {
$$ = {ecRecover: 1}
}
SLOAD {
$$ = {sRD: 1}
}
Storage读操作。读取SMT中,以(A/B)C寄存器值为key的值。
SSTORE {
$$ = {sWR: 1}
}
Storage写操作。将D寄存器的值存储到SMT((A/B)C寄存器值相关的)位置中。若inFree为1,同时还返回写操作之后的smt new root。
ARITH {
$$ = { arith: 1, arithEq0: 1}
}
使用Arithmetic状态机。计算EQ0: x 1 ⋅ y 1 + x 2 − y 2 ⋅ 2 256 − y 3 = 0 x_1\cdot y_1+x_2-y_2\cdot 2^{256}-y_3=0 x1y1+x2y22256y3=0。实际为 A ⋅ B + C = D ⋅ 2 256 + E A\cdot B+C=D\cdot 2^{256}+E AB+C=D2256+E
ARITH_ECADD_DIFFERENT {
$$ = { arith: 1, arithEq1: 1, arithEq3: 1}
}
使用Arithmetic状态机。计算椭圆曲线上不同的2个点之和。
ARITH_ECADD_SAME {
$$ = { arith: 1, arithEq2: 1, arithEq3: 1}
}
使用Arithmetic状态机。计算椭圆曲线上相同的2个点之和。
SHL {
$$ = { shl: 1}
}
SHR {
$$ = { shr: 1}
}
ADD {
$$ = { bin: 1, binOpcode: 0}
}
使用Binary状态机。byte-wise加法运算。
SUB {
$$ = { bin: 1, binOpcode: 1}
}
使用Binary状态机。byte-wise减法运算。
LT {
$$ = { bin: 1, binOpcode: 2}
}
使用Binary状态机。byte-wise小于运算。
SLT {
$$ = { bin: 1, binOpcode: 3}
}
使用Binary状态机。byte-wise有符号小于运算。
EQ {
$$ = { bin: 1, binOpcode: 4}
}
使用Binary状态机。byte-wise等于运算。
AND {
$$ = { bin: 1, binOpcode: 5}
}
使用Binary状态机。bit-wise位与运算。
OR {
$$ = { bin: 1, binOpcode: 6}
}
使用Binary状态机。bit-wise位或运算。
XOR {
$$ = { bin: 1, binOpcode: 7}
}
使用Binary状态机。bit-wise位异或运算。
MEM_ALIGN_RD {
$$ = { memAlign: 1, memAlignWR: 0, memAlignWR8: 0}
}
使用Memory Align状态机。读取指定offset开始的32 byte内存值。
MEM_ALIGN_WR {
$$ = { memAlign: 1, memAlignWR: 1, memAlignWR8: 0}
}
使用Memory Align状态机。写入指定offset开始的32 byte内存值。
MEM_ALIGN_WR8 {
$$ = { memAlign: 1, memAlignWR: 0, memAlignWR8: 1}
}
使用Memory Align状态机。写入指定offset开始的1 byte内存值。
INST_MAP_ROM {
$$ = {instMapRom: 1}
}

其中HASHK/HASHKLEN/HASHKDIGEST/HASHP/HASHPLEN/HASHPDIGEST中的参数hashId的类型有:

hashId取值类型 对应列说明 备注
NUMBER {
$$ = {ind: 0, indRR: 0, offset:$1}
}
E {
$$ = {ind: 1, indRR: 0, offset:0}
}

sm_main_exec.jsexecute函数中的incCounter具体取值为:

  • 1)对于HASHPDIGEST操作符为对56取模:incCounter = Math.ceil((ctx.hashP[addr].data.length + 1) / 56);
  • 2)对于HASHKDIGEST操作符为对136取模:incCounter = Math.ceil((ctx.hashK[addr].data.length + 1) / 136)
  • 3)对于SLOAD和SSTORE操作符为:incCounter = res.proofHashCounter + 2;,其中proofHashCounter为 证明该操作所需的哈希次数:
    const res = await smt.get(sr8to4(ctx.Fr, ctx.SR), key);
    /**
     * Get value merkle-tree
     * @param {Array[Field]} root - merkle-tree root
     * @param {Array[Field]} key - path to retoreve the value
     * @returns {Object} Information about the value to retrieve
     *      {Array[Field]} root: merkle-tree root,
     *      {Array[Field]} key: key to look for,
     *      {Scalar} value: value retrieved,
     *      {Array[Array[Field]]} siblings: array of siblings,
     *      {Bool} isOld0: is new insert or delete,
     *      {Array[Field]} insKey: key found,
     *      {Scalar} insValue: value found,
     *      {Number} proofHashCounter: counter of hashs must be done to proof this operation
     */
    async get(root, key) {
        const self = this;
        const { F } = this;
    
        let r = root;
    
        const keys = self.splitKey(key);
        let level = 0;
    
        const accKey = [];
        let foundKey;
        let siblings = [];
    
        let insKey;
        let insValue = Scalar.e(0);
    
        let value = Scalar.e(0);
        let isOld0 = true;
    
        let foundVal;
    
        while ((!nodeIsZero(r, F)) && (typeof (foundKey) === 'undefined')) {
            siblings[level] = await self.db.getSmtNode(r);
            if (isOneSiblings(siblings[level], F)) {
                const foundValA = (await self.db.getSmtNode(siblings[level].slice(4, 8))).slice(0, 8);
                const foundRKey = siblings[level].slice(0, 4);
                foundVal = fea2scalar(F, foundValA);
                foundKey = this.joinKey(accKey, foundRKey);
            } else {
                r = siblings[level].slice(keys[level] * 4, keys[level] * 4 + 4);
                accKey.push(keys[level]);
                level += 1;
            }
        }
    
        level -= 1;
        accKey.pop();
    
        if (typeof (foundKey) !== 'undefined') {
            if (nodeIsEq(key, foundKey, F)) {
                value = foundVal;
            } else {
                insKey = foundKey;
                insValue = foundVal;
                isOld0 = false;
            }
        }
    
        siblings = siblings.slice(0, level + 1);
    
        return {
            root,
            key,
            value,
            siblings,
            isOld0,
            insKey,
            insValue,
            proofHashCounter: nodeIsZero(root, F) ? 0 : (siblings.length + ((F.isZero(value) && isOld0 !== false) ? 0 : 2)),
        };
    }
    

zkASM操作符列表opList表示为:

操作符列表标志 说明 备注
opList ‘,’ op {
$1.push($3);
$$ = $1
}
op {
$$ = [$1]
}

4.1 Poseidon哈希运算

Polygon zkEVM Poseidon哈希运算相关操作符和寄存器有:

  • 1)寄存器HASHPOS:对应Rom状态机内的setHASHPOS常量多项式。
  • 2)操作符HASHP(hashId):以HASH(E)为例,对应Rom状态机内的hashP、ind、indRR、offset常量多项式。
  • 3)操作符HASHPLEN(hashId):以HASHPLEN(E)为例,对应Rom状态机内的hashPLen、ind、indRR、offset常量多项式。
  • 4)操作符HASHPDIGEST(hashId):以HASHPDIGEST(E)为例,对应Rom状态机内的hashPDigest、ind、indRR、offset常量多项式。

这些多项式之间的逻辑关系见zkevm-proverjs/src/sm/sm_main/sm_main_exec.js中的execute函数:

  • 1)HASHPOS寄存器对应的setHASHPOS常量多项式计算逻辑为:
    // 1. 在每一步中,将incHashPos初始化为0
    let incHashPos = 0;
    // 2. 在当前步中有hashP或hashK时,将D寄存器D[0]值给incHashPos
    const size = fe2n(Fr, ctx.D[0], ctx);
    incHashPos = size;
    const pos = fe2n(Fr, ctx.HASHPOS, ctx);
    console.log('ZYD i:' + i + ', hashP size:' + size + ', pos:' + pos);
    
    if (l.setHASHPOS == 1) {
    	console.log('ZYD i:' + i + ', hashP size:' + size + ', pos:' + pos);
    	pols.setHASHPOS[i]=1n;
        pols.HASHPOS[nexti] = BigInt(fe2n(Fr, op0, ctx) + incHashPos);
    } else {
        pols.setHASHPOS[i]=0n;
        pols.HASHPOS[nexti] = pols.HASHPOS[i] + BigInt( incHashPos);
    }
    
    test/counters/padding_pg.js为例,相应打印日志为:
    **** ZYD i:10, op0:0, incHashPos:0
    ZYD i:12, hashP size:32, pos:0
    ZYD i:14, hashP size:23, pos:32
    **** ZYD i:23, op0:0, incHashPos:0
    ZYD i:25, hashP size:32, pos:0
    ZYD i:27, hashP size:24, pos:32
    **** ZYD i:34, op0:0, incHashPos:0
    ZYD i:36, hashP size:32, pos:0
    ZYD i:38, hashP size:25, pos:32
    **** ZYD i:45, op0:0, incHashPos:0
    ZYD i:47, hashP size:32, pos:0
    ZYD i:48, hashP size:32, pos:32
    ZYD i:49, hashP size:32, pos:64
    ZYD i:51, hashP size:15, pos:96
    **** ZYD i:58, op0:0, incHashPos:0
    ZYD i:60, hashP size:32, pos:0
    ZYD i:61, hashP size:32, pos:32
    ZYD i:62, hashP size:32, pos:64
    ZYD i:64, hashP size:16, pos:96
    **** ZYD i:71, op0:0, incHashPos:0
    ZYD i:73, hashP size:32, pos:0
    ZYD i:74, hashP size:32, pos:32
    ZYD i:75, hashP size:32, pos:64
    ZYD i:77, hashP size:17, pos:96
    **** ZYD i:84, op0:0, incHashPos:0
    ZYD i:86, hashP size:32, pos:0
    ZYD i:87, hashP size:32, pos:32
    ZYD i:88, hashP size:32, pos:64
    ZYD i:90, hashP size:18, pos:96
    **** ZYD i:101, op0:0, incHashPos:0
    

4.2 JMP/CALL/JMPN/JMPC/RETURN有条件跳转

具体参看:

  • zkASM Compiler项目中的c_code_generator.js文件
    	if (rom.program[zkPC].mOp || rom.program[zkPC].mWR || rom.program[zkPC].hashK || rom.program[zkPC].hashKLen || rom.program[zkPC].hashKDigest || rom.program[zkPC].hashP || rom.program[zkPC].hashPLen || rom.program[zkPC].hashPDigest || rom.program[zkPC].JMP || rom.program[zkPC].JMPN || rom.program[zkPC].JMPC)
        {
            let bAddrRel = false;
            let bOffset = false;
            code += "    // If address is involved, load offset into addr\n";
            if (rom.program[zkPC].ind && rom.program[zkPC].indRR)
            {
                console.log("Error: Both ind and indRR are set to 1");
                process.exit();
            }
            if (rom.program[zkPC].ind)
            {
                code += "    fr.toS32(addrRel, pols.E0[" + (bFastMode?"0":"i") + "]);\n";
                bAddrRel = true;
            }
            if (rom.program[zkPC].indRR)
            {
                code += "    fr.toS32(addrRel, pols.RR[" + (bFastMode?"0":"i") + "]);\n";
                bAddrRel = true;
            }
            if (rom.program[zkPC].offset && (rom.program[zkPC].offset != 0))
            {
                bOffset = true;
            }
            if (bAddrRel && bOffset)
            {
                if (rom.program[zkPC].offset > 0)
                {
                code += "    // If offset is possitive, and the sum is too big, fail\n"
                code += "    if ((uint64_t(addrRel)+uint64_t(" + rom.program[zkPC].offset +  "))>=0x10000)\n"
                code += "    {\n"
                code += "        cerr << \"Error: addrRel >= 0x10000 ln: \" << " + zkPC + " << endl;\n"
                code += "        exitProcess();\n"
                code += "    }\n"
                }
                else // offset < 0
                {
                code += "    // If offset is negative, and its modulo is bigger than addrRel, fail\n"
                code += "    if (" + (-rom.program[zkPC].offset) + ">addrRel)\n"
                code += "    {\n"
                code += "        cerr << \"Error: addrRel < 0 ln: \" << " + zkPC + " << endl;\n"
                code += "        exitProcess();\n"
                code += "    }\n"
                }
                code += "    addrRel += " + rom.program[zkPC].offset + ";\n"
                code += "    addr = addrRel;\n\n";
            }
            else if (bAddrRel && !bOffset)
            {
                code += "    addr = addrRel;\n\n"; // TODO: Check that addrRel>=0 and <0x10000, if this is as designed
            }
            else if (!bAddrRel && bOffset)
            {
                if ((rom.program[zkPC].offset < 0) || (rom.program[zkPC].offset >= 0x10000))
                {
                    console.log("Error: invalid offset=" + rom.program[zkPC].offset);
                    program.exit();
                }
                if (!bFastMode)
                    code += "    addrRel = " + rom.program[zkPC].offset + ";\n";
                code += "    addr = " + rom.program[zkPC].offset + ";\n\n";
            }
            else if (!bAddrRel && !bOffset)
            {
                if (!bFastMode)
                    code += "    addrRel = 0;\n";
                code += "    addr = 0;\n\n";
            }
        }
        
    	/*********/
        /* JUMPS */
        /*********/
    
        // If JMPN, jump conditionally if op0<0
        if (rom.program[zkPC].JMPN)
        {
            if (!bFastMode)
                code += "    pols.JMPN[i] = fr.one();\n";
            code += "    fr.toS32(o, op0);\n"
            // If op<0, jump to addr: zkPC'=addr
            code += "    if (o < 0) {\n";
            if (!bFastMode)
            {
                code += "        pols.isNeg[i] = fr.one();\n";
                code += "        pols.zkPC[nexti] = fr.fromU64(addr); // If op<0, jump to addr: zkPC'=addr\n";
                code += "        required.Byte4[0x100000000 + o] = true;\n";
            }
            //code += "        goto *" + functionName + "_labels[addr]; // If op<0, jump to addr: zkPC'=addr\n";
            code += "        bJump = true;\n";
            bConditionalJump = true;
            code += "    }\n";
            // If op>=0, simply increase zkPC'=zkPC+1
            code += "    else\n";
            code += "    {\n";
            if (!bFastMode)
            {
                code += "        pols.zkPC[nexti] = fr.add(pols.zkPC[i], fr.one()); // If op>=0, simply increase zkPC'=zkPC+1\n";
                code += "        required.Byte4[o] = true;\n";
            }
            code += "        addr = " + (zkPC+1) + ";\n";
            //code += "        goto RomLine" + (zkPC+1) + ";\n";
            code += "    }\n";
        }
        // If JMPC, jump conditionally if carry
        else if (rom.program[zkPC].JMPC)
        {
            if (!bFastMode)
                code += "    pols.JMPC[i] = fr.one();\n";
            code += "    if (!fr.isZero(pols.carry[" + (bFastMode?"0":"i") + "]))\n";
            code += "    {\n";
            if (!bFastMode)
                code += "        pols.zkPC[nexti] = fr.fromU64(addr); // If carry, jump to addr: zkPC'=addr\n";
            bConditionalJump = true;
            code += "        bJump = true;\n";
            if (bFastMode) // We reset the global variable to prevent jumping in next zkPC
                code += "        pols.carry[0] = fr.zero();\n";
            code += "    }\n";
            if (!bFastMode)
            {
                code += "    else\n";
                code += "{\n";
                code += "        pols.zkPC[nexti] = fr.add(pols.zkPC[i], fr.one()); // If not carry, simply increase zkPC'=zkPC+1\n";
                code += "}\n";
            }
        }
        // If JMP, directly jump zkPC'=addr
        else if (rom.program[zkPC].JMP)
        {
            if (!bFastMode)
            {
                code += "    pols.zkPC[nexti] = fr.fromU64(addr);\n";
                code += "    pols.JMP[i] = fr.one();\n";
            }
            //code += "    goto *" + functionName + "_labels[addr]; // If JMP, directly jump zkPC'=addr\n";
            bForcedJump = true;
            //code += "    bJump = true;\n";
        }
        // Else, simply increase zkPC'=zkPC+1
        else if (!bFastMode)
        {
            code += "    pols.zkPC[nexti] = fr.add(pols.zkPC[i], fr.one());\n";
        }
    

JMP/CALL/JMPN/JMPC/RETURN 有条件跳转操作符 涉及的 列有:

  • 1)JMP:直接跳转,zkPC’=addr。
    pols.zkPC[nexti] = fr.fromU64(addr);
    pols.JMP[i] = fr.one();
    
  • 2)JMPC:若carry为1,则跳转,zkPC’=addr;否则,不跳转,只是zkPC’=zkPC+1。
    pols.JMPC[i] = fr.one();
    if (!fr.isZero(pols.carry[i]))
    {
        pols.zkPC[nexti] = fr.fromU64(addr); // If carry, jump to addr: zkPC'=addr
        bJump = true;
    }
    else
    {
            pols.zkPC[nexti] = fr.add(pols.zkPC[i], fr.one()); // If not carry, simply increase zkPC'=zkPC+1
    }
    
  • 3)JMPN:若op0<0,则跳转,zkPC’=addr;否则,不跳转,只是zkPC’=zkPC+1。
    pols.JMPN[i] = fr.one();
    fr.toS32(o, op0);
    if (o < 0) {
        pols.isNeg[i] = fr.one();
        pols.zkPC[nexti] = fr.fromU64(addr); // If op<0, jump to addr: zkPC'=addr
        required.Byte4[0x100000000 + o] = true;
        bJump = true;
    }
    else
    {
        pols.zkPC[nexti] = fr.add(pols.zkPC[i], fr.one()); // If op>=0, simply increase zkPC'=zkPC+1
        required.Byte4[o] = true;
        addr = 56;
    }
    
  • 4)ind:ind和indRR二者只能有一个为1,不能同时设置为1。ind是从E0中加载地址。若offset的值为整数,则实际地址为E0地址加1;若offset值为负数,则实际地址为E0地址加offset值。
    // If address is involved, load offset into addr
    fr.toS32(addrRel, pols.E0[i]);
    // If offset is possitive, and the sum is too big, fail
    if ((uint64_t(addrRel)+uint64_t(1))>=0x10000)
    {
        cerr << "Error: addrRel >= 0x10000 ln: " << 1415 << endl;
        exitProcess();
    }
    addrRel += 1;
    addr = addrRel;
    
  • 5)indRR:ind和indRR二者只能有一个为1,不能同时设置为1。indRR是从RR中加载地址。若offset的值为整数,则实际地址为E0地址加313;若offset值为负数,则实际地址为E0地址加offset值。
    // If address is involved, load offset into addr
    fr.toS32(addrRel, pols.RR[i]);
    // If offset is possitive, and the sum is too big, fail
    if ((uint64_t(addrRel)+uint64_t(313))>=0x10000)
    {
        cerr << "Error: addrRel >= 0x10000 ln: " << 1203 << endl;
        exitProcess();
    }
    addrRel += 313;
    addr = addrRel;
    
  • 6)offset:当ind和indRR均为0时,则实际地址为offset值:
    		else if (!bAddrRel && bOffset)
            {
                if ((rom.program[zkPC].offset < 0) || (rom.program[zkPC].offset >= 0x10000))
                {
                    console.log("Error: invalid offset=" + rom.program[zkPC].offset);
                    program.exit();
                }
                if (!bFastMode)
                    code += "    addrRel = " + rom.program[zkPC].offset + ";\n";
                code += "    addr = " + rom.program[zkPC].offset + ";\n\n";
            }
    
  • 7)assignment:仅CALL操作符涉及。如assignment: { in: {type: ‘add’, values: [{type: ‘REG’, reg: ‘zkPC’}, {type: ‘CONST’, const: 1}] }, out:[‘RR’]},表示zkPC+1 => RR

其中ind、indRR、offset之间对实际地址的影响关系,也可参见zkevm-proverjs sm_main_exec.js中的:

		let addrRel = 0;
        let addr = 0;
        if (l.mOp || l.JMP || l.JMPN || l.JMPC ||  l.hashP || l.hashPLen || l.hashPDigest ||  l.hashK || l.hashKLen || l.hashKDigest || l.JMP || l.JMPC) {
            if (l.ind) {
                addrRel = fe2n(Fr, ctx.E[0], ctx);
            }
            if (l.indRR) {
                addrRel += fe2n(Fr, ctx.RR, ctx);
            }
            if (l.offset) addrRel += l.offset;
            if (addrRel >= 0x10000) throw new Error(`Address too big: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`);
            if (addrRel <0 ) throw new Error(`Address can not be negative: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`);
            addr = addrRel;
        }

5. zkASM 地址运算

zkASM相关地址运算addr有:【基于SP和PC等寄存器】

地址运算 对应列说明 备注
SP {
$$ = { isStack: 1, isCode: 0, isMem:0, ind:0, indRR: 0, incCode:0, incStack:0, offset: 0, useCTX: 1}
}
SP ‘+’ NUMBER {
$$ = { isStack: 1, isCode: 0, isMem:0, ind:0, indRR: 0, incCode:0, incStack: 0, offset: $3, useCTX: 1}}
}
SP ‘-’ NUMBER {
$$ = { isStack: 1, isCode: 0, isMem:0, ind:0, indRR: 0, incCode:0, incStack: 0, offset: -$3, useCTX: 1}}
}
SP ‘++’ {
$$ = { isStack: 1, isCode: 0, isMem:0, ind:0, indRR: 0, incStack: 1, offset: 0, useCTX: 1}}
}
SP ‘–’ {
$$ = { isStack: 1, isCode: 0, isMem:0, ind:0, indRR: 0, incCode:0, incStack: -1, offset: 0, useCTX: 1}}
}
PC {
$$ = { isStack: 0, isCode: 1, isMem:0, ind:0, indRR: 0, incCode:0, incStack: 0, offset: 0, useCTX: 1}
}
PC ‘+’ NUMBER {
$$ = { isStack: 0, isCode: 1, isMem:0, ind:0, indRR: 0, incCode:0, incStack: 0, offset: $3, useCTX: 1}
}
PC ‘-’ NUMBER {
$$ = { isStack: 0, isCode: 1, isMem:0, ind:0, indRR: 0, incCode:0, incStack: 0, offset: -$3, useCTX: 1}
}
PC ‘++’ {
$$ = { isStack: 0, isCode: 1, isMem:0, ind:0, indRR: 0, incCode:1, incStack: 0, offset: 0, useCTX: 1}
}
PC ‘–’ {
$$ = { isStack: 0, isCode: 1, isMem:0, ind:0, indRR: 0, incCode:-1, incStack: 0, offset: 0, useCTX: 1}
}
SYS ‘:’ E ‘+’ NUMBER {
$$ = { isStack: 0, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: $5}
}
SYS ‘:’ E ‘-’ NUMBER {
$$ = { isStack: 0, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: -$5}
}
SYS ‘:’ E {
$$ = { isStack: 0, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: 0}
}
MEM ‘:’ E ‘+’ NUMBER {
$$ = { isStack: 0, isMem: 1, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: $5, useCTX: 1}
}
MEM ‘:’ E ‘-’ NUMBER {
$$ = { isStack: 0, isMem: 1, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: -$5, useCTX: 1}
}
MEM ‘:’ E {
$$ = { isStack: 0, isMem: 1, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: 0, useCTX: 1}
}
CODE ‘:’ E ‘+’ NUMBER {
$$ = { isStack: 0, isCode: 1, ind:1, indRR: 0, incCode:0, incStack: 0, offset: $5, useCTX: 1}
}
CODE ‘:’ E ‘-’ NUMBER {
$$ = { isStack: 0, isCode: 1, ind:1, indRR: 0, incCode:0, incStack: 0, offset: -$5, useCTX: 1}
}
CODE ‘:’ E {
$$ = { isStack: 0, isCode: 1, ind:1, indRR: 0, incCode:0, incStack: 0, offset: 0, useCTX: 1}
}
STACK ‘:’ E ‘+’ NUMBER {
$$ = { isStack: 1, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: $5, useCTX: 1}
}
STACK ‘:’ E ‘-’ NUMBER {
$$ = { isStack: 1, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: -$5, useCTX: 1}
}
STACK ‘:’ E {
$$ = { isStack: 1, isCode: 0, ind:1, indRR: 0, incCode:0, incStack: 0, offset: 0, useCTX: 1}
}
IDENTIFIER {
$$ = { offset: $1 }
}
IDENTIFIER ‘+’ RR {
$$ = { offset: $1, ind: 0, indRR: 1 }
}
IDENTIFIER ‘+’ E {
$$ = { offset: $1, ind: 1, indRR: 0 }
}

6. zkEVM Storage Rom状态机中的zkASM语法

zkEVM Storage Rom状态机中的zkASM语法,详细见:

  • https://github.com/0xPolygonHermez/zkevm-storage-rom/blob/main/src/zkasm_parser.jison(zkEVM-Storage-Rom项目)
  • https://github.com/0xPolygonHermez/zkevm-storage-rom/blob/main/src/command_parser.jison(zkEVM-Storage-Rom项目)

大多数语法与zkASM Compiler项目中的一致,本章仅罗列不同之处。

6.1 Storage Rom中的zkASM寄存器

Storage Rom中的zkASM寄存器reg有:

寄存器名 说明 备注
HASH_LEFT
HASH_RIGHT
OLD_ROOT
NEW_ROOT
VALUE_LOW
VALUE_HIGH
SIBLING_VALUE_HASH
RKEY
SIBLING_RKEY
RKEY_BIT
LEVEL
PC
ROTL_VH

6.2 Storage Rom中的zkASM操作符

Storage Rom中的zkASM操作符op有:

操作符名 对应列说明 备注
JMP ‘(’ IDENTIFIER ‘)’ {
$$ = {iJmp: 1n, addressLabel: $3}
}
JMPZ ‘(’ IDENTIFIER ‘)’ {
$$ = {iJmpz: 1n, addressLabel: $3}
}
有条件跳转。即JUMP Zero,是指当A寄存器的前一状态为0时,跳转到指定位置。
HASH0 {
$$ = {iHash: 1n, iHashType: 0}
}
HASH1 {
$$ = {iHash: 1n, iHashType: 1}
}
LATCH_SET {
$$ = {iLatchSet: 1n}
}
LATCH_GET {
$$ = {iLatchGet: 1n}
}
CLIMB_RKEY {
$$ = {iClimbRkey: 1n}
}
CLIMB_SIBLING_RKEY {
$$ = { iClimbSiblingRkey: 1n}
}
CLIMB_SIBLING_RKEY_N {
$$ = { iClimbSiblingRkeyN: 1n}
}
ROTATE_LEVEL {
$$ = { iRotateLevel: 1n}
}

参考资料

[1] zkASM Basic Syntax

附录:Polygon Hermez 2.0 zkEVM系列博客

  • ZK-Rollups工作原理
  • Polygon zkEVM——Hermez 2.0简介
  • Polygon zkEVM网络节点
  • Polygon zkEVM 基本概念
  • Polygon zkEVM Prover
  • Polygon zkEVM工具——PIL和CIRCOM
  • Polygon zkEVM节点代码解析
  • Polygon zkEVM的pil-stark Fibonacci状态机初体验
  • Polygon zkEVM的pil-stark Fibonacci状态机代码解析
  • Polygon zkEVM PIL编译器——pilcom 代码解析
  • Polygon zkEVM Arithmetic状态机
  • Polygon zkEVM中的常量多项式
  • Polygon zkEVM Binary状态机
  • Polygon zkEVM Memory状态机
  • Polygon zkEVM Memory Align状态机
  • Polygon zkEVM zkASM编译器——zkasmcom
  • Polygon zkEVM哈希状态机——Keccak-256和Poseidon

你可能感兴趣的:(zkVM,零知识证明)