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 | { $$ = {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 x1⋅y1+x2−y2⋅2256−y3=0。实际为 A ⋅ B + C = D ⋅ 2 256 + E A\cdot B+C=D\cdot 2^{256}+E A⋅B+C=D⋅2256+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.js中execute函数中的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关注打赏
