前序博客有:
Polygon zkEVM zkROM代码库为:
根据zkEVM Audit Education Sessions 1/4 -Circuit Arithmetization for ZKP有:
相关zkASM指令有:
zkROM的基本流程为:
zkROM第一步为加载输入变量:
;;;;;;;;;;;;;;;;;;
;; A - Load input variabales
;;;;;;;;;;;;;;;;;;
; 加载ctx.globalHash值到globalHash全局变量中
; globalHash = H_keccak(oldStateRoot | oldLocalExitRoot | newStateRoot | newLocalExitRoot | batchHashData | numBatch | timestamp )
${getGlobalHash()} :MSTORE(globalHash)
STEP => A
1 :ASSERT ; Ensure it is the begining of the execution
; 加载ctx.input.globalExitRoot值到globalExitRoot全局变量中
; globalExitRoot对应为Global exit-tree root
${getGlobalExitRoot()} :MSTORE(globalExitRoot)
; 加载ctx.input.oldStateRoot值到oldStateRoot全局变量中
; oldStateRoot对应为Previous state-tree root
${getOldStateRoot()} :MSTORE(oldStateRoot)
; 加载ctx.input.oldLocalExitRoot值到oldLocalExitRoot全局变量中
; oldLocalExitRoot对应为Previous local exit-tree root
${getOldLocalExitRoot()} :MSTORE(oldLocalExitRoot)
; 加载ctx.input.sequencerAddr值到sequencerAddr全局变量中
; sequencerAddr对应为Coinbase address which will receive the fees
${getSequencerAddr()} :MSTORE(sequencerAddr)
; 加载ctx.input.batchHashData值到batchHashData全局变量中
; batchHashData = H_keccak( transactions(即batchL2Data) | globalExitRoot | sequencerAddr )
${getBatchHashData()} :MSTORE(batchHashData)
; 加载ctx.input.numBatch值到numBatch全局变量中
; numBatch对应为Current batch to process
${getNumBatch()} :MSTORE(numBatch)
; 加载ctx.input.timestamp值到timestamp全局变量中
; timestamp对应为Current batch timestamp
${getTimestamp()} :MSTORE(timestamp)
; 加载((ctx.input.batchL2Data.length-2) / 2)值到batchL2DataLength全局变量中
; batchL2DataLength对应为Transactions bytes read from the input
${getTxsLen()} :MSTORE(batchL2DataLength)
; Fill globalHash: oldStateRoot & oldLocalExitRoot
32 => D ; HASHK或HASHKE的左侧长度为D,HASHKLEN/HASHPLEN的增量为D。
; 将oldStateRoot全局变量值加载到A寄存器中,此处实际为ctx.input.oldStateRoot值
$ => A :MLOAD(oldStateRoot)
; H_keccak(oldStateRoot |
A :HASHK(0) ; GlobalHash address is 0
A => SR ;SR寄存器为Storage状态机sparse merkle tree state root
; 将oldLocalExitRoot全局变量值加载到A寄存器中,此处实际为ctx.input.oldLocalExitRoot值
$ => A :MLOAD(oldLocalExitRoot)
; H_keccak(oldStateRoot | oldLocalExitRoot |
A :HASHK(0)
; HASHPOS寄存器存储的为H_keccak函数的输入拼接字符串的最后位置
HASHPOS :MSTORE(oldHashPos) ; Save globalHash data position
zkROM第二步为设置batch storage state-tree,其又分为2步来实现:
;;;;;;;;;;;;;;;;;;
;; B - Set batch storage state-tree: batchHash (oldStateRoot) & globalExitRoot
;;;;;;;;;;;;;;;;;;
;; Set batch hash
; 将HASHPOS重置为0,表示将启动新的哈希运算
0 => HASHPOS ; A new hash with position 0 is started
; lastHashKIdUsed全局变量对应为Last hash address used
; 加载lastHashKIdUsed全局变量值到E寄存器中
$ => E :MLOAD(lastHashKIdUsed)
; 将E寄存器值+1,并更新到lastHashKIdUsed全局变量中
E+1 => E :MSTORE(lastHashKIdUsed)
; numBatch全局变量对应为Current batch to process
; 加载numBatch全局变量值到A寄存器中
$ => A :MLOAD(numBatch)
; H_keccak( (numBatch-1) |
A - 1 :HASHK(E)
; 常量值 CONST %STATE_ROOT_STORAGE_POS = 0
%STATE_ROOT_STORAGE_POS :HASHK(E) ; Storage position of the batch hash
; H_keccak( (numBatch-1) | %STATE_ROOT_STORAGE_POS
HASHPOS :HASHKLEN(E)
; 校验计数器
%MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH - 2:JMPN(outOfCounters)
; 将H_keccak( (numBatch-1) | %STATE_ROOT_STORAGE_POS)哈希结果给C寄存器
$ => C :HASHKDIGEST(E)
; 打印日志,表示即将启动a batch process
${eventLog(onStartBatch, C)}
; 常量值CONSTL %ADDRESS_SYSTEM = 0x0000000000000000000000000000000000000000n
%ADDRESS_SYSTEM => A ; 系统地址默认为0地址,加载到A寄存器
; 常量值CONST %SMT_KEY_SC_STORAGE = 3,为智能合约STORAGE在SMT中的KEY
%SMT_KEY_SC_STORAGE => B ;
; 加载oldStateRoot全局变量值加载到D寄存器中,此处实际为ctx.input.oldStateRoot值
$ => D :MLOAD(oldStateRoot)
; 将D寄存器值存入SMT中(C寄存器值)位置处。
; 即将oldStateRoot存入Stoarge 'keccak256(numBatch - 1, 0)'位置中,
; 将更新的SMT root存入SR寄存器中。
$ => SR :SSTORE ; Store 'oldStateRoot' in storage position 'keccak256(numBatch - 1, 0)'
;; Set global exit root
; 将HASHPOS重置为0,表示将启动新的哈希运算
0 => HASHPOS
; 加载lastHashKIdUsed全局变量值到E寄存器中
$ => E :MLOAD(lastHashKIdUsed)
; 将E寄存器值+1,并更新到lastHashKIdUsed全局变量中
E+1 => E :MSTORE(lastHashKIdUsed)
; 设置D寄存器值为32
32 => D
; 加载globalExitRoot全局变量值到A寄存器中。; Global exit-tree root
$ => A :MLOAD(globalExitRoot)
; H_keccak( globalExitRoot |
A :HASHK(E)
; CONST %GLOBAL_EXIT_ROOT_STORAGE_POS = 0
; H_keccak( globalExitRoot | 0 |
%GLOBAL_EXIT_ROOT_STORAGE_POS :HASHK(E) ; Storage position of the global exit root map
; HASHPOS寄存器存储的为H_keccak函数的输入拼接字符串的最后位置
HASHPOS :HASHKLEN(E)
; 计数器校验
%MAX_CNT_KECCAK_F - CNT_KECCAK_F - %MIN_CNT_KECCAK_BATCH - 2:JMPN(outOfCounters)
; 计算H_keccak( globalExitRoot | 0) 哈希结果给C寄存器。
$ => C :HASHKDIGEST(E)
; 将`CONSTL %ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 = 0xAE4bB80bE56B819606589DE61d5ec3b522EEB032n`给A寄存器
%ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 => A
; 将`CONST %SMT_KEY_SC_STORAGE = 3`给B寄存器
%SMT_KEY_SC_STORAGE => B
; 加载numBatch全局变量值到D寄存器中 ; Current batch to process
$ => D :MLOAD(numBatch)
; 将D寄存器值存入SMT中(C寄存器值)位置处。
; 即将numBatch存入Stoarge 'keccak256(globalExitRoot, 0)'位置中,
; 将更新的SMT root存入SR寄存器中。
$ => SR :SSTORE ; Store 'numBatch' in storage position 'keccak256(globalExitRoot, 0)'
; 将SR寄存器值(更新的SMT root)存入全局变量batchSR中
; batchSR表示State root before processing any transaction
SR :MSTORE(batchSR)
zkROM第三步为循环解析RLP交易:
;;;;;;;;;;;;;;;;;;
;; C - Loop parsing RLP transactions
;; - Load transaction RLP data and ensure it has correct RLP encoding
;; - If an error is found in any transaction, the batch will not process any transaction
;;;;;;;;;;;;;;;;;;
; 将E寄存器值+1,并更新到lastHashKIdUsed全局变量中
; lastHashKIdUsed表示Last hash address used
E+1 => E :MSTORE(lastHashKIdUsed)
; 将batchHashPos全局变量设置为0
0 :MSTORE(batchHashPos)
; batchHashDataId表示hash address used when adding bytes to batchHashData
; 将lastHashKIdUsed全局变量值 给 batchHashDataId全局变量
E :MSTORE(batchHashDataId)
; lastTxHashId表示First hash address to be used when processing transactions
; 将lastHashKIdUsed全局变量值 给 lastTxHashId全局变量
E :MSTORE(lastTxHashId) ; Points at first hash address to be used when processing transactions
; lastCtxUsed表示Last context that has been used
; 加载lastCtxUsed全局变量值给A寄存器
$ => A :MLOAD(lastCtxUsed)
; ctxTxToUse表示First context to be used when processing transactions
; 将lastCtxUsed全局变量值 给 ctxTxToUse全局变量
A :MSTORE(ctxTxToUse) ; Points at first context to be used when processing transactions
; 表示内联javascript指令注入【在addHashTx中用作index,表示从ctx.input.batchL2Data的哪个位置开始读取】
$${var p = 0}
txLoopRLP:
; 加载lastCtxUsed全局变量值给A寄存器
$ => A :MLOAD(lastCtxUsed)
; 更新lastCtxUsed加1
; lastCtxUsed表示 Last context that has been used
A+1 => CTX :MSTORE(lastCtxUsed)
; batchL2DataLength表示Transactions bytes read from the input
; 加载batchL2DataLength全局变量值 给 A寄存器
$ => A :MLOAD(batchL2DataLength)
; batchL2DataParsed表示Number of bytes read when decoding RLP transactions. Computed during RLP loop
; 加载batchL2DataParsed全局变量值 给 C寄存器
$ => C :MLOAD(batchL2DataParsed)
; batchL2DataParsed - batchL2DataLength,若结果为负数,则跳转到loadTx_rlp中继续处理;
C - A :JMPN(loadTx_rlp)
; batchL2DataParsed - batchL2DataLength,若结果为非负数,则跳转到endCheckRLP
:JMP(endCheckRLP)
endCheckRLP:
; 跳转到txLoop,即第4步“D:循环处理交易”
:JMP(txLoop)
其中loadTx_rlp
,为Blocks RLP解析。
RLP(Recursive Length Prefix),递归长度前缀编码,是一种编码规则,主要用来序列化/反序列化数据,可用于编码任意嵌套的二进制数组数据。RLP编码是以太坊数据序列化的主要编码方式,以太坊中的所有对象都会使用RLP编码序列化为字节数组。详细的编码规则可参看:
loadTx_rlp
,为Blocks RLP解析,可分为5步实现:
;;;;;;;;;;;;;;;;;;
;; A - Initialization
;; - Data to parse: [rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0)|r|s|v]
;; - Signed Ethereum transaction: H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0))
;; - RLP encoding information: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp
;; - Entire batch is discarded (no transaction is processed) if any error is found
;;;;;;;;;;;;;;;;;;
loadTx_rlp:
; A new hash with position 0 is started
; 将HASHPOS重置为0,表示将启动新的哈希运算
0 => HASHPOS
; We get a new hashId
; 加载lastHashKIdUsed全局变量值给E寄存器
$ => E :MLOAD(lastHashKIdUsed)
; 将E寄存器+1,并更新lastHashKIdUsed全局变量
E+1 => E :MSTORE(lastHashKIdUsed)
; Pointer to next RLP bytes to read
; 设置C寄存器值为0
0 => C
;;;;;;;;;;;;;;;;;;
;; B - Read and check RLP fields. Fill 'batchHashData' and Ethereum signed transaction bytes
;;;;;;;;;;;;;;;;;;
;; Read RLP list length 【其中D寄存器表示从ctx.input.batchL2Data中读取的字段长度,单位为byte。】
; Should be a list
; 设置D寄存器值为1
1 => D
; 调用addHashTx
:CALL(addHashTx)
; 调用addBatchHashData
:CALL(addBatchHashData)
; 此时,A寄存器值为:rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0)
; 若A小于0xc0,则跳转到invalidTxRLP
; 根据[https://ethbook.abyteahead.com/ch4/rlp.html]可知:
; RLP中数组编码的起始范围为0xc0,0xc0为空数组的编码
A - 0xc0 :JMPN(invalidTxRLP)
; 若A小于0xf8,则跳转到shortList
; 根据[https://ethbook.abyteahead.com/ch4/rlp.html]可知:
; 0xc1~0xf7,表示会紧跟一个不大于55个字符的数组
A - 0xf8 :JMPN(shortList)
; 否则,A大于等于0xf8。0xf8~0xff,表示会紧跟一条大于55个字符的数组
; 对于小于55个字符串的数组,其结构为:[前缀, 有效数组]
; 对于大于55个字符串的数组,其结构为:[前缀, , 有效数组]
longList:
; A-0xf7,给D寄存器
A - 0xf7 => D
; 调用addHashTx
:CALL(addHashTx)
; 调用addBatchHashData
:CALL(addBatchHashData)
; 跳转到endList
:JMP(endList)
shortList:
; A-0xc0,表示总数据长度,给A寄存器
A - 0xc0 => A
endList:
; txRLPLength表示 transaction RLP list length
A + C :MSTORE(txRLPLength)
;; Read RLP 'nonce'
; 64 bits max
; 读取RLP中的`nonce`字段值,
nonceREAD:
; 设置D寄存器值为1
1 => D
; 调用addHashTx
:CALL(addHashTx)
; 调用addBatchHashData
:CALL(addBatchHashData)
; 若单一字符在0x00~0x7f范围内,则保持原样,不作变更
A - 0x80 :JMPN(endNonce)
; 若A等于0x80,对应为0值表示。
A - 0x81 :JMPN(nonce0)
; 若不是单一字符,为2~55个字符组成的字符串,则前缀为“0x80+字符串长度”
; 由于nonce取值最多为64bit,每个字符以8bit表示
; 因此,长度最多为8个,A的值应不超过0x88。
; 以及 “单个字符在0x80~0xff” 的情况。即同时处理了A为0x81~0x88的情况
A - 0x89 :JMPN(shortNonce)
:JMP(invalidTxRLP)
nonce0:
; 对应0x80 rlp表示的是0值,设置lengthNonce全局变量值为0。
0 :MSTORE(lengthNonce)
0 => A
:JMP(endNonce)
shortNonce:
; 即同时处理了A为0x81~0x88的情况
; A-0x80为字符串长度,存储在D寄存器中
A - 0x80 => D
; 更新lengthNonce CTX变量值为(A - 0x80)
D :MSTORE(lengthNonce)
; 调用addHashTx
:CALL(addHashTx)
; 调用addBatchHashData
:CALL(addBatchHashData)
endNonce:
; 将RLP解析的相应nonce结果存储在txNonce CTX变量中
; txNonce表示 transaction parameter: nonce
A :MSTORE(txNonce)
;; Read RLP 'gas price'
; 256 bits max
; 解析RLP中的`gasprice`字段,最大值为256bit。
gasPriceREAD:
; 设置D为1
1 => D
:CALL(addHashTx)
:CALL(addBatchHashData)
; 以上2个指令,读取了gasprice RLP的前缀信息
; 若单一字符在0x00~0x7f范围内,则保持原样,不作变更
A - 0x80 :JMPN(endGasPrice)
; 若A等于0x80,对应为0值表示。
A - 0x81 :JMPN(gasPrice0)
; 由于gasprice最大为256bit,每个字符为8bit,
; 因此对应长度最大为32,前缀值为0x80+0x20=0xa0
; 即同时处理了为0x81~0xa0的情况
A - 0xa1 :JMPN(shortGasPrice)
:JMP(invalidTxRLP)
gasPrice0:
0 => A
:JMP(endGasPrice)
shortGasPrice:
; 即同时处理了为0x81~0xa0的情况
; A-0x80表示RLP后续有效字符长度
A - 0x80 => D
; 若D小于1,则跳转endGasPrice【感觉没必要?】
D - 1 :JMPN(endGasPrice)
; 以下2个指令为将表示gasprice的有效字符串加载到A寄存器中
:CALL(addHashTx)
:CALL(addBatchHashData)
endGasPrice:
; txGasPriceRLP表示 transaction parameter: 'gasPrice' decoded from the RLP
; 将A寄存器值存入txGasPriceRLP全局变量中
A :MSTORE(txGasPriceRLP)
;; Read RLP 'gas limit'
; 256 bits max
; 读取RLP中的`gaslimit`字段,最大值为256bit。
gasLimitREAD:
; 前缀值字符数为1
1 => D
; 以下2个指令为读取`gaslimit` RLP对应的前缀值
:CALL(addHashTx)
:CALL(addBatchHashData)
; 若单一字符在0x00~0x7f范围内,则保持原样,不作变更
A - 0x80 :JMPN(endGasLimit)
; 若A等于0x80,对应为0值表示。
A - 0x81 :JMPN(gasLimit0)
; 由于gaslimit最大为256bit,每个字符为8bit,
; 因此对应长度最大为32,前缀值为0x80+0x20=0xa0
; 即同时处理了为0x81~0xa0的情况
A - 0xa1 :JMPN(shortGasLimit)
:JMP(invalidTxRLP)
gasLimit0:
0 => A :JMPN(endGasLimit)
shortGasLimit:
; 即同时处理了为0x81~0xa0的情况
; A-0x80表示RLP后续有效字符长度
A - 0x80 => D
; 若D小于1,则跳转endGasLimit【感觉没必要?】
D - 1 :JMPN(endGasLimit)
; 以下2个指令为将表示gaslimit的有效字符串加载到A寄存器中
:CALL(addHashTx)
:CALL(addBatchHashData)
endGasLimit:
; txGasLimit表示 transaction parameter: 'gas limit'
; 将A寄存器值存入txGasLimit全局变量中
A :MSTORE(txGasLimit)
;; Read RLP 'to'
; 160 bits max
; 读取RLP中的`to`字段,必须正好为160bit=20byte,表示接收地址
; 仅当to为空-》对应为表示创建合约;和 to正好为20byte,才是有效的。
; 其它情况都调整到invalidTxRLP
toREAD:
1 => D
:CALL(addHashTx)
:CALL(addBatchHashData)
A - 0x80 :JMPN(invalidTxRLP)
; 若A等于0x80,对应为表示创建合约。
A - 0x81 :JMPN(noTo)
A - 0x94 :JMPN(invalidTxRLP)
; `to`字段为20byte,对应长度为20,0x80+0x14=0x94
; 即,A=0x94时,为有效地址。
A - 0x95 :JMPN(shortTo)
:JMP(invalidTxRLP)
noTo:
; 当A=0x80时,设置isCreateContract为1
; isCreateContract表示flag to determine if a transaction will create a new contract
1 :MSTORE(isCreateContract)
:JMP(endTo)
shortTo:
; A-0x80表示RLP后续有效字符长度
; 对应A=0x94的情况
A - 0x80 => D
; 以下2个指令为将表示to的有效字符串加载到A寄存器中
:CALL(addHashTx)
:CALL(addBatchHashData)
; 将to地址存入txDestAddr和storageAddr全局变量中
; txDestAddr表示transaction parameter: 'to'
; storageAddr表示address which the storage will be modified
A :MSTORE(txDestAddr)
A :MSTORE(storageAddr)
endTo:
;; Read RLP 'value'
; 256 bits max
; 读取RLP中的`value`字段,最大值为256bit
valueREAD:
1 => D
:CALL(addHashTx)
:CALL(addBatchHashData)
A - 0x80 :JMPN(endValue)
A - 0x81 :JMPN(value0)
; 由于value最大为256bit,每个字符为8bit,
; 因此对应长度最大为32,前缀值为0x80+0x20=0xa0
; 即同时处理了为0x81~0xa0的情况
A - 0xa1 :JMPN(shortValue)
:JMP(invalidTxRLP)
value0:
0 => A
:JMPN(endValue)
shortValue:
A - 0x80 => D
D - 1 :JMPN(endValue)
:CALL(addHashTx)
:CALL(addBatchHashData)
endValue:
; 将A寄存器值存入txValue全局变量中
; txValue表示transaction parameter: 'value'
A :MSTORE(txValue)
;; Read RLP 'data'
; should not be a list
; 读取RLP中的`data`字段,其不应该是a list,即表示前缀应小于0xc0
dataREAD:
; 加载batchHashPos值给D寄存器
; batchHashPos表示hash batchHashData position
$ => D :MLOAD(batchHashPos)
; 加载batchHashPos值,存入dataStarts全局变量中
; dataStarts表示hash position where de transaction 'data' starts in the batchHashData
D :MSTORE(dataStarts)
; 设置前缀为1个字符
1 => D
; 设置stack pointer为1024
1024 => SP
; 以下2个指令为读取`data` RLP的前缀值到A寄存器中
:CALL(addHashTx)
:CALL(addBatchHashData)
; 若单一字符在0x00~0x7f范围内,
A - 0x80 :JMPN(veryShortData)
; 若A等于0x80,则什么都不做
A - 0x81 :JMPN(endData)
; 0x81~0xb7,表示前缀之后紧跟1~55个字符
A - 0xb8 :JMPN(shortData)
; 0xb8~bf,表示前缀之后紧跟 大于55个字符
A - 0xc0 :JMPN(longData)
; 若大于等于0xc0,为数组RLP表示,data不能为数组,跳转到invalidTxRLP
:JMP(invalidTxRLP)
veryShortData:
; 若A在0x00~0x7f范围内,
; 设置全局变量txCalldataLen为1
; txCalldataLen表示calldata length
1 :MSTORE(txCalldataLen)
; 令D为31
31 => D
; 调用SHLarith,计算A << D => A
:CALL(SHLarith)
; 将A存入SP中,SP再加1
A :MSTORE(SP++)
; 跳转到endData,什么都不做
:JMP(endData)
shortData:
; A为0x81~0xb7,表示前缀之后紧跟1~55个字符
$ => D :MLOAD(batchHashPos)
; 加载batchHashPos全局变量值到dataStarts全局变量中
D :MSTORE(dataStarts)
; A-0x80,为RLP中data有效字符长度,存储在B寄存器以及txCalldataLen全局变量中
; txCalldataLen表示calldata length
A - 0x80 => B :MSTORE(txCalldataLen)
; 跳转到readData
:JMP(readData)
longData:
; A为0xb8~bf,表示前缀之后紧跟 大于55个字符
; 此时的RLP结构为[前缀P3(0xb7+P2长度), P2(字符串总长度的16进制表达), P1(原始字符串的16进制表达)]
A - 0xb7 => D
; 以下2个指令为从RLP data中读取P2(字符串总长度的16进制表达)到A寄存器
:CALL(addHashTx)
:CALL(addBatchHashData)
; 读取batchHashPos 到 D寄存器
$ => D :MLOAD(batchHashPos)
; 读取batchHashPos 到 dataStarts全局变量中
D :MSTORE(dataStarts)
; 将从RLP data中读取P2(字符串总长度的16进制表达) 加载到 B寄存器,并存储在txCalldataLen全局变量中
; txCalldataLen表示 calldata length
A => B :MSTORE(txCalldataLen)
readData:
; 每次读取32个字节
32 => D
; 若RLP data中剩余有效字符长度小于32,则跳转到readDataFinal
B - D :JMPN(readDataFinal)
; 在txDataRead中存储剩余的有效字符长度
B - D :MSTORE(txDataRead)
; 从RLP data中读取32个字节到A寄存器中
:CALL(addHashTx)
; 将RLP data中读取的32个字节存入SP中,SP再加1
A :MSTORE(SP++)
; 对32字节的A,逐字节调用addBatchHashData
:CALL(addBatchHashByteByByte)
; 将 剩余的有效字符长度 加载到B寄存器中。
$ => B :MLOAD(txDataRead)
; 循环读取,每次读取32个字节
:JMP(readData)
readDataFinal:
; 此时B值小于32
; 若剩余的有效字符长度 小于 1,则跳转到endData
B - 1 :JMPN(endData)
; 将B值给D
B => D
; 读取剩余所有的字符 到 A寄存器
:CALL(addHashTx)
; 调用SHLarith左移将A寄存器中的值补齐为32字节
; 此时的32-D为补齐的位数
32 - D => D
:CALL(SHLarith)
; 将补齐后的A中的data值存储在SP中。此时SP未加1
A :MSTORE(SP)
; 此时的32-D为原始未补齐的位数
32 - D => D
; 将原始未补齐的data值,逐字节调用addBatchHashData
:CALL(addBatchHashByteByByte)
endData:
;; Read RLP 'chainId'
; 64 bits max
; 读取RLP中的`chainId`字段,最大值为64bit
chainREAD:
; 读取RLP chainId的前缀值 到 A寄存器中
1 => D
:CALL(addHashTx)
:CALL(addBatchHashData)
; 若单一字符在0x00~0x7f范围内,则保持原样,不作变更
A - 0x80 :JMPN(endChainId)
; 若A等于0x80,对应为0值表示。
A - 0x81 :JMPN(chanId0)
; chainId最大为64bit,对应8字节,0x80+0x8=0x88
; 此时A的取值为0x81~0x88
A - 0x89 :JMPN(shortChainId)
:JMP(invalidTxRLP)
chanId0:
0 => A
:JMPN(endChainId)
shortChainId:
; 即同时处理了A为0x81~0x88的情况
; A-0x80为字符串长度,存储在D寄存器中
A - 0x80 => D
; 若D小于1,则跳转到endChainId【感觉没必要?】
D - 1 :JMPN(endChainId)
; 以下2条指令为读取chainId的有效字符串 到 A寄存器中
:CALL(addHashTx)
:CALL(addBatchHashData)
endChainId:
; 将 A寄存器值 存储在 txChainId全局变量中
; txChainId表示 transaction parameter: 'chainId'
A :MSTORE(txChainId)
;; Read RLP last two values (0, 0)
; 64 bits max
; 读取RLP中的最后2个值(0,0)到A寄存器中,最大为64bit。
2 => D
:CALL(addHashTx)
:CALL(addBatchHashData)
; 设置B寄存器值为0x8080,为(0,0)的RLP表示
0x8080 => B
; 判断A和B是否相等,若相等则返回1,跳转到sizeVerification;否则invalidTxRLP
$ :EQ,JMPC(sizeVerification)
:JMP(invalidTxRLP)
;; size verification
; checks RLP lenght read at the RLP header with bytes read during RLP parsing
; 检查通过RLP前缀读取的长度,与,解析RLP有效字符获得的长度,二者是否一致
sizeVerification:
; C寄存器在addHashTx解析时会增加更新
; 将RLP有效字符解析获得的长度 加载到 A寄存器中
C => A
; 加载txRLPLength(为根据RLP前缀读取的长度)到B寄存器
$ => B :MLOAD(txRLPLength)
; 二者必须相等,否则invalidTxRLP
$ :EQ,JMPC(sizeVerificationSuccess)
:JMP(invalidTxRLP)
sizeVerificationSuccess:
; 获取H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0))的输入位置HASHPOS,给HASHKLEN(E)。
; HASHPOS寄存器存储的为H_keccak函数的输入拼接字符串的最后位置
HASHPOS :HASHKLEN(E)
;;;;;;;;;;;;;;;;;;
;; C - Read signature. Fill 'batchHashData' bytes
;;;;;;;;;;;;;;;;;;
;; read ecdsa 'r'
; loadTx_rlp解析的数据为:[rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0)|r|s|v]
rREADTx:
; 取D为32
32 => D
; 继续从输入中读取32字节ecdsa签名 r值到 A寄存器中
:CALL(getTxBytes)
; 将32字节ecdsa签名 r值 存储到 txR全局变量中
; txR表示 transaction parameter: ecdsa signature R
A :MSTORE(txR)
; 不同于addHashTx,getTxBytes内未更新C寄存器值,此时需主动更新。
C + D => C
;将ecdsa签名 r值 添加到相应的H_keccak哈希运算中
:CALL(addBatchHashData)
;; read ecdsa 's'
sREADTx:
; 读取ecdsa签名 s值,存储在txS全局变量中,并添加到相应的H_keccak哈希运算中
; txS表示 transaction parameter: ecdsa signature S
32 => D
:CALL(getTxBytes)
A :MSTORE(txS)
C + D => C
:CALL(addBatchHashData)
;; read ecdsa 'v'
vREADTx:
; 读取ecdsa签名 v值,存储在txV全局变量中,并添加到相应的H_keccak哈希运算中
; txV表示 transaction parameter: ecdsa signature V
1 => D
:CALL(getTxBytes)
A :MSTORE(txV)
C + D => C
:CALL(addBatchHashData)
;;;;;;;;;
;; D - Finish RLP parsing
;;;;;;;;;
;; update bytes parsed
; batchL2DataParsed为全局变量,表示 Number of bytes read when decoding RLP transactions. Computed during RLP loop
; 加载batchL2DataParsed 为 A
$ => A :MLOAD(batchL2DataParsed)
; C为解析当前输入数据的有效字符长度,A+C为迄今为止的解析的有效输入字符总数batchL2DataParsed
A + C => A :MSTORE(batchL2DataParsed)
;; increase number of transaction to process
; pendingTxs表示Number of transactions decoded in RLP block
$ => A :MLOAD(pendingTxs)
; 本次解析了一个RLP block,需更新pendingTxs加1,并更新到A寄存器
A + 1 => A :MSTORE(pendingTxs)
; 将当前哈希输入最后的位置存储在sigDataSize全局变量中
; sigDataSize表示 hash position for the ethereum transaction hash
HASHPOS :MSTORE(sigDataSize) ; save bytes length added to ethereum transaction hash
; 本轮输入RLP解析处理完毕,跳回main.zkasm中的txLoopRLP
; 从而支持处理多个batch,每个batch有一笔交易。
:JMP(txLoopRLP)
;;;;;;;;;
;; E - Handler error RLP fields
;;;;;;;;;
invalidTxRLP:
;; Append all missing 'batchL2Data' to 'batchDataHash' bytes
$ => B :MLOAD(batchL2DataLength)
$ => C :MLOAD(batchHashPos)
$ => HASHPOS :MLOAD(batchHashPos)
$ => E :MLOAD(batchHashDataId)
appendTxs:
B - C - 32 :JMPN(finalAppendTxs)
32 => D
${getTxs(p,D)} => A
$${p = p + D}
A :HASHK(E)
C + D => C
:JMP(appendTxs)
finalAppendTxs:
B - C => D
D - 1 :JMPN(endAppendTxs)
${getTxs(p,D)} => A
$${p = p + D}
A :HASHK(E)
C + D => C
endAppendTxs:
HASHPOS :MSTORE(batchHashPos)
:JMP(processTxsEnd)
其中:
addHashTx
逻辑为:;; Add bytes to generate ethereum transaction hash. transactionHash = H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0))
addHashTx:
$ => A :MLOAD(batchL2DataLength)
$ => B :MLOAD(batchL2DataParsed)
A - B - C - D :JMPN(invalidTxRLP)
; `getTxs`为:从ctx.input.batchL2Data中截取特定长度值,转换为8个32bit field elements表示。
; `getTxs`实际返回的就是rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0),存入A寄存器中。
${getTxs(p,D)} => A
; 内联javascript指令注入
$${p = p + D}
; H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0) |
; 此处更新的为ctx.hashK[lastHashKIdUsed].data中的内容
A :HASHK(E)
C + D => C
:RETURN
addBatchHashData
逻辑为:;; Add 'data' bytes to batchHashData. batchHashData = H_keccak( transactions | globalExitRoot | sequencerAddr )
addBatchHashData:
; batchHashPos表示hash batchHashData position
; 加载batchHashPos全局变量值 给 HASHPOS
$ => HASHPOS :MLOAD(batchHashPos)
; batchHashDataId表示hash address used when adding bytes to batchHashData
; 加载batchHashDataId全局变量值 给 E寄存器
$ => E :MLOAD(batchHashDataId)
; H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0) |
; 此处更新的为ctx.hashK[batchHashDataId].data中的内容
A :HASHK(E)
; HASHPOS寄存器存储的为H_keccak函数的输入拼接字符串的最后位置
; 将HASHPOS寄存器值 存入 batchHashPos全局变量中
HASHPOS :MSTORE(batchHashPos)
; 将`addHashTx`中C寄存器中的值( C + D => C)给HASHPOS
C => HASHPOS
; 加载lastHashKIdUsed全局变量值 给 E寄存器
; lastHashKIdUsed表示 Last hash address used
$ => E :MLOAD(lastHashKIdUsed)
:RETURN
SHLarith
逻辑为:;@in A - (A << D)
;@in D - (A << D) D bytes
;@out A - A << D => A
SHLarith:
B :MSTORE(tmpVarB2)
C :MSTORE(tmpVarC2)
D :MSTORE(tmpVarD2)
E :MSTORE(tmpVarE2)
A => E
D => A
8 => B
0 => C
0 => D
${A*B} => D :ARITH
E => A
:JMP(SHLarithinit)