Polygon zkEVM zkROM代码解析(1)

1. 引言

前序博客有:

  • Polygon zkEVM zkASM中的函数集合
  • Polygon zkEVM zkASM语法

Polygon zkEVM zkROM代码库为:

  • https://github.com/0xPolygonHermez/zkevm-rom(以zkASM语言编写,为以太坊交易处理器)

根据zkEVM Audit Education Sessions 1/4 -Circuit Arithmetization for ZKP有:
Polygon zkEVM zkROM代码解析(1)_第1张图片
相关zkASM指令有:
Polygon zkEVM zkROM代码解析(1)_第2张图片
Polygon zkEVM zkROM代码解析(1)_第3张图片
Polygon zkEVM zkROM代码解析(1)_第4张图片

zkROM的基本流程为:

  • 1)A:加载输入变量;
  • 2)B:设置batch storage state-tree:batchHash (oldStateRoot) & globalExitRoot;
  • 3)C:循环解析RLP交易;
  • 4)D:循环处理交易;
  • 5)E:batch asserts:localExitRoot、transactions size、batchHashData & globalHash;
  • 6)F:finalize execution

2. A:加载输入变量

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

3. B:设置batch storage state-tree

zkROM第二步为设置batch storage state-tree,其又分为2步来实现:

  • B.1)设置batch hash
  • B.2)设置global exit root
;;;;;;;;;;;;;;;;;;
;; 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)

4. C:循环解析RLP交易

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编码序列化为字节数组。详细的编码规则可参看:

  • 以太坊的指南针第4章 数据结构——RLP编码

4.1 Blocks RLP解析

loadTx_rlp,为Blocks RLP解析,可分为5步实现:

  • 1)A:初始化
  • 2)B:读取和检查RLP字段,Fill ‘batchHashData’ and Ethereum signed transaction bytes
  • 3)C:读取签名,Fill ‘batchHashData’ bytes
  • 4)D:完成RLP解析
  • 5)E:Handler error RLP fields
;;;;;;;;;;;;;;;;;;
;; 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)

其中:

  • 1)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
    
  • 2)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
    
  • 3)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)
    

附录: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
  • Polygon zkEVM zkASM语法
  • Polygon zkEVM可验证计算简单状态机示例
  • Polygon zkEVM zkASM 与 以太坊虚拟机opcode 对应集合

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