前序博客有:
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(无效指令,没有意义):
zkASM通过${ExecutorMethod(params)} => A :JMP(param)
,即$和大括号来表示函数调用。
Polygon zkEVM在各状态机中实现了一系列的zkASM函数,本文重点关注如下代码库中的相关zkASM函数:
zkevm-proverjs项目sm_main_exec.js文件中支持的函数调用有:
函数名 | 函数实现 | 说明 |
---|---|---|
beforeLast | function eval_beforeLast(ctx) { if (ctx.step >= ctx.N-2) { return [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]; } else { return [ctx.Fr.negone, 0n, 0n, 0n, 0n, 0n, 0n, 0n]; //不是倒数第二行,直接返回负值。 } } |
在主业务流程和最后清零操作之后,进行补零到execution trace表的倒数第二行。通常的调用方式类似为:【当beforeLast()返回负值时,会持续调用JMPN。】finalWait: ${beforeLast()} : JMPN(finalWait) : JMP(start) |
getGlobalHash | 本质上ctx.globalHahs=Keccak256(oldStateRoot| oldLocalExitRoot|newStateRoot |newLocalExitRoot|batchHashData |numBatch|timestamp), 其中batchHashData=Keccak256(batchL2Data|globalExitRoot|sequencerAddr), 这些参数均来自inputs-executor/*.json文件,均以16进制256bit表示,不足的前方补零。 |
将ctx.globalHash值转换为8个32bit field elements表示。 |
getOldStateRoot | 为inputs-executor/*.json文件中的oldStateRoot。 | 将ctx.input.oldStateRoot值转换为8个32bit field elements表示。 |
getNewStateRoot | 为inputs-executor/*.json文件中的newStateRoot。 | 将ctx.input.newStateRoot值转换为8个32bit field elements表示。 |
getSequencerAddr | 为inputs-executor/*.json文件中的sequencerAddr。 | 将ctx.input.sequencerAddr值转换为8个32bit field elements表示。 |
getOldLocalExitRoot | 为inputs-executor/*.json文件中的oldLocalExitRoot。 | 将ctx.input.oldLocalExitRoot值转换为8个32bit field elements表示。 |
getNewLocalExitRoot | 为inputs-executor/*.json文件中的newLocalExitRoot。 | 将ctx.input.newLocalExitRoot值转换为8个32bit field elements表示。 |
getNumBatch | 为inputs-executor/*.json文件中的numBatch。 | 返回结果为[ctx.Fr.e(ctx.input.numBatch), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; |
getTimestamp | 为inputs-executor/*.json文件中的timestamp。 | 返回结果为[ctx.Fr.e(ctx.input.timestamp), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; |
getBatchHashData | batchHashData=Keccak256(batchL2Data|globalExitRoot|sequencerAddr), 这些参数均来自inputs-executor/*.json文件,均以16进制256bit表示,不足的前方补零。 |
将ctx.input.batchHashData值转换为8个32bit field elements表示。 |
getGlobalExitRoot | 为inputs-executor/*.json文件中的globalExitRoot。 | 将ctx.input.globalExitRoot值转换为8个32bit field elements表示。 |
getTxs | function eval_getTxs(ctx, tag) { if (tag.params.length != 2) throw new Error(“Invalid number of parameters function …”); const txs = ctx.input.batchL2Data; const offset = Number(evalCommand(ctx,tag.params[0])); const len = Number(evalCommand(ctx,tag.params[1])); let d = “0x” + txs.slice(2+offset*2, 2+offset*2 + len*2); if (d.length == 2) d = d+‘0’; return scalar2fea(ctx.Fr, Scalar.e(d)); } |
从ctx.input.batchL2Data中截取特定长度值,转换为8个32bit field elements表示。 |
getTxsLen | 为:(ctx.input.batchL2Data.length-2) / 2 | 返回的结果为[ctx.Fr.e((ctx.input.batchL2Data.length-2) / 2), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero] |
eventLog | 返回为全零值:[ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; |
|
cond | 有条件返回,若evalCommand结果为true,则返回-1;否则返回0。 | |
inverseFpEc | 输入为a,若a为0,抛异常;否则返回 a − 1 a^{-1} a−1 | |
inverseFnEc | 输入为a,若a为0,抛异常;否则返回 a − 1 a^{-1} a−1 | |
sqrtFpEc | 输入为a,返回 a \sqrt{a} a | |
dumpRegs | 打印A/B/C/D/E寄存器中的值,并返回0。 | |
dump | 打印输入信息,并返回0。 | |
dumphex | 以16进制打印输入信息,并返回0。 | |
xAddPointEc | 取不同点求和后的x坐标。 | |
yAddPointEc | 取不同点求和后的y坐标。 | |
xDblPointEc | 取相同点求和后的x坐标。 | |
yDblPointEc | 取相同点求和后的y坐标。 | |
test** | 对应test_tools.js中的相关函数 | |
getBytecode | evalCommand获得hashContract=》从ctx.input.contractsBytecode中获得bytecode,从bytecode中截取特定长度值,转换为8个32bit field elements表示。 | |
touchedAddress | 若地址对应为某预编译合约,则考虑warm access,直接返回0。若该地址在ctx.input.touchedAddress数组之中,则返回0;否则将addr放入ctx.input.touchedAddress中并返回1。 | |
touchedStorageSlots | 输入为addr和key,若addr已在ctx.input.touchedStorageSlots中,则返回0;否则将{addr,key}存入ctx.input.touchedStorageSlots中,并返回1。 | |
**bitwise** | 输入为a,b,根据具体的函数名进行add/or/xor/not运算。 | |
**comp** | 输入为a,b,根据具体的函数名进行lt/gt/eq运算。 | |
loadScalar | 读取相应参数中的值并处理 | |
log | 输入为frLog和label,打印frLog等信息。返回0。 | |
resetTouchedAddress | 将ctx.input.touchedAddress置空,返回0。 | |
resetStorageSlots | 将ctx.input.touchedStorageSlots置空,并返回0。 | |
exp | 输入为a,b,将 a b a^b ab结果转换为8个32bit field elements表示。 | |
storeLog | 输入为indexLog、isTopic、data,若ctx.outLogs[indexLog]未定义,若isTopic为true,则将data以十六进制形式存入ctx.outLogs[indexLog].topics中;否则将data以十六进制形式存入ctx.outLogs[indexLog].data中。返回0。 | |
**precompiled** | ||
break | 打印断点信息,返回0。 | |
memAlignWR_W0 | 输入为m0,value,offset,进行处理后将结果以8个32bit field elements表示。 | |
memAlignWR_W1 | 输入为m1,value,offset,进行处理后将结果以8个32bit field elements表示。 | |
memAlignWR8_W0 | 输入为m0,value,offset,取bits=(31-offset)*8,进行处理后将结果以8个32bit field elements表示。 | |
saveContractBytecode | 输入为addr,将ctx.hashP[addr].data 存入 ctx.input.contractsBytecode[ctx.hashP[addr].digest] 中,并返回0。 |
zkevm-proverjs项目sm_storage.js文件中的函数调用有:【对应为Storage状态机的action。Storage状态机的输入和输出状态实际上是SMT(Sparse Merkle Tree),所以相应的action也是SMT set】
函数名 | 函数实现 | 说明 |
---|---|---|
isSetUpdate | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "update") 为true,则设置op[0]=1。update existing value |
|
isSetInsertFound | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "insertFound") 为true,则设置op[0]=1。insert with found key; found a leaf node with a common set of key bits |
|
isSetInsertNotFound | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "insertNotFound") 为true,则设置op[0]=1。insert with no found key |
|
isSetReplacingZero | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "insertNotFound") 为true,则设置op[0]=1。替换为0 |
|
isSetDeleteLast | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "deleteLast") 为true,则设置op[0]=1。delete the last node, so root becomes 0 |
|
isSetDeleteFound | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "deleteFound") 为true,则设置op[0]=1。delete with found key |
|
isSetDeleteNotFound | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "deleteNotFound") 为true,则设置op[0]=1。delete with no found key |
|
isSetZeroToZero | 若(!actionListEmpty && action[a].bIsSet && action[a].setResult.mode == "zeroToZero") 为true,则设置op[0]=1。value was zero and remains zero |
|
GetIsOld0 | 若!actionListEmpty && (action[a].bIsSet ? action[a].setResult.isOld0 : action[a].getResult.isOld0) 为true,则设置op[0]=1。can be a final leaf (isOld0=true)。 |
|
isGet | 若(!actionListEmpty && !action[a].bIsSet) 为true,则设置op[0]=1。若key not found,则返回0;否则,返回非零值。 |
|
GetRkey | 设置op[0/1/2/3]=ctx.rkey[0/1/2/3]。Get the remaining key, i.e. the key after removing the bits used in the tree node navigation。 | |
GetSiblingRkey | 设置op[0/1/2/3]=ctx.rkey[0/1/2/3]。Get the sibling remaining key, i.e. the part that is not common to the value key。 | |
GetSiblingHash | 若action[a].bIsSet为true对应setResult,否则为getResult,相应设置op[0/1/2/3]=action[a].set/getResult.siblings[ctx.currentLevel][(1n-ctx.bits[ctx.currentLevel])*4n+0/1/2/3n] 。Get the sibling hash, obtained from the siblings array of the current level, taking into account that the sibling bit is the opposite (1-x) of the value bit。 |
|
GetValueLow | 取action的getResult.value或setResult.newValue值,将该值的lower 4 elements赋值为op[0/1/2/3]。Value is an u256 split in 8 u32 chuncks, each one stored in the lower 32 bits of an u63 field element。u63 means that it is not an u64, since some of the possible values are lost due to the prime effect。 | |
GetValueHigh | 取action的getResult.value或setResult.newValue值,将该值的higher 4 elements赋值为op[0/1/2/3]。 | |
GetSiblingValueLow | 取action的getResult.insValue或setResult.insValue值,将该值的lower 4 elements赋值为op[0/1/2/3]。 | |
GetSiblingValueHigh | 取action的getResult.insValue或setResult.insValue值,将该值的higher 4 elements赋值为op[0/1/2/3]。 | |
GetOldValueLow | 取action的setResult.oldValue值,将该值的lower 4 elements赋值为op[0/1/2/3]。 | |
GetOldValueHigh | 取action的setResult.oldValue值,将该值的higher 4 elements赋值为op[0/1/2/3]。 | |
GetLevelBit | 输入为单个bit,若( ctx.level & (1< |
|
GetTopTree | 仅当到达the top of the tree(即ctx.currentLevel=0),返回0。 | |
GetTopOfBranch | Returns 0 if we reached the top of the branch, i.e. if the level matches the siblings size。 | |
GetNextKeyBit | Get the next key bit。该调用会自动减少the current level。 | |
isAlmostEndPolynomial | if (i == (polSize-2)) { op[0] = fr.one; } |
与前面的beforeLast 函数功能类似。在主业务流程和最后清零操作之后,进行补零到execution trace表的倒数第二行。通常的调用方式类似为:【仅当为倒数第二行时,isAlmostEndPolynomial才返回1,否则返回均为0值。若返回0值,则持续调用NotEndPol。】 NotEndPol: ${isAlmostEndPolynomial()} :JMPZ(NotEndPol) :JMP(Run) ;为execution trace的最后一行。 |