美文网首页
Polygon zkEVM ROM 技术解析

Polygon zkEVM ROM 技术解析

作者: 雪落无留痕 | 来源:发表于2023-09-11 14:30 被阅读0次

    Polygon zkEVM ROM 是一个zkASM 语言实现的EVM,实现了对BATCH 进行执行处理的过程,主要分为以下几步:

    • A:将初始的寄存器的值载入内存中:oldStateRoot(B), oldAccInputHash(C),oldNumBatch(SP),chainID(GAS).
    • B: 设置Batch 全局变量;
    • C: 循环解析RLP交易;
    • D: 循环处理交易;
    • E:Batch 计算:获取 newLocalExitRoot, 断言 transactions size, 计算batchHashDatanewAccInputHash.

    载入输入变量

    在Main_executor 中初始化状态时,首先对寄存器B, C, SP, GAS 进行初始化,如下所示:

        // Set oldStateRoot to register B
        [
            pols.B0[0],
            pols.B1[0],
            pols.B2[0],
            pols.B3[0],
            pols.B4[0],
            pols.B5[0],
            pols.B6[0],
            pols.B7[0]
        ] = scalar2fea(ctx.Fr, Scalar.e(ctx.input.oldStateRoot));
    
        // Set oldAccInputHash to register C
        [
            pols.C0[0],
            pols.C1[0],
            pols.C2[0],
            pols.C3[0],
            pols.C4[0],
            pols.C5[0],
            pols.C6[0],
            pols.C7[0]
        ] = scalar2fea(ctx.Fr, Scalar.e(ctx.input.oldAccInputHash));
    
        // Set oldNumBatch to SP register
        pols.SP[0] = ctx.Fr.e(ctx.input.oldNumBatch)
    
        // Set chainID to GAS register
        pols.GAS[0] = ctx.Fr.e(ctx.input.chainID)
    

    然后通过zkASM指令读取寄存器的值到内存中:

    ;;;;;;;;;;;;;;;;;;
            STEP => A    // 断言初始STEP 为 0
            0                                   :ASSERT ; Ensure it is the beginning of the execution
            B                                   :MSTORE(oldStateRoot)
            C                                   :MSTORE(oldAccInputHash)
            SP                                  :MSTORE(oldNumBatch)
            GAS                                 :MSTORE(chainID)
    

    以下将相关的输入保存在到内存中:

            ${getGlobalExitRoot()}              :MSTORE(globalExitRoot) // 保存globalExitRoot
            ${getSequencerAddr()}               :MSTORE(sequencerAddr)  // 保存sequencerAddr
            ${getTimestamp()}                   :MSTORE(timestamp)      // 保存timestamp
            ${getTxsLen()}                      :MSTORE(batchL2DataLength) //保存batchL2DataLength
    
            B => SR ;set initial state root   // 赋值SR寄存器为State Root  
    
            ; Increase batch number
            SP + 1                              :MSTORE(newNumBatch) // 保存newNumBatch
    

    设置Batch 全局变量

    首先判断是否需要设置globalExitRoot.

            $${eventLog(onStartBatch, C)}   // 日志打印信息
    
            $ => A                                  :MLOAD(globalExitRoot)
            0 => B
            $                                       :EQ, JMPC(skipSetGlobalExitRoot)
    

    globalExitRoot 为0, 则不需要再设置globalExitRoot.

    setGlobalExitRoot

    首先计算 keccak256(globalExitRoot, 0)

            0 => HASHPOS  // HashK 输入数据的起始位置
            $ => E                                  :MLOAD(lastHashKIdUsed) // HashK id
            E+1 => E                                :MSTORE(lastHashKIdUsed)
    
            32 => D     // 定义本次HashK 输入数据的长度
            A                                       :HASHK(E) // HashK 输入寄存器A中的值
            
            // HashK 输入常量 0
            %GLOBAL_EXIT_ROOT_STORAGE_POS           :HASHK(E) ; Storage position of the global exit root map
            HASHPOS                                 :HASHKLEN(E)  // 根据HASHPOS的最终长度计算 HashK
            $ => C                                  :HASHKDIGEST(E) // 将HashK 计算的hash值保存在寄存器 C 上
    

    然后以寄存器A,B,C 的值计算key, 从SMT 树读取相应的值,保存在寄存器D中。若读取的值不为0,则进行跳转。

            %ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 => A  //GLOBAL_EXIT_ROOT_MANAGER_L2 合约地址
            %SMT_KEY_SC_STORAGE => B  // 常量值3
    
            ; read timestamp given the globalExitRoot
            ; skip overwrite timestamp if it is different than 0
            $ => D                                  :SLOAD, JMPNZ(skipSetGlobalExitRoot)
    

    SSTORE 指令中,以A,B,C 寄存器计算key, D 寄存器中的值为value, 保存在SMT 存储中,最后将SMT更新后得到的新的状态根保存在SR 寄存器中。

            $ => D                     :MLOAD(timestamp)
            $ => SR                    :SSTORE ; Store 'timestamp' in storage position 'keccak256(globalExitRoot, 0)'
    
    

    skipSetGlobalExitRoot

    从SMT中读取交易的数量,并保存在内存中。

            SR                                      :MSTORE(batchSR) // 读取当前的state root 到内存中
            ; Load current tx count
            %LAST_TX_STORAGE_POS => C   // 常量为0
            %ADDRESS_SYSTEM => A        // 系统合约:0x000000000000000000000000000000005ca1ab1en
            %SMT_KEY_SC_STORAGE => B  // 常量3
            $ => D          :SLOAD      
            D               :MSTORE(txCount)
    

    首先根据交易数据长度,除以136(每轮KecccakF 处理1088位),得到当前Batch需要的KeccakF个数,和最允许的最大个数进行比较。若超过范围,则报错。

            ; Compute necessary keccak counters to finish batch
            $ => A          :MLOAD(batchL2DataLength)
            ; Divide the total data length + 1 by 136 to obtain the keccak counter increment.
            ; 136 is the value used by the prover to increment keccak counters
            A + 1                                   :MSTORE(arithA)
            136                                     :MSTORE(arithB), CALL(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB]
            $ => B                                  :MLOAD(arithRes1)
            ; Compute minimum necessary keccaks to finish the batch
            B + 1 + %MIN_CNT_KECCAK_BATCH           :MSTORE(cntKeccakPreProcess) //MIN_CNT_KECCAK_BATCH 为1
            %MAX_CNT_KECCAK_F - CNT_KECCAK_F => A   // MAX_CNT_KECCAK_F 为2376
            $                                       :LT, JMPC(outOfCountersKeccak)
    

    循环解析RLP交易

    载入RLP 编码的交易数据,保存RLP 编码正确性,若有任意一个交易的RLP编码出现问题,则不会处理任何交易 。

    ;;;;;;;;;;;;;;;;;;
    
            E+1 => E                            :MSTORE(lastHashKIdUsed) // 更新last HashK id used
            0                                   :MSTORE(batchHashPos)  // bashHash 的起始位置 
            E                                   :MSTORE(batchHashDataId) // bashHashData的id
            $ => A                              :MLOAD(lastCtxUsed)   // 读取 lastCtxUsed
            A                                   :MSTORE(ctxTxToUse) ; Points at first context to be used when processing transactions   // 保存使用的 ctx
     
            $${var p = 0} // 在js中定义变量p的值为0, p为指向batchL2Data的offset
    

    txLoopRLP

    batchL2Data 的交易数据格式为:

    rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0)|r|s|v| rlp(...)|r|s|v|...
    

    对于解析的每笔交易,CTX 增1 。然后从内存中读取总的交易数据长度batchL2DataLength 和 已经解析的交易数据长度batchL2DataParsed. 若没解析完,则跳转到loadTx_rlp; 若解析完成,则跳转到endChekcRLP处。

    txLoopRLP:
            $ => A          :MLOAD(lastCtxUsed)
            A+1 => CTX      :MSTORE(lastCtxUsed) // 每笔交易占用一个CTX 空间
    
            $ => A          :MLOAD(batchL2DataLength)
            $ => C          :MLOAD(batchL2DataParsed)
            C - A           :JMPN(loadTx_rlp, endCheckRLP)
    endCheckRLP:
                            :JMP(txLoop)
    

    loadTx_rlp

    RLP 解析包含五步,分别为:

    • A : 初始化;
    • B: 读取并且检查RLP各个字段,填充batchHashData 和 以太坊签名的交易字节数据;
    • C: 读取签名,填充 batchHashData;
    • D: 完成RLP 解析;
    • E: RLP的错误处理

    初始化

    对于每笔交易,解析的数据为:

    [rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0)|r|s|v]
    

    签名的以太坊交易为:

    H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0))
    

    初始化过程为如下:

            ; A new hash with position 0 is started
            0 => HASHPOS    // 将HASHPOS 置为0
    
            ; We get a new hashId
            $ => E                          :MLOAD(lastHashKIdUsed)
            E+1 => E                        :MSTORE(lastHashKIdUsed)  // E寄存器存储新的HashK id
            ; Pointer to next RLP bytes to read
            0 => C
    

    解析RLP字段

    首先理解两个调用的函数addHashTxaddBatchHashData

    对于addHashTx 用于从batchL2Data 读取D 长度的数据,输入到HashK中。

    ;; Add bytes to generate ethereum transaction hash. transactionHash = H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0))
    addHashTx:
            $ => A                          :MLOAD(batchL2DataLength) // 获取batchL2Data 数据总长度
            $ => B                          :MLOAD(batchL2DataParsed)  // 获取已经解析的数据长度
            A - B - C - D                   :JMPN(invalidTxRLP)   // C: 当前交易已经读取的offset, D: 本次读取的长度
            ${getTxs(p,D)} => A   // p: batchL2Data读取位置的offset D: 本次读取的长度  
            $${p = p + D}     // 更新p的值
            A                               :HASHK(E) // 将从batchL2Data新读取的数据输入到HashK中,用于计算交易Hash
            C + D => C                      :RETURN    // 更新C 偏移量的值
    

    对于addBatchHashData, 将从batchL2Data 读取的数据,用于计算batchHashData.

    ;@info: Add 'data' bytes to batchHashData. batchHashData = H_keccak( transactions )
    ;@in: D: length of the hash
    addBatchHashData:
            $ => HASHPOS                    :MLOAD(batchHashPos)    // 读取batchHashPos
            $ => E                          :MLOAD(batchHashDataId)  // 读取batchHashData id 
            A                               :HASHK(E)             // 读取的数据输入到HashK
            HASHPOS                         :MSTORE(batchHashPos)   // 更新batchHashPos
            C => HASHPOS         // 将HASHPOS 置为当前交易读取位置的offset
            $ => E                          :MLOAD(lastHashKIdUsed), RETURN  // 将E重置为当前交易的HashK id
    

    利用上面两个函数,首先读取batchL2Data的第一个字节,若其值小于0xc0, 则说明不是数组,RLP编码无效。

    ;; Read RLP list length
            ; Should be a list
            1 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A - 0xc0                        :JMPN(invalidTxRLP)
            A - 0xf8                        :JMPN(shortList) // 根据RLP编译,若值小于f8, 则于短数组(长度不大于55)
    

    对于长列表,需从batchL2Data 读取 RLP 列表的实际长度,对于短列表,直接计算出交易RLP列表实际长度。

    longList:
            A - 0xf7 => D                   :CALL(addHashTx)  
                                            :CALL(addBatchHashData)
                                            :JMP(endList)
    shortList:
            A - 0xc0 => A  // 对于短列表,获取RLP编译的实现长度
    
    endList:
            A + C => B                      :MSTORE(txRLPLength) // 保存RLP编码后交易的总长度
            ; Check enough zk counters to digest tx hash
            B + 1                           :MSTORE(arithA)
            136                             :MSTORE(arithB), CALL(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB]
            $ => B                          :MLOAD(arithRes1)
            $ => D                          :MLOAD(cntKeccakPreProcess)
            %MAX_CNT_KECCAK_F - CNT_KECCAK_F - B - D - 1:JMPN(handleOOCKatRLP)
    

    解析 nonce

    batchL2Data 读取一个字节,若其值小于0x80, 则其值为txNonce; 若为0x80,则txNonce 值为0, lengthNonce 值为0; 若于小于0x89, 则再次从batchL2Data 读取txNonce .

    txNoncelengthNonce 保存在CTX中, CTX 在内存的起始位置为: 0x040000

    ;; Read RLP 'nonce'
            ; 64 bits max  //最多8个字节
    nonceREAD:
            1 => D                          :CALL(addHashTx) 
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(endNonce)
            A - 0x81                        :JMPN(nonce0)
            A - 0x89                        :JMPN(shortNonce, invalidTxRLP)
    
    nonce0:
            0  => A                         :MSTORE(lengthNonce), JMP(endNonce)
    
    shortNonce:
            A - 0x80 => D    //lenthNonce表示txNonce的字节长度
            D                               :MSTORE(lengthNonce), CALL(addHashTx)
                                            :CALL(addBatchHashData)
    
    endNonce:
            A                               :MSTORE(txNonce)
    

    解析 gas price

    batchL2Data 读取gas price, 并保存CTX中的 txGasPriceRLP 位置。

    ;; Read RLP 'gas price'
            ; 256 bits max    // 最多32个字节
    gasPriceREAD:
            1 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(endGasPrice)
            A - 0x81                        :JMPN(gasPrice0)
            A - 0xa1                        :JMPN(shortGasPrice, invalidTxRLP)
    
    gasPrice0:
            0 => A                          :JMP(endGasPrice)
    
    shortGasPrice:
            A - 0x80 => D
            D - 1                           :JMPN(endGasPrice)
                                            :CALL(addHashTx)
                                            :CALL(addBatchHashData)
    
    endGasPrice:
            A                               :MSTORE(txGasPriceRLP)
    

    解析 gas limit

    batchL2Data 读取gas limit, 并保存CTX中的 txGasLimit 位置。

    ;; Read RLP 'gas limit'
            ; 256 bits max  // 最大32 个字节
    gasLimitREAD:
            1 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(endGasLimit)
            A - 0x81                        :JMPN(gasLimit0)
            A - 0xa1                        :JMPN(shortGasLimit, invalidTxRLP)
    
    gasLimit0:
            0 => A                          :JMPN(endGasLimit)
    
    shortGasLimit:
            A - 0x80 => D
            D - 1                           :JMPN(endGasLimit)
                                            :CALL(addHashTx)
                                            :CALL(addBatchHashData)
    
    endGasLimit:
            A                               :MSTORE(txGasLimit)
    

    解析 to

    to 长度为0,则表示为创建合约的交易,将isCreateContract 设为1; 若长度以20, 则将 to 地址保存到 txDestAddrstorageAddr 中。

    isCreateContract, txDestAddr, storageAddr 皆保存在CTX中。

    ;; Read RLP 'to' 
            ; 160 bits max   // 20字节或0
    toREAD:
            1 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(invalidTxRLP)
            A - 0x81                        :JMPN(noTo)
            A - 0x94                        :JMPN(invalidTxRLP)
            A - 0x95                        :JMPN(shortTo, invalidTxRLP)
    
    noTo:
            1                               :MSTORE(isCreateContract), JMP(endTo)
    
    shortTo:
            A - 0x80 => D                   :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A                               :MSTORE(txDestAddr)
            A                               :MSTORE(storageAddr)
    
    endTo:
    

    解析 value

    batchL2Data 读取value, 并保存CTX中的 txValue位置。

    ;; Read RLP 'value'
            ; 256 bits max  // 最大32个字节
    valueREAD:
            1 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(endValue)
            A - 0x81                        :JMPN(value0)
            A - 0xa1                        :JMPN(shortValue, invalidTxRLP)
    
    value0:
            0 => A
                                            :JMPN(endValue)
    
    shortValue:
            A - 0x80 => D
            D - 1                           :JMPN(endValue)
                                            :CALL(addHashTx)
                                            :CALL(addBatchHashData)
    
    endValue:
            A                               :MSTORE(txValue)
    

    解析 data

    解析RLP中的交易data 数据,通过SP 寄存器保存在 EVM Stack, 在内存的起始的位置为:0x040000 + 0x010000 + CALLDATA_OFFSET(1024)

    将解析后数据长度保存CTX中的txCalldataLen位置,数据保存在EVM Stack中,每个位置保存32字节,左边为低位,右边为高位。

    注:dataStarts 用于表示data真正数据在batchL2Data的位置,保存为CTX中的数据,在内存的起始位置为:0x40000 + 38, 其中0x40000 为CTX起始位置,38为 dtataStarts 的 offset.

    ;; Read RLP 'data'
            ; should not be a list
    dataREAD:
            $ => D                          :MLOAD(batchHashPos)
            D                               :MSTORE(dataStarts)  // 设置dataStarts 为 batchHashPos
            1 => D        //首先读到data rlp 编码的第1个字节
            %CALLDATA_OFFSET => SP          :CALL(addHashTx)  //CALLDATA_OFFSET 值为1024
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(veryShortData) // data为单字节
            A - 0x81                        :JMPN(endData)       // data为空
            A - 0xb8                        :JMPN(shortData)     // data 长度不大于55个字节
            A - 0xc0                        :JMPN(longData, invalidTxRLP) // data 长度大于55个字节
    
    veryShortData:
            1                               :MSTORE(txCalldataLen) // data 长度为1
            31 => D                         :CALL(SHLarith)    // SHLarith 功能: A << D, 将A 往左移 D字节
            A                               :MSTORE(SP++), JMP(endData) // 将A存在EVM Stack中
    
    shortData:
            $ => D                          :MLOAD(batchHashPos)
            D                               :MSTORE(dataStarts)  // 更新 dataStarts 为 batchHashPos
            A - 0x80 => B                   :MSTORE(txCalldataLen), JMP(readData)
    
    longData:
            A - 0xb7 => D                   :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            $ => D                          :MLOAD(batchHashPos)
            D                               :MSTORE(dataStarts)   // 更新 dataStarts 为 batchHashPos
            A => B                          :MSTORE(txCalldataLen) // 解析后 data 的数据长度
    
    readData:
            32 => D
            B - D                           :JMPN(readDataFinal) // 若最后一部数据小于32字节,则跳转
            B - D                           :MSTORE(txDataRead), CALL(addHashTx) //txDataRead 为要读取data的剩余长度
            A                               :MSTORE(SP++), CALL(addBatchHashByteByByte) 
            //将读取的32字节数据保存在EVM Stack,位置根据SP确定,同时将读到的数据添加到BatchHashData中。 
            
            $ => B                          :MLOAD(txDataRead), JMP(readData) // 循环读取32字节
    
    readDataFinal:
            B - 1                           :JMPN(endData) // 确保最后一部data的长度大于等于1
            B => D                          :CALL(addHashTx) 
            32 - D => D                     :CALL(SHLarith) // 将读到的A寄存器器值左移 D 字节
            A                               :MSTORE(SP)   // 保存在EVM Stack 中
            32 - D => D                     :CALL(addBatchHashByteByByte) //将最后一部分data 添加到BatchHashData中
    
    endData:
            ; Check all bytes read to detect pre EIP-155 tx
            C => A
            $ => B                  :MLOAD(txRLPLength) 
            $                       :EQ,JMPC(setPreEIP155Flag) // 判断是否为EIP155前的交易类型,即不含(ChainId,0,0)
    

    解析 chainId

    batchL2Data 读取 chainId, 保存CTX中的txChainID位置。

    ;; Read RLP 'chainId'
            ; 64 bits max //最大8个字节
    chainREAD:
            1 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            A - 0x80                        :JMPN(endChainId)
            A - 0x81                        :JMPN(chainId0)
            A - 0x89                        :JMPN(shortChainId, invalidTxRLP)
    
    chainId0:
            0 => A                          :JMP(endChainId)
    
    
    shortChainId:
            A - 0x80 => D
            D - 1                           :JMPN(endChainId)
                                            :CALL(addHashTx)
                                            :CALL(addBatchHashData)
    
    endChainId:
            A                               :MSTORE(txChainId)
    

    解析(0,0)

    解析最后两个字节的数据为:0x8080.

    ;; Read RLP last two values (0, 0)
            ; 64 bits max
            2 => D                          :CALL(addHashTx)
                                            :CALL(addBatchHashData)
            0x8080 => B
            $                               :EQ,JMPC(sizeVerification, invalidTxRLP)
    

    size verification

    setPreEIP155Flag:
            1                               :MSTORE(isPreEIP155) // 用于表示为EIP155 之前的交易类型
    ;; size verification
            ; checks RLP lenght read at the RLP header with bytes read during RLP parsing
    sizeVerification:
            C => A
            $ => B                          :MLOAD(txRLPLength) // 比较RLP编码后的长度
            $                               :EQ,JMPC(sizeVerificationSuccess, invalidTxRLP)
    sizeVerificationSuccess:
            HASHPOS                         :HASHKLEN(E)  // 计算交易的Hash值
    

    签名解析

    分别读取txR, txS, txV, 保存到CTX中,并添加到BatchHashData中。

    ;; read ecdsa 'r'
    rREADTx:
            32 => D                         :CALL(getTxBytes)
            A                               :MSTORE(txR)
            C + D => C                      :CALL(addBatchHashData)
    
    ;; read ecdsa 's'
    sREADTx:
            32 => D                         :CALL(getTxBytes)
            A                               :MSTORE(txS)
            C + D => C                      :CALL(addBatchHashData)
    
    ;; read ecdsa 'v'
    vREADTx:
            1 => D                          :CALL(getTxBytes)
            A                               :MSTORE(txV)
            C + D => C                      :CALL(addBatchHashData)
    

    完成RLP 交易解析

    首先更新已经解析的交易数据长度batchL2DataParsed, 将pendingTxs 个数增1 ,将交易hash 保存在CTX中。

    ;;;;;;;;;
    ;; D - Finish RLP parsing
    ;;;;;;;;;
    ;; update bytes parsed
            $ => A                          :MLOAD(batchL2DataParsed)
            A + C => A                      :MSTORE(batchL2DataParsed)
    ;; increase number of transaction to process
            $ => A                          :MLOAD(pendingTxs)
            A + 1 => A                      :MSTORE(pendingTxs)
    ;; compute signature
            $ => A                          :HASHKDIGEST(E)
            A                               :MSTORE(txHash), JMP(txLoopRLP)
    

    最后跳转,去解析下一个RLP交易.

    循环处理交易

    txLoop

    分别载入每个交易的数据,并对其进行解释。

    ;;;;;;;;;;;;;;;;;;
    ;; D - Loop processing transactions
    ;;      - Load transaction data and interpret it
    ;;;;;;;;;;;;;;;;;;
    
    txLoop:
            $ => A          :MLOAD(pendingTxs)
            A-1 => A        :MSTORE(pendingTxs), JMPN(processTxsEnd)
    
            $ => A          :MLOAD(ctxTxToUse) ; Load first context used by transaction //跳转到交易 CTX
            A+1 => CTX      :MSTORE(ctxTxToUse),JMP(processTx)
    
    processTxEnd:
                            :CALL(updateSystemData)
    processTxFinished:
            $${eventLog(onFinishTx)}   :JMP(txLoop)
    
    processTxsEnd:
    

    processTx

    交易的处理主要分为以下几步:

    • A: 验证ecdsa 签名;
    • B: 验证 tx.sender;
    • C: 验证chainID;
    • D: 验证和增加 nonce ;
    • E: 验证预付花费;
    • F: 检查交易类型
      • 调用合约
      • 部署合约
    • 处理 gas.
    验证 ecdsa 签名

    根据txR, txS, txV, txHash, 通过ecrecover 计算 from 地址,将txSrcAddr 保存在交易CTX中,txSrcOriginAddr 保存在非交易CTX的内存中。

    ;;;;;;;;;;;;;;;;;;
    
            $${eventLog(onProcessTx)} // 日志打印
    
    ;;;;;;;;;
    ;; Store init state
    ;;;;;;;;;
    
            ; Store initial state at the beginning of the transaction
            SR                              :MSTORE(originSR)  //交易处理前的状态根  全局
            SR                              :MSTORE(initSR) //CTX: 处理完nonce和预付cost后的根
    
            ; Minimum of 100000 steps left to process a tx
            %MAX_CNT_STEPS - STEP - 100000 :JMPN(outOfCountersStep) //MAX_CNT_STEPS: 2^23 - 200 = 8388408
            %MAX_CNT_BINARY - CNT_BINARY - 100   :JMPN(outOfCountersBinary) //MAX_CNT_BINARY: 2^23 /16
            $ => A                          :MLOAD(txHash) // 读取txHash
            ; Check the signature
            $ => B                          :MLOAD(txR)   // 读取 txR
            $ => C                          :MLOAD(txS)    // 读取 txS
            $ => D                          :MLOAD(txV), CALL(ecrecover_tx) // 调用ecrecover 函数
            ; Check result is non-zero
    checkAndSaveFrom:
            0 => B
            A                               :MSTORE(txSrcAddr)
            A                               :MSTORE(txSrcOriginAddr)
            $                               :EQ,JMPC(invalidIntrinsicTxSignature)
    

    若计算的from 地址为0,则为无效的交易。

    验证 tx.sender

    根据A,B,C 计算key, 从SMT 中获取 from 地址合约代码是否为空,以确保 from 不是合约地址。

    ;;;;;;;;;;;;;;;;;;
    ;; B - Verify tx.sender does not have any code deployed (EIP3607)
    ;;;;;;;;;;;;;;;;;;
    
            %SMT_KEY_SC_LENGTH => B   //SMT_KEY_SC_LENGTH: 4
            0 => C
            $ => B                          :SLOAD, JMPNZ(invalidIntrinsicTxSenderCode)
    
    验证 chainID

    对于EIP155之前的交易类型,不再验证chainId.

    对于EIP155之后的交易类型,主要验证交易的chainId 和 Batch chainId是否一致。

    ;;;;;;;;;;;;;;;;;;
    
            ; Don't check chainId for legacy transactions
            $ => A                          :MLOAD(isPreEIP155), JMPNZ(endCheckChainId)
            $ => A                          :MLOAD(txChainId) // 交易的chainId
            $ => B                          :MLOAD(chainID)   // batch chainID
            $                               :EQ,JMPC(endCheckChainId, invalidIntrinsicTxChainId)  
                                            ; If A == B --> endCheckChainId
    endCheckChainId:
    
    ;; Reset warm/cold information
            $ => A                          :MLOAD(txSrcOriginAddr), CALL(initTouchedTree)
                                            :CALL(isColdAddress) ; add tx.origin to touched addresses
            0                               :MSTORE(depth) ; Initial depth is 0
    
    ;; Set gasPrice global var
            $ => A                          :MLOAD(txGasPriceRLP) // txGasPriceRLP 在CTX 中
            A                               :MSTORE(txGasPrice)  // txGasPrice 为全局变量
    

    tx.origin 添加到 touched address 地址中,TouchedTree 采用SMT树存储,每个交易单独构建, 根为touchedSR, 每个地址的key由tx.origin, SMT_KEY_TOUCHED_ADDR (常量5), 0(内部存储key) 三部分计算, value 为 0 表示为cold address,为1 表 warm address。

    checkWarmed:
        ; save current state root & load touched root
        SR              :MSTORE(tmpStateRoot)
        $ => SR         :MLOAD(touchedSR)
    
        ; read from touched tree if address is warm
        ; A register already set
        %SMT_KEY_TOUCHED_ADDR => B
        0 => C
        $ => D          :SLOAD, JMPZ(markWarmAddress)
    

    注:零地址和预编译合约(地址小于 10)为warm address.

    验证 nonce

    比较SMT 中 from 地址中的 nonce 值 和 交易中的nonce是否一致;

    然后将nonce 增加 1 保存到 SMT中,返回新的状态机到SR中。

    
            $ => A, E                       :MLOAD(txSrcOriginAddr) ; Address of the origin to A and E
            %SMT_KEY_NONCE => B    // SMT_KEY_NONCE 值为1
            0 => C
            $ => A                          :SLOAD
            $ => B                          :MLOAD(txNonce)
            $ => C                          :EQ, JMPNC(invalidIntrinsicTxNonce) ; Compare nonce state tree with nonce transaction
            B                               :ASSERT ; sanity check
            A + 1 => D
            E => A
            %SMT_KEY_NONCE => B
            0 => C
            $ => SR                         :SSTORE ; Store the nonce plus one
    
    验证预付 cost

    对于部署合约: gas Limit >= 53000 + calldata cost

    对于调用合约: gasLimit >= 2100 + calldata cost;

    对于calldata: 分别累加每个字节的 gas, 若字节为0, 则加4, 否则加16

    然后验证: balance >= gas price * gas limit + value

    再更新from 余额为:balance -(gas price * gas limit )

    最后将未用完的 gas 保存在 GAS 寄存器中。

            ; Verify batch gas limit
            $ => B                          :MLOAD(txGasLimit)
            ; Check batch gas limit is not exceeded by transaction
            %TX_GAS_LIMIT => A                       // 交易最大的Gas limit: 30000000
            $                               :LT,JMPC(invalidIntrinsicBatchGasLimit)
    
            ; Intrinsic gas --> gas Limit >= 21000 + calldata cost + deployment cost
            %BASE_TX_GAS => E ; Store init intrinsic gas at E
            $ => A                          :MLOAD(isCreateContract), JMPNZ(addDeploymentGasCost, getCalldataGasCost)   
    
    addDeploymentGasCost:
            %BASE_TX_DEPLOY_GAS => E ; 53000 gas if transaction is a create
    
    getCalldataGasCost:
            $ => A                          :MLOAD(txCalldataLen)
            0 => B
            $                               :EQ,JMPC(endCalldataIntrinsicGas)
    
    addGas:
            $ => HASHPOS                    :MLOAD(dataStarts)
            0 => C                          :JMP(loopBytes)
    
    loopBytes:
            %MAX_CNT_STEPS - STEP - 10 :JMPN(outOfCountersStep)
            A - C - 1                       :JMPN(endCalldataIntrinsicGas)
            E => B
            $ => E                          :MLOAD(batchHashDataId)
            $ => D                          :HASHK1(E)
            B => E
            C + 1 => C
            D - 1                           :JMPN(add4Gas, add16Gas)
    
    add4Gas:
            E + 4 => E                      :JMP(loopBytes)
    
    add16Gas:
            E + 16 => E                     :JMP(loopBytes)
    
    endCalldataIntrinsicGas:
            ; Compare gas limit >= intrinsic gas
            $ => A                          :MLOAD(txGasLimit)
            E => B
            $                               :LT, JMPC(invalidIntrinsicTxGasLimit)
            ; Store calculated gas for later usage
            E                               :MSTORE(gasCalldata)
    
            ; Check upfront cost: balance >= gas price * gas limit + value
            ; gas price * gas limit
            $ => B                          :MLOAD(txGasPrice)
            A                               :MSTORE(arithA)
            B                               :MSTORE(arithB), CALL(mulARITH)
            $ => D                          :MLOAD(arithOverflow), JMPNZ(invalidIntrinsicTxGasOverflow)
            $ => D                          :MLOAD(arithRes1)
            ; Get caller balance
            $ => A                          :MLOAD(txSrcOriginAddr)
            0 => B, C
            $ => C                          :SLOAD
            ; (gas price * gas limit) + value
            $ => B                          :MLOAD(txValue)
            D                               :MSTORE(arithA)
            B                               :MSTORE(arithB), CALL(addARITH)
            $ => B                          :MLOAD(arithRes1)
            ; Comparison
            C => A
            $                               :LT, JMPC(invalidIntrinsicTxBalance)
    
            ; Substract (gas price * gas limit) from caller balance
            C                               :MSTORE(arithA)
            D                               :MSTORE(arithB), CALL(subARITH)
            ; Substracted balance result in D
            $ => D                          :MLOAD(arithRes1)
            $ => A                          :MLOAD(txSrcOriginAddr)
            0 => B,C
            $ => SR                         :SSTORE
    
            ; Store state root with upfront cost substracted and nonce increased
            SR                              :MSTORE(initSR)
    
            ; Substract intrinsic gas
            $ => GAS                        :MLOAD(txGasLimit)
            $ => A                          :MLOAD(gasCalldata)
            GAS - A => GAS
    
    检查交易类型

    首先确定交易类型:

    to 为空,则为部署合约交易;

    to 地址加入到touched addresses;

    to 地址小于10, 则为预编译合约;

    其它的为普通的合约调用。

    txType:
    
            ; Compute deployment address if create contract operation
            $ => A                          :MLOAD(isCreateContract), JMPNZ(getContractAddress) // 部署合约交易
            $ => A                          :MLOAD(txDestAddr)
                                            :CALL(isColdAddress) ; Add 'to' to touched addresses  
            ; Check 'to' is zero or precompiled contract
            ; Check zero address since zero address is not a precompiled contract
            0 => B
            $                               :EQ, JMPC(callContract)  // 调用地址为0的合约
            10 => B
            $                               :LT,JMPC(selectorPrecompiled, callContract) // 预编译合约和普通的合约调用
    
    部署合约交易

    计算合约地址

    对于create , 合约地址为:keccak256(rlp.encode([normalize_address(sender), nonce]))[12:]

    对于create2, 合约地址为:keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12:]

    当于create 的情况下:

    ;; compute new create address
    getContractAddress:
            ; A new hash with position 0 is started
            0 => HASHPOS
            ; We get a new hashId
            $ => E                          :MLOAD(lastHashKIdUsed)  // 获取新的HashK id
            E+1 => E                        :MSTORE(lastHashKIdUsed)
            ; Check if create is with CREATE2 opcode
            $ => A                          :MLOAD(isCreate2), JMPNZ(create2) // 判断是否为create2
            ; Check keccak counters
            $ => A                          :MLOAD(cntKeccakPreProcess)
            %MAX_CNT_KECCAK_F - CNT_KECCAK_F - A - 1:JMPN(outOfCountersKeccak)
            $ => A                          :MLOAD(txNonce)
            0x80 => B
            $                               :LT,JMPC(nonce1byte) // 判断nonce是否为1个字节
            $ => C                          :MLOAD(lengthNonce)
            ; 1 byte length address + 20 bytes address + 1 byte length nonce + C bytes nonce
            0xc0 + 22 + C                   :HASHK1(E)  // 计算 keccak256(rlp(address, nonce))
            0x94                            :HASHK1(E)
            20 => D
            $ => B                          :MLOAD(txSrcAddr)
            B                               :HASHK(E)
            0x80 + C                        :HASHK1(E)
            C => D
            A                               :HASHK(E), JMP(endContractAddress)
    
    nonce1byte:    // nonce 为1个字节
            $ => A                          :MLOAD(txSrcAddr)   
            $ => B                          :MLOAD(txNonce)
            0xc0 + 22                       :HASHK1(E)
            0x94                            :HASHK1(E)
            20 => D
            A                               :HASHK(E)
            B                               :JMPZ(nonceIs0)
            B                               :HASHK1(E),JMP(endContractAddress)
    
    nonceIs0:  // nonce 为 0
            0x80                            :HASHK1(E),JMP(endContractAddress)
    

    对于create2 情况下,合约地址创建:

    ;; compute new contract address as CREATE2 spec: keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:] (https://eips.ethereum.org/EIPS/eip-1014)
    create2:
            $ => C                          :MLOAD(txCalldataLen) 
            ; Check keccak counters
            C + 1                           :MSTORE(arithA)
            136                             :MSTORE(arithB), CALL(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB]
            $ => B                          :MLOAD(arithRes1)
            $ => A                          :MLOAD(cntKeccakPreProcess)
            ; -2 because we will use one more keccack for generating contract address
            %MAX_CNT_KECCAK_F - CNT_KECCAK_F - A - 2 => A
            $                               :LT, JMPC(outOfCountersKeccak)
    
            $ => CTX                        :MLOAD(originCTX)
            $ => B                          :MLOAD(argsOffsetCall)
    
    loopCreate2:    // 用于计算 keccak256(init_code)
            ; check zk-counters
            %MAX_CNT_STEPS - STEP - 100 :JMPN(outOfCountersStep)
            %MAX_CNT_BINARY - CNT_BINARY - 4 :JMPN(outOfCountersBinary)
    
            C                               :JMPZ(create2end)
            C - 32                          :JMPN(endloopCreate2)
            B => E                          :CALL(MLOAD32)
            E => B
            32 => D
            $ => E                          :MLOAD(lastHashKIdUsed)
            A                               :HASHK(E)
            C - 32 => C                     :JMP(loopCreate2)
    
    endloopCreate2:
            B => E                          :CALL(MLOADX)
            32 - C => D                     :CALL(SHRarith)
            C => D
            $ => E                          :MLOAD(lastHashKIdUsed)
            A                               :HASHK(E)
    
    create2end:
            $ => CTX                        :MLOAD(currentCTX)
            HASHPOS                         :HASHKLEN(E)
            $ => C                          :HASHKDIGEST(E)  // C 即为:用于计算 keccak256(init_code)
    
            ; new hash with position 0 is started
            0 => HASHPOS                        // keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]   
            $ => E                          :MLOAD(lastHashKIdUsed)
            E+1 => E                        :MSTORE(lastHashKIdUsed)
            0xff                            :HASHK1(E)
            20 => D
            $ => A                          :MLOAD(txSrcAddr)
            A                               :HASHK(E)
            32 => D
            $ => B                          :MLOAD(salt)
            B                               :HASHK(E)
            32 => D
            C                               :HASHK(E)
    
    endContractAddress:
            HASHPOS                         :HASHKLEN(E)
            $ => A                          :HASHKDIGEST(E), CALL(maskAddress) ; Mask address to 20 bytes 
                                                                // 得到合约地址
            A                               :MSTORE(createContractAddress)
            A                               :MSTORE(txDestAddr)
            A                               :MSTORE(storageAddr)
    

    得到的合约地址保存在 createContractAddress, txDestAddr, storageAddr 位置,皆存在CTX中。

    合约部署

    合约部署时首先将合约保存在warm address中;

    然后判断合约地址nonce为0, 否则会产生 deployAddreCollision 错误;

    然后将合约地址nonce 置为1, 并更新状态根 SR

    txValue 大于0, 则调用moveBalances 函数将from 资金给合约地址。

    deploy:
                                    :CALL(isColdAddress) ; add address to touched addresses
            ; check if address is deployable ( nonce == bytecode == 0)
            A => E
    
            ; read nonce
            0 => C
            %SMT_KEY_NONCE => B
            $ => B                      :SLOAD
            0 => A
            $                           :LT,JMPC(deployAddressCollision)
    
            ; read bytecode
            E => A
            %SMT_KEY_SC_CODE => B
            $ => B                      :SLOAD
            0 => A
            $                           :LT,JMPC(deployAddressCollision)
    
            ; set contract nonce to 1
            E => A
            1 => D
            %SMT_KEY_NONCE => B
            $ => SR                         :SSTORE
            ; Move balances if value > 0 just before deploy
            $ => B                          :MLOAD(txValue)
            0 => A
            zkPC+2 => RR
            $                               :LT, JMPC(moveBalances)
            0 => PC
            0 => SP                         :JMP(readCode)
    

    对于readDeployBytecode, 根据dataStarts 标记的交易databatchHash的位置,读取data , 然后执行相应的字节码。

    ;; read calldata bytes of a deploy transaction and process them
    readDeployBytecode:
            ; check transaction is a deploy transaction
            $ => B                          :MLOAD(isCreate), JMPNZ(readDeployBytecodeCreate)
            ; check enough bytes to read in calldata
            $ => B                          :MLOAD(txCalldataLen)
            B - PC - 1                      :JMPN(defaultOpCode)
            $ => HASHPOS                    :MLOAD(dataStarts)
            HASHPOS + PC => HASHPOS
            $ => E                          :MLOAD(batchHashDataId)
            $ => RR                         :HASHK1(E)
            $${eventLog(onOpcode(RR))}
            PC + 1 => PC                    :JMP(@mapping_opcodes + RR)
    

    对于CREATE/CREATE2创建合约的情形如下:

    ;; read calldata bytes of a CREATE/CREATE2 call and process them
    readDeployBytecodeCreate:
            $ => E                          :MLOAD(txCalldataLen)
            $ => CTX                        :MLOAD(originCTX)
            ; check enough bytes to read in memory
            E - PC - 1                      :JMPN(readDeployBytecodeCreateDefault)
            $ => E                          :MLOAD(argsOffsetCall)
            E + PC => E
            1 => C                          :CALL(MLOADX)
            $ => CTX                        :MLOAD(currentCTX)
            31 => D                         :CALL(SHRarith)
            A => RR
            $${eventLog(onOpcode(RR))}
            PC + 1 => PC                    :JMP(@mapping_opcodes + RR)
    

    最后将bytecode 长度保存在SMT中,并更新SR; 通过 hashPoseidonLinearFromMemory 调用,计算 hash bytecode, 并将bytecode 通过saveContractBytecode 保存在 ctx.input.contractBytecode中。

    ;; Compute and save hash bytecode and bytecode length in the state-tree
    endDeploy:
            ; called from `opRETURNDeploy` which has: C --> length, E --> offset
            ; only when first context ends on deploy
            ; If length is 0 do not modify state-tree
            C                               :JMPZ(handleGas)
    
            ; save offset memory and length to compute hash bytecode. Read bytecode first byte
            E                               :MSTORE(memOffsetLinearPoseidon)
            C                               :MSTORE(memSizeLinearPoseidon), CALL(checkBytecodeStartsEF)
    
            ; check bytecode first byte != 0xEF
            $                               :MLOAD(startsWithEF), JMPNZ(invalidCodeStartsEF)
    
            ; set bytecode length
            $ => A                          :MLOAD(createContractAddress)
            %SMT_KEY_SC_LENGTH => B
            C => D
            0 => C
            $ => SR                         :SSTORE
            A                               :MSTORE(txDestAddr), CALL(hashPoseidonLinearFromMemory)
            $ => A                          :MLOAD(createContractAddress)
            0 => C
            %SMT_KEY_SC_CODE => B
            $ => SR                         :SSTORE
    
    调用合约交易

    检查ctx.input.contractBytecode 的hash值是否和SMT的一致。

    callContract:
            ; Move balances if value > 0 just before executing the contract CALL
            $ => B                          :MLOAD(txValue)
            0 => A
            zkPC+2 => RR
            $                               :LT, JMPC(moveBalances)
            0 => PC
            0 => SP
    
            $ => A                          :MLOAD(txDestAddr)
    
            ; get contract length
            %SMT_KEY_SC_LENGTH => B
            0 => C
            $ => B                          :SLOAD
            B                               :MSTORE(bytecodeLength)
            0 => A
            $                               :EQ, JMPC(defaultOpCode) ;no bytecode
    
            ; check poseidon counters
            ; 56 is the value used by the prover to increment poseidon counters depending on the hash length
            B               :MSTORE(arithA)
            56              :MSTORE(arithB), CALL(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB]
            $ => B          :MLOAD(arithRes1)
            %MAX_CNT_POSEIDON_G - CNT_POSEIDON_G - 1 => A
            $               :LT, JMPC(outOfCountersPoseidon)
    
            $ => A                          :MLOAD(txDestAddr)
            ; get hash contract
            %SMT_KEY_SC_CODE => B
            $ => A                          :SLOAD
            A                               :MSTORE(hashContractTxDestAddr)
            0 => HASHPOS
            $ => B                          :MLOAD(bytecodeLength)
    
            ; get a new hashPId
            $ => E                          :MLOAD(nextHashPId)
            E                               :MSTORE(contractHashId)
            E+1                             :MSTORE(nextHashPId)
    
            ; check steps used by REPEAT
            %MAX_CNT_STEPS - STEP - B - 4   :JMPN(outOfCountersStep)
    
            ; set repeat itarations: RCX register + 1
            B - 1 => RCX                    :JMPN(checkHashBytecodeEnd)  // 计算 hash contract bytecode
            ${getBytecode(A, HASHPOS, 1)}   :HASHP1(E), REPEAT(RCX) ; hash contract bytecode
    
    checkHashBytecodeEnd:
            HASHPOS                         :HASHPLEN(E)
            $ => E                          :HASHPDIGEST(E)
            ; check hash computed matches hash in the smt leaf
            $ => A                          :MLOAD(hashContractTxDestAddr)
            E                               :ASSERT, JMP(readCode)
    

    然后处理字节码,此时交易calldata 存在CTX中的calldata中。

    readByteCode:
            $ => E                          :MLOAD(contractHashId) ; hash index
            $ => A                          :MLOAD(txDestAddr)
            ; check next byte exist on the bytecode
            $ => B                          :MLOAD(bytecodeLength)
            B - PC - 1                      :JMPN(defaultOpCode) ; no bytecode treated as 0x00
            PC => HASHPOS
            $ => RR                         :HASHP1(E)
            $${eventLog(onOpcode(RR))}
            PC + 1 => PC                    :JMP(@mapping_opcodes + RR)
    
    调用预编译合约

    目前仅支持 ECRECOVERIDENTITY 两个预编译合约。

    selectorPrecompiled:
        A - 2               :JMPN(funcECRECOVER)
        A - 3               :JMPN(callContract)  ;:JMPN(SHA256)
        A - 4               :JMPN(callContract)  ;:JMPN(RIPEMD160)
        A - 5               :JMPN(IDENTITY)
        A - 6               :JMPN(callContract) ;:JMPN(MODEXP)
        A - 7               :JMPN(callContract) ;:JMPN(ECADD)
        A - 8               :JMPN(callContract) ;:JMPN(ECMUL)
        A - 9               :JMPN(callContract) ;:JMPN(ECPAIRING)
        A - 10              :JMPN(callContract) ;:JMPN(BLAKE2F)
    
    Gas 处理

    检查计算要退回的 gas, 主要是计算min(gasRefund, (txGasLimit-GAS)/2)

    ;; compute maximum gas to refund
    handleGas:
            0 => A
            $ => B                          :MLOAD(gasRefund), JMPZ(refundGas)
            $ => A                          :MLOAD(txGasLimit)
            A - GAS => A
            ; Div operation with Arith
            A                               :MSTORE(arithA)
            2                               :MSTORE(arithB), CALL(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB]
            $ => A                          :MLOAD(arithRes1)
            A - B                           :JMPN(refundGas)
            B => A
    

    计算退回的 gas ,并添加到用户地址的余额中。

    ;; add remaining gas to transaction origin
    refundGas:
            GAS + A => GAS
            GAS => A
            $ => B                          :MLOAD(txGasPrice)
            ;Mul operation with Arith
            A                               :MSTORE(arithA)
            B                               :MSTORE(arithB), CALL(mulARITH)
            $ => D                          :MLOAD(arithRes1)
    
            $ => A                          :MLOAD(txSrcOriginAddr)
            0 => B,C ; balance key smt
            $ => A                          :SLOAD ; Original Balance in A
    
            ; Add operation with Arith
            A                               :MSTORE(arithA)
            D                               :MSTORE(arithB), CALL(addARITH)
            $ => D                          :MLOAD(arithRes1)
    
            $ => A                          :MLOAD(txSrcOriginAddr)
            0 => B,C                        ; balance key smt
            $ => SR                         :SSTORE
    

    将消耗掉的 gas 发送给sequencerAddr, 更新其地址余额。

    sendGasSeq: 
            $ => A          :MLOAD(txGasLimit)
            A - GAS => A
    
            $ => B          :MLOAD(txGasPrice)
            ; Mul operation with Arith
            A               :MSTORE(arithA)
            B               :MSTORE(arithB), CALL(mulARITH)
            $ => D          :MLOAD(arithRes1) ; value to pay the sequencer in D
    
            $ => A          :MLOAD(sequencerAddr)
            0 => B,C ; Balance key smt
            $ => A          :SLOAD ; Original Balance in A
            ; Add operation with Arith
            A               :MSTORE(arithA)
            D               :MSTORE(arithB), CALL(addARITH)
            $ => D          :MLOAD(arithRes1)
            $ => A          :MLOAD(sequencerAddr)
            0 => B,C ; balance key smt
            $ => SR         :SSTORE, JMP(processTxEnd) 
    
    更新系统合约数据

    首先更新系统合约中txCount个数,将其增加1 。然后保存当前交易处理之后的状态根到系统合约中。

    updateSystemData:
        ; check keccak counters
        $ => A                              :MLOAD(cntKeccakPreProcess)
        %MAX_CNT_KECCAK_F - CNT_KECCAK_F - A - 1:JMPN(outOfCountersKeccak)
    
        ; Get last tx count
        %LAST_TX_STORAGE_POS => C
        %ADDRESS_SYSTEM => A
        %SMT_KEY_SC_STORAGE => B
        $ => D                              :MLOAD(txCount)
        ;Update last tx Count at system storage
        D + 1 => D                          :MSTORE(txCount)
        $ => SR                             :SSTORE
    
        ;Update state root mapping
        D => A
        32 => D
        0 => HASHPOS ; A new hash with position 0 is started
        $ => E                              :MLOAD(lastHashKIdUsed)
        E + 1 => E                          :MSTORE(lastHashKIdUsed)
        A                                   :HASHK(E)
        %STATE_ROOT_STORAGE_POS             :HASHK(E)
        HASHPOS                             :HASHKLEN(E)
        $ => C                              :HASHKDIGEST(E)
        %ADDRESS_SYSTEM => A
        SR => D
        $ => SR                             :SSTORE, RETURN
    

    最后通过txLoop 处理下一笔交易 。

    Batch 计算

    首先从ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 读取 newLocalExitRoot;

    然后根据batchHashPosbatchL2DataLength比较验证所有交易大小是否一致;

    计算: batchHashData = keccak( transactions )

    计算:newAccInputHash =keccak(oldAccInputHash, batchHashData, globalExitRoot, timestamp, sequencerAddr)

    ;; Get local exit root
            ; Read 'localExitRoot' variable from GLOBAL_EXIT_ROOT_MANAGER_L2 and store
            ; it to the 'newLocalExitRoot' input
            %ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2  => A
            %SMT_KEY_SC_STORAGE => B
            %LOCAL_EXIT_ROOT_STORAGE_POS => C
            $ => A                                          :SLOAD
            A                                               :MSTORE(newLocalExitRoot)
    
    ;; Transactions size verification
            ; Ensure bytes added to compute the 'batchHashData' matches the number of bytes loaded from input
            $ => A                          :MLOAD(batchHashPos)
            $                               :MLOAD(batchL2DataLength), ASSERT
    
    ;; Compute 'batchHashData'
            A => HASHPOS
            $ => E                          :MLOAD(batchHashDataId)
    
            HASHPOS                         :HASHKLEN(E)
            $ => A                          :HASHKDIGEST(E)
    
            A                               :MSTORE(batchHashData)
    
    ;; Compute 'newAccInputHash'
            0 => HASHPOS
    
            32 => D
            $ => A                          :MLOAD(oldAccInputHash)
            A                               :HASHK(0)
    
            $ => A                          :MLOAD(batchHashData)
            A                               :HASHK(0)
    
            $ => A                          :MLOAD(globalExitRoot)
            A                               :HASHK(0)
    
            8 => D
            $ => A                          :MLOAD(timestamp)
            A                               :HASHK(0)
    
            20 => D
            $ => A                          :MLOAD(sequencerAddr)
            A                               :HASHK(0)
    
            HASHPOS                         :HASHKLEN(0)
    
            $ => C                          :HASHKDIGEST(0)
            C                               :MSTORE(newAccInputHash)
            $${eventLog(onFinishBatch)}
    

    Finalized execution

            ; Set output registers
            $ => D                          :MLOAD(newAccInputHash)
            $ => E                          :MLOAD(newLocalExitRoot)
            $ => PC                         :MLOAD(newNumBatch)
    
            ; Set registers to its initials values
            $ => B                          :MLOAD(oldStateRoot)
            $ => C                          :MLOAD(oldAccInputHash)
            $ => SP                         :MLOAD(oldNumBatch)
            $ => GAS                        :MLOAD(chainID)
            finalizeExecution:
                                            :JMP(finalWait)
    

    参考

    https://github.com/0xPolygonHermez/zkevm-rom

    https://github.com/0xPolygonHermez/zkevm-proverjs

    https://ethbook.abyteahead.com/ch4/rlp.html

    https://mirror.xyz/xyyme.eth/GNVcUgKAOEiLyClKeqkmD35ctLu6_XomT3ZDIfV3tz8

    https://mirror.xyz/xyyme.eth/6vqE2DRsMzlPNmh3kYiwTdMBj-9hanmxyDuTHM7tZDU

    https://mirror.xyz/xyyme.eth/6vqE2DRsMzlPNmh3kYiwTdMBj-9hanmxyDuTHM7tZDU

    https://www.evm.codes/

    相关文章

      网友评论

          本文标题:Polygon zkEVM ROM 技术解析

          本文链接:https://www.haomeiwen.com/subject/gkwevdtx.html