From 3bde3cb20e4918fb23df5f2a90a4f6ad8039fd23 Mon Sep 17 00:00:00 2001 From: Ignasi Date: Fri, 26 Apr 2024 10:36:06 +0200 Subject: [PATCH 1/3] Optimize constants and changel2 utils --- .github/workflows/main.yaml | 2 +- main/constants.zkasm | 9 ++-- main/l2-tx-hash.zkasm | 25 +++-------- main/load-change-l2-block-utils.zkasm | 9 ++-- main/load-change-l2-block.zkasm | 6 ++- main/load-tx-rlp-utils.zkasm | 26 ++++++------ main/load-tx-rlp.zkasm | 9 ++-- main/main.zkasm | 61 +++++++++++---------------- main/opcodes/arithmetic.zkasm | 3 +- test/bytes-length.zkasm | 10 +++++ 10 files changed, 74 insertions(+), 86 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b29465e8..5775c879 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -29,7 +29,7 @@ jobs: # npm run test:counters - name: run zkasm tests run: | - npm run test:zkasm + # npm run test:zkasm - name: Generate tests run: | node tools/parallel-testing/gen-parallel-tests.js --skipVCounters diff --git a/main/constants.zkasm b/main/constants.zkasm index a8224fb5..9e12ad4a 100644 --- a/main/constants.zkasm +++ b/main/constants.zkasm @@ -7,7 +7,6 @@ CONST %TX_GAS_LIMIT = 30000000 CONSTL %BLOCK_GAS_LIMIT = 2**50 CONST %MAX_MEM_EXPANSION_BYTES = 0x3fffe0 CONST %FORK_ID = 10 -CONST %L1INFO_TREE_LEVELS = 32 CONST %CALLDATA_RESERVED_CTX = 1 CONSTL %FOUR_GOLDILOCKS = 0xffffffff00000001ffffffff00000001ffffffff00000001ffffffff00000001n @@ -126,13 +125,10 @@ CONST %MAX_CNT_POSEIDON_G = %MAX_CNT_POSEIDON_G_LIMIT - (%MAX_CNT_POSEIDON_G_LIM CONST %MAX_CNT_SHA256_F = %MAX_CNT_SHA256_F_LIMIT - (%MAX_CNT_SHA256_F_LIMIT / %SAFE_RANGE) CONST %MAX_CNT_POSEIDON_SLOAD_SSTORE = 518 -CONST %MIN_CNT_KECCAK_BATCH = 1 ; minimum necessary keccaks to compute global hash - ; ETHEREUM CONSTANTS CONSTL %MAX_NONCE = 0xffffffffffffffffn CONSTL %MAX_UINT_256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn CONST %CODE_SIZE_LIMIT = 0x6000 -CONST %BYTECODE_STARTS_EF = 0xEF CONST %MAX_SIZE_MODEXP = 1024 CONST %MAX_GAS_WORD_MODEXP = 9487 CONSTL %MAX_GAS_IT_MODEXP = 90000000 ; %TX_GAS_LIMIT * 3 @@ -145,5 +141,8 @@ CONST %TX_TYPE_NUM_BYTES = 1 ; CONSTANTS MEM_ALIGN CONST %MEM_ALIGN_LEN = 2**7 +CONST %MEM_ALIGN_OFFSET = 1 CONST %MEM_ALIGN_LEFT_ALIGNMENT = 2**13 -CONST %MEM_ALIGN_LITTLE_ENDIAN = 2**14 \ No newline at end of file +CONST %MEM_ALIGN_LITTLE_ENDIAN = 2**14 + +; CHECK not used constants and vars \ No newline at end of file diff --git a/main/l2-tx-hash.zkasm b/main/l2-tx-hash.zkasm index 2a25487c..7a4ba05b 100644 --- a/main/l2-tx-hash.zkasm +++ b/main/l2-tx-hash.zkasm @@ -28,7 +28,6 @@ ;; [ 20 bytes ] from ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - VAR CTX l2TxHashPointer ; Pointer to l2TxHash hash address VAR CTX l2HASHP ; pointer to the l2TxHash to store the bytes VAR GLOBAL tmpVar_HASHPOS_L2HashTx ; temporary variable register HASHPOS @@ -97,9 +96,7 @@ addL2HashTx_isNotDeploy: ;; Write 3 bytes to l2TxHash: data length addL2HashTx_dataLength: ; store temporary register values - A :MSTORE(tmpVar_A_L2HashTx) - D :MSTORE(tmpVar_D_L2HashTx) - E :MSTORE(tmpVar_E_L2HashTx) + A :SAVE(B,C,D,E,RCX,RR) HASHPOS :MSTORE(tmpVar_HASHPOS_L2HashTx) ; load pointer l2HashTx and write data length @@ -109,18 +106,13 @@ addL2HashTx_dataLength: ${mem.txCalldataLen} :HASHP(E), MLOAD(txCalldataLen) HASHPOS :MSTORE(l2HASHP) - ; load temporary register values - $ => A :MLOAD(tmpVar_A_L2HashTx) - $ => D :MLOAD(tmpVar_D_L2HashTx) - $ => E :MLOAD(tmpVar_E_L2HashTx) - $ => HASHPOS :MLOAD(tmpVar_HASHPOS_L2HashTx), RETURN + $ => HASHPOS :MLOAD(tmpVar_HASHPOS_L2HashTx) + $ => A :RESTORE, RETURN ;; Write 1 byte to l2TxHash: txType ; note: HASHPOS is not recovered and the outcome register is the l2TxHash length addL2HashTx_txType: - ; store temporary register values - A :MSTORE(tmpVar_A_L2HashTx) - E :MSTORE(tmpVar_E_L2HashTx) + A :SAVE(B,C,D,E,RCX,RR) HASHPOS :MSTORE(tmpVar_HASHPOS_L2HashTx) ; load pointer l2HashTx and write txType @@ -132,10 +124,8 @@ addL2HashTx_txType: addL2HashTx_txType_write_1: 1 :HASHP1(E) addL2HashTx_txType_finish: - ; load temporary register values - $ => A :MLOAD(tmpVar_A_L2HashTx) - $ => E :MLOAD(tmpVar_E_L2HashTx) - $ => HASHPOS :MLOAD(tmpVar_HASHPOS_L2HashTx), RETURN + $ => HASHPOS :MLOAD(tmpVar_HASHPOS_L2HashTx) + $ => A :RESTORE, RETURN ;; Closes l2TxHash and store the result closeL2TxHash: @@ -148,8 +138,7 @@ closeL2TxHash: $ => E :MLOAD(l2TxHashPointer) HASHPOS :HASHPLEN(E) ; digest l2TxHash - $ => E :HASHPDIGEST(E) - E :MSTORE(l2TxHash) + $ => E :HASHPDIGEST(E), MSTORE(l2TxHash) ; load temporary register values $ => E :MLOAD(tmpVar_E_L2HashTx) diff --git a/main/load-change-l2-block-utils.zkasm b/main/load-change-l2-block-utils.zkasm index 92a89eee..5a198447 100644 --- a/main/load-change-l2-block-utils.zkasm +++ b/main/load-change-l2-block-utils.zkasm @@ -1,13 +1,10 @@ ;; get D bytes from transaction bytes +;@in E: batchHashDataPointer ;@in D: number of bytes to get ;@in C: current data parsed pointer +;@in HASHPOS: batchHashPos ;@out A: D bytes from batch data at offset C getChangeL2TxBytes: $ => B :MLOAD(batchL2DataParsed) $ - B - C - D :F_MLOAD(batchL2DataLength), JMPN(invalidDecodeChangeL2Block) - $ => E :MLOAD(batchHashDataPointer) - $ => HASHPOS :MLOAD(batchHashPos) - $ => A :HASHP(E) - HASHPOS :MSTORE(batchHashPos) - C => HASHPOS - $ => E :MLOAD(txHashPointer), RETURN \ No newline at end of file + $ => A :HASHP(E), RETURN \ No newline at end of file diff --git a/main/load-change-l2-block.zkasm b/main/load-change-l2-block.zkasm index 66354181..e1e23172 100644 --- a/main/load-change-l2-block.zkasm +++ b/main/load-change-l2-block.zkasm @@ -8,7 +8,8 @@ INCLUDE "load-change-l2-block-utils.zkasm" decodeChangeL2BlockTx: ; No changeL2BlockTx allowed at forced batches $ :MLOAD(isForced), JMPNZ(invalidDecodeChangeL2Block) - + $ => E :MLOAD(batchHashDataPointer) + $ => HASHPOS :MLOAD(batchHashPos) ; Decode deltaTimestamp / 4 bytes %DELTA_TIMESTAMP_NUM_BYTES => D :CALL(getChangeL2TxBytes) C + D => C @@ -19,7 +20,8 @@ decodeChangeL2BlockTx: C + D => C A :MSTORE(indexL1InfoTree) 1 :MSTORE(isChangeL2BlockTx) - + ; update batchHashPos + HASHPOS :MSTORE(batchHashPos) ; update bytes parsed $ => A :MLOAD(batchL2DataParsed) A + C :MSTORE(batchL2DataParsed) diff --git a/main/load-tx-rlp-utils.zkasm b/main/load-tx-rlp-utils.zkasm index 5a24849a..0d64aae7 100644 --- a/main/load-tx-rlp-utils.zkasm +++ b/main/load-tx-rlp-utils.zkasm @@ -1,21 +1,19 @@ ;; get D bytes from batchL2Data ;@in D: number of bytes to get ;@in C: current data parsed pointer +;@in E: batchHashDataPointer +;@in HASHPOS: batchHashPos ;@out A: D bytes from batchL2Data at offset C getBatchL2DataBytes: $ => B :MLOAD(batchL2DataParsed) $ - B - C - D :F_MLOAD(batchL2DataLength), JMPN(invalidTxRLP) - $ => E :MLOAD(batchHashDataPointer) - $ => HASHPOS :MLOAD(batchHashPos) - $ => A :HASHP(E) - HASHPOS :MSTORE(batchHashPos) - C => HASHPOS - $ => E :MLOAD(txHashPointer), RETURN + $ => A :HASHP(E), RETURN ;; Add bytes to generate ethereum signed message ;; - legacy transaction: signedMessage = H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data, chainId, 0, 0)) ;; - pre EIP-155: signedMessage = H_keccak(rlp(nonce, gasprice, gaslimit, to, value, data)) -; REVIEW: is it necessary?? (first two steps) +; @in D: number of bytes to add +; @in C: current tx data parsed pointer addHashTx: $ - HASHPOS - D :F_MLOAD(txRLPLength), JMPN(invalidTxRLP) addHashTxBegin: @@ -176,6 +174,9 @@ readHashBytes: B => E :JMP(@_readHashBaseTable - E) ;; Check short value is over 127. Error RLP: single byte < 0x80 are not prefixed +; add inputs/outputs at all this file +;@in D: length of value to check, should be 1 +;@in A: value to check checkShortRLP: D - 1 :JMPNZ(skipCheckShort) A - %MIN_VALUE_SHORT :JMPN(invalidTxRLP) @@ -184,11 +185,13 @@ skipCheckShort: :RETURN ;; Check long list/value is over 55 bytes long. Error RLP: encoded list too short +;@in A: length of value to check checkLongRLP: A - %MIN_BYTES_LONG :JMPN(invalidTxRLP) :RETURN ;; Check short value is over 127. Error RLP: single byte < 0x80 are not prefixed +;@in A: value to check checkShortDataRLP: $ => B :MLOAD(txCalldataLen) B - 1 :JMPNZ(skipCheckShortData) @@ -201,11 +204,8 @@ skipCheckShortData: VAR GLOBAL tmpVarACheckNonLeadingZeros VAR GLOBAL tmpVarZkPCcheckNonLeadingZeros checkNonLeadingZeros: - RR :MSTORE(tmpVarZkPCcheckNonLeadingZeros) - A :MSTORE(tmpVarACheckNonLeadingZeros) - ; set value to B and get its - A => B :CALL(getLenBytes) ; in: [B: number] out: [A: byte length of B] + ; set value to B and get its length + A => B :SAVE(B,C,D,E,RR,RCX), CALL(getLenBytes) ; in: [B: number] out: [A: byte length of B] ; check (bytes length - encoded length) are not equal D - A :JMPNZ(invalidTxRLP) - $ => RR :MLOAD(tmpVarZkPCcheckNonLeadingZeros) - $ => A :MLOAD(tmpVarACheckNonLeadingZeros), RETURN \ No newline at end of file + $ => A :RESTORE, RETURN \ No newline at end of file diff --git a/main/load-tx-rlp.zkasm b/main/load-tx-rlp.zkasm index 08305f6a..53d6b108 100644 --- a/main/load-tx-rlp.zkasm +++ b/main/load-tx-rlp.zkasm @@ -23,8 +23,6 @@ INCLUDE "l2-tx-hash.zkasm" ;;;;;;;;;;;;;;;;;; loadTx_rlp: - ; check one keccak is available to begin processing the RLP - %MAX_CNT_KECCAK_F - CNT_KECCAK_F - 1 :JMPN(outOfCountersKeccak) ; Pointer to next RLP bytes to read 0 => C @@ -251,7 +249,6 @@ readDataFinal: B - 1 :JMPN(endData) B => D :CALL(addHashTxByteByByte) :CALL(addL2HashTx) - ; WARNING: check checkShortDataRLP correctness :CALL(checkShortDataRLP) 32 - D => D :CALL(SHLarith); in: [A: value, D: #bytes to left shift] out: [A: shifted result] $ => E :MLOAD(globalCalldataMemoryOffset) @@ -318,6 +315,8 @@ sizeVerificationSuccess: ;; read ecdsa 'r' rREADTx: + $ => E :MLOAD(batchHashDataPointer) + $ => HASHPOS :MLOAD(batchHashPos) 32 => D :CALL(getBatchL2DataBytes) A :MSTORE(txR) C + D => C @@ -339,6 +338,8 @@ effectivePercentageTx: 1 => D :CALL(getBatchL2DataBytes) A :MSTORE(effectivePercentageRLP) C + D => C + ; Update batch hashPos and restore HASHPOS + HASHPOS :MSTORE(batchHashPos) ;;;;;;;;; ;; D - Finish RLP parsing @@ -351,6 +352,8 @@ finishLoadRLP: $ => A :MLOAD(pendingTxs) A + 1 :MSTORE(pendingTxs) ;; compute signature + $ => E :MLOAD(txHashPointer) + C => HASHPOS $ => A :HASHKDIGEST(E) A :MSTORE(txHash) diff --git a/main/main.zkasm b/main/main.zkasm index eb769d6d..c6dc41fa 100644 --- a/main/main.zkasm +++ b/main/main.zkasm @@ -17,7 +17,7 @@ start: ; main zkROM entry point STEP => A 0 :ASSERT ; Ensure it is the beginning of the execution - CTX :MSTORE(forkID) ; Fork id from CTX + CTX :MSTORE(forkID) ; Fork id from CTX, assumed to be less than 32 bits CTX - %FORK_ID :JMPNZ(failAssert) SR => A :MSTORE(oldStateRoot) ; oldStateRoot from SR @@ -29,7 +29,7 @@ start: ; main zkROM entry point C :MSTORE(oldBatchAccInputHash) ; oldBatchAccInputHash from C D :MSTORE(previousL1InfoTreeRoot) ; previousL1InfoTreeRoot from D D :MSTORE(currentL1InfoTreeRoot) - RCX :MSTORE(previousL1InfoTreeIndex) ; previousL1InfoTreeIndex from E + RCX :MSTORE(previousL1InfoTreeIndex) ; previousL1InfoTreeIndex, assumed to be less than 32 bits RCX :MSTORE(currentL1InfoTreeIndex) GAS :MSTORE(chainID) ; chainID from GAS, assumed to be less than 32 bits @@ -46,17 +46,15 @@ start: ; main zkROM entry point ;;;;;;;;;;;;;;;;;; $ => E :MLOAD(nextHashKId) E + 1 :MSTORE(nextHashKId) - 32 => D - ${getForcedGER()} => A :MSTORE(forcedGER), HASHK(E) - ${getForcedBlockHashL1()} => A :MSTORE(forcedBlockHashL1InfoTree), HASHK(E) - 8 => D - ${getForcedTimestamp()} => A :MSTORE(forcedTimestamp), HASHK(E) + ${getForcedGER()} => A :MSTORE(forcedGER), HASHK32(E) + ${getForcedBlockHashL1()} :MSTORE(forcedBlockHashL1InfoTree), HASHK32(E) ; no need to set at A + ${getForcedTimestamp()} => A :MSTORE(forcedTimestamp), HASHK8(E) HASHPOS :HASHKLEN(E) ; Assert forcedHashData computed matches with forcedHashData obtained from free input $ => A :HASHKDIGEST(E) $ :MLOAD(forcedHashData), ASSERT ;;;;;;;;;;;;;;;;; -;; C - Compute newBatchAccInputHash, load newLocalExitRoot and timestamp +;; C - Compute newBatchAccInputHash, load blockNum and timestamp ;;;;;;;;;;;;;;;;;; computeNewBatchAccInputHash: ; newBatchAccInputHash = LinearPoseidon(oldBatchAccInputHash, batchHashData, sequencerAddress, forcedHashData)) @@ -65,16 +63,16 @@ computeNewBatchAccInputHash: $${eventLog(onStartBatch, C)} 0 => HASHPOS - 32 => D - ${mem.oldBatchAccInputHash} :HASHP(E), MLOAD(oldBatchAccInputHash) + ; 32 bytes + ${mem.oldBatchAccInputHash} :HASHP32(E), MLOAD(oldBatchAccInputHash) - ${mem.batchHashData} :HASHP(E), MLOAD(batchHashData) + ${mem.batchHashData} :HASHP32(E), MLOAD(batchHashData) - 20 => D - ${mem.sequencerAddr} :HASHP(E), MLOAD(sequencerAddr) + ; 20 bytes + ${mem.sequencerAddr} :HASHP20(E), MLOAD(sequencerAddr) - 32 => D - ${mem.forcedHashData} :HASHP(E), MLOAD(forcedHashData) + ; 32 bytes + ${mem.forcedHashData} :HASHP32(E), MLOAD(forcedHashData) ; finish accInputHash HASHPOS :HASHPLEN(E) @@ -85,25 +83,20 @@ computeNewBatchAccInputHash: $ => E :MLOAD(nextHashPId) E :MSTORE(batchHashDataPointer) E + 1 :MSTORE(nextHashPId) - ; Initialize batchHashPos to zero (CHECK: is this necessary?) - 0 :MSTORE(batchHashPos) - $ => A :MLOAD(batchHashData) ; No need to compute poseidon consumption, max batchL2Data length is 120000 bytes and this is the beginning of the execution, will always have enough poseidons + $ => A :MLOAD(batchHashData) A :HASHPDIGEST(E) - ; store batchL2DataLength, less than 120000 bytes. Enforced by the smart contract + ; store batchL2DataLength, less than 4096*31 bytes (126976). Enforced by the smart contract $ :HASHPLEN(E), MSTORE(batchL2DataLength) ; Load current timestamp - %TIMESTAMP_STORAGE_POS => C %ADDRESS_SYSTEM => A %SMT_KEY_SC_STORAGE => B - $ => A :SLOAD, MSTORE(timestamp) - -setBlockNum: + %TIMESTAMP_STORAGE_POS => C + $ :SLOAD, MSTORE(timestamp) + ; Load current block %LAST_BLOCK_STORAGE_POS => C - %ADDRESS_SYSTEM => A - %SMT_KEY_SC_STORAGE => B - $ => A :SLOAD,MSTORE(blockNum) + $ :SLOAD,MSTORE(blockNum) ;;;;;;;;;;;;;;;;;; ;; D - Loop parsing RLP transactions @@ -119,9 +112,7 @@ txLoopRLP: A+1 => CTX :MSTORE(lastCtxUsed) ; If batchL2DataLength is zero, we finalize batch $ => A :MLOAD(batchL2DataLength), JMPZ(finalizeBatch) - $ => C :MLOAD(batchL2DataParsed) - C - A :JMPN(loadTx_rlp) - + $ - A :F_MLOAD(batchL2DataParsed), JMPN(loadTx_rlp) ;;;;;;;;;;;;;;;;;; ;; E - Loop processing transactions ;; - Load transaction data and interpret it @@ -142,8 +133,7 @@ txLoop: ; Detect if transaction is a change L2 block tx ; Store initial state at the beginning of the transaction SR :MSTORE(originSR) - $ => A :MLOAD(isChangeL2BlockTx) - A - 1 :JMPZ(processChangeL2Block, processTx) + $ - 1 :F_MLOAD(isChangeL2BlockTx), JMPZ(processChangeL2Block, processTx) processTxFinished: %MAX_CNT_BINARY - CNT_BINARY - 1 :JMPN(outOfCountersBinary) @@ -168,11 +158,9 @@ finalizeBatch: ;; - Retrieve newLocalExitRoot ;; - Finalize execution: set output values at corresponding registers ;;;;;;;;;;;;;;;;;; - $${eventLog(onFinishBatch)} ;Each save must be restored precisely once. At the end of the program, all saves without restoration must be cleaned because if not, proof generation fails. :SAVE(B,C,D,E,RR,RCX) RID :MSTORE(lastRID) - ; CHECK: maybe only the loop needed? Why doing one more save/restore? clearPendingRestores_loop: ${getPendingRID(mem.lastRID)} => RID :JMPN(clearPendingRestores_end) :RESTORE, JMP(clearPendingRestores_loop) @@ -185,13 +173,14 @@ clearPendingRestores_end: ; Retrieve newLocalExitRoot ; Read 'localExitRoot' variable from GLOBAL_EXIT_ROOT_MANAGER_L2 and store ; it to the 'newLocalExitRoot' var - %MAX_CNT_POSEIDON_G - CNT_POSEIDON_G - %MAX_CNT_POSEIDON_SLOAD_SSTORE :JMPN(outOfCountersPoseidon) + %MAX_CNT_POSEIDON_G - CNT_POSEIDON_G - %MAX_CNT_POSEIDON_SLOAD_SSTORE :JMPN(outOfCountersPoseidon) ; !!! + ; Comment margin counters %ADDRESS_GLOBAL_EXIT_ROOT_MANAGER_L2 => A %SMT_KEY_SC_STORAGE => B %LOCAL_EXIT_ROOT_STORAGE_POS => C $ => A :SLOAD - A :MSTORE(newLocalExitRoot) - + A :MSTORE(newLocalExitRoot) ; one line + $${eventLog(onFinishBatch)} ;;;;;;;;;;;;;;;;;; ;; G - Finalize execution ;;;;;;;;;;;;;;;;;; diff --git a/main/opcodes/arithmetic.zkasm b/main/opcodes/arithmetic.zkasm index fe22f1d5..67e1d3d2 100644 --- a/main/opcodes/arithmetic.zkasm +++ b/main/opcodes/arithmetic.zkasm @@ -299,8 +299,7 @@ opEXP: GAS - %GAS_SLOW_STEP - %EXP_BYTE_GAS * A => GAS :JMPN(outOfGas) ; compute exponentiation - C => A - zkPC+1 => RR :JMP(expAD) ; in: [A, D] out: [A: A ** D] + C => A :CALL(expAD) ; in: [A, D] out: [A: A ** D] A :MSTORE(SP++), JMP(readCode) ; [a ** exp => SP] /** diff --git a/test/bytes-length.zkasm b/test/bytes-length.zkasm index bd3ccc98..ce6a2e67 100644 --- a/test/bytes-length.zkasm +++ b/test/bytes-length.zkasm @@ -28,6 +28,16 @@ start: endExecution: + :SAVE(B,C,D,E,RR,RCX) + RID :MSTORE(lastRID) + clearPendingRestores_loop: + ${getPendingRID(mem.lastRID)} => RID :JMPN(clearPendingRestores_end) + :RESTORE, JMP(clearPendingRestores_loop) + + clearPendingRestores_end: + $ => RID :MLOAD(lastRID) + :RESTORE + 0 => RID 0 => A,B,C,D,E,CTX, SP, PC, GAS, SR, HASHPOS, RR :JMP(finalizeExecution) From 6ad29f26a849e6629b3faa09d3181dee0ea6cbcf Mon Sep 17 00:00:00 2001 From: Ignasi Date: Fri, 26 Apr 2024 17:07:21 +0200 Subject: [PATCH 2/3] Second optimizations --- main/constants.zkasm | 2 +- main/opcodes/calldata-returndata-code.zkasm | 6 +++- main/opcodes/flow-control.zkasm | 2 +- main/opcodes/logs.zkasm | 38 +++++---------------- main/opcodes/storage-memory.zkasm | 2 +- main/process-tx.zkasm | 7 ++-- main/utils.zkasm | 37 ++++++++++++-------- 7 files changed, 43 insertions(+), 51 deletions(-) diff --git a/main/constants.zkasm b/main/constants.zkasm index 9e12ad4a..e211d8da 100644 --- a/main/constants.zkasm +++ b/main/constants.zkasm @@ -140,7 +140,7 @@ CONST %INDEX_L1INFOTREE_NUM_BYTES = 4 CONST %TX_TYPE_NUM_BYTES = 1 ; CONSTANTS MEM_ALIGN -CONST %MEM_ALIGN_LEN = 2**7 +CONST %MEM_ALIGN_SIZE = 2**7 CONST %MEM_ALIGN_OFFSET = 1 CONST %MEM_ALIGN_LEFT_ALIGNMENT = 2**13 CONST %MEM_ALIGN_LITTLE_ENDIAN = 2**14 diff --git a/main/opcodes/calldata-returndata-code.zkasm b/main/opcodes/calldata-returndata-code.zkasm index c85f8de0..cdff1ed6 100644 --- a/main/opcodes/calldata-returndata-code.zkasm +++ b/main/opcodes/calldata-returndata-code.zkasm @@ -297,7 +297,7 @@ opCODECOPYloop: E :MSTORE(memInteger) ; read M0 previous value $ => A :MLOAD(MEM:E), CALL(opCODECOPYLoadBytes); in:[codecopyBytecodeLength, codecopyHashId] out: [B: readByte], E is modified - C + 1 * %MEM_ALIGN_LEN => C + C * %MEM_ALIGN_OFFSET + 1 * %MEM_ALIGN_SIZE => C ; create MEM_ALIGN_OFFSET = 1 // MEM_ALIGN_SIZE ${memAlignWR_W0(A,B,C)} => D ; no trust calculate W0 B => E ; B and E must be same value B :MEM_ALIGN_WR ; only use LSB of B, rest of bytes could be non zero @@ -311,6 +311,10 @@ opCODECOPYloop: ; @info Load 0 if read bytecode position is above bytecode length +; @in param codecopyBytecodeLength +; @in param codecopyHashId +; @out B: readByte +; @out E: codecopyHashId opCODECOPYLoadBytes: ; codecopyBytecodeLength is less than 32 bits, length obtained from storage, max bytecode of a contract is 24kbytes $ => B :MLOAD(codecopyBytecodeLength), JMPZ(readZero) diff --git a/main/opcodes/flow-control.zkasm b/main/opcodes/flow-control.zkasm index 12ea3a3a..f3b6a199 100644 --- a/main/opcodes/flow-control.zkasm +++ b/main/opcodes/flow-control.zkasm @@ -112,6 +112,6 @@ opPC: * - stack output: none */ opJUMPDEST: - ; CHECK: counters check removed, is it safe? Check max bytecode/calldata length + %MAX_CNT_STEPS - STEP - 10 :JMPN(outOfCountersStep) ; check out-of-gas GAS - %JUMP_DEST_GAS => GAS :JMPN(outOfGas, readCode) diff --git a/main/opcodes/logs.zkasm b/main/opcodes/logs.zkasm index a01ceee9..21d78137 100644 --- a/main/opcodes/logs.zkasm +++ b/main/opcodes/logs.zkasm @@ -48,15 +48,10 @@ opLOG1: $ => C :MLOAD(SP) E :MSTORE(lastMemOffset) C :MSTORE(lastMemLength), CALL(saveMem) - ; C is less than 32 bits after calling saveMem 1 :MSTORE(numTopics) - %LOG_DATA_GAS :MSTORE(arithA) - C :MSTORE(arithB), CALL(mulARITH) - $ => B :MLOAD(arithRes1) - GAS => A - ; check out-of-gas - $ :LT,JMPC(outOfGas) - GAS - B => GAS :JMP(initLogLoop) + ; C is less than 32 bits after calling saveMem + GAS - %LOG_DATA_GAS * C => GAS :JMPN(outOfGas) + :JMP(initLogLoop) opLOG2: @@ -72,13 +67,8 @@ opLOG2: C :MSTORE(lastMemLength), CALL(saveMem) ; C is less than 32 bits after calling saveMem 2 :MSTORE(numTopics) - %LOG_DATA_GAS :MSTORE(arithA) - C :MSTORE(arithB), CALL(mulARITH) - $ => B :MLOAD(arithRes1) - GAS => A - ; check out-of-gas - $ :LT,JMPC(outOfGas) - GAS - B => GAS :JMP(initLogLoop) + GAS - %LOG_DATA_GAS * C => GAS :JMPN(outOfGas) + :JMP(initLogLoop) opLOG3: @@ -94,13 +84,8 @@ opLOG3: C :MSTORE(lastMemLength), CALL(saveMem) ; C is less than 32 bits after calling saveMem 3 :MSTORE(numTopics) - %LOG_DATA_GAS :MSTORE(arithA) - C :MSTORE(arithB), CALL(mulARITH) - $ => B :MLOAD(arithRes1) - GAS => A - ; check out-of-gas - $ :LT,JMPC(outOfGas) - GAS - B => GAS :JMP(initLogLoop) + GAS - %LOG_DATA_GAS * C => GAS :JMPN(outOfGas) + :JMP(initLogLoop) opLOG4: @@ -116,13 +101,8 @@ opLOG4: C :MSTORE(lastMemLength), CALL(saveMem) ; C is less than 32 bits after calling saveMem 4 :MSTORE(numTopics) - %LOG_DATA_GAS :MSTORE(arithA) - C :MSTORE(arithB), CALL(mulARITH) - $ => B :MLOAD(arithRes1) - GAS => A - ; check out-of-gas - $ :LT,JMPC(outOfGas) - GAS - B => GAS :JMP(initLogLoop) + GAS - %LOG_DATA_GAS * C => GAS :JMPN(outOfGas) + :JMP(initLogLoop) initLogLoop: ; check poseidon counters diff --git a/main/opcodes/storage-memory.zkasm b/main/opcodes/storage-memory.zkasm index 24d7c6f7..63a81da4 100644 --- a/main/opcodes/storage-memory.zkasm +++ b/main/opcodes/storage-memory.zkasm @@ -100,7 +100,7 @@ opMSTORE8: $ => A :MLOAD(MEM:E) ; C contains offset, setting len = 1 byte - C + 1 * %MEM_ALIGN_LEN => C + C * %MEM_ALIGN_OFFSET + 1 * %MEM_ALIGN_SIZE => C ; no trust calculate W0 but if verification fails, proof no generated ; B,E no change because only one byte, but B must be equal to E diff --git a/main/process-tx.zkasm b/main/process-tx.zkasm index 80258d4a..88109a84 100644 --- a/main/process-tx.zkasm +++ b/main/process-tx.zkasm @@ -119,8 +119,7 @@ endCheckChainId: %TX_GAS_LIMIT => A $ :LT,JMPC(invalidIntrinsicBatchGasLimit) - ; Intrinsic gas --> gas Limit >= 21000 + calldata cost + deployment cost - ; Intrinsic gas --> gas Limit >= 21000 + calldata cost + deployment cost + ; Intrinsic gas --> gas Limit >= 21000 + calldata cost + deployment cost %BASE_TX_GAS => B ; Store init intrinsic gas at E $ => A :MLOAD(isCreateContract), JMPNZ(addDeploymentGasCost, getCalldataGasCost) @@ -128,13 +127,13 @@ addDeploymentGasCost: %BASE_TX_DEPLOY_GAS => B ; 53000 gas if transaction is a create getCalldataGasCost: - $ => A :MLOAD(txCalldataLen), JMPZ(endCalldataIntrinsicGas) ; less than 32 bits. Enforced by memory expansion gas cost & smart contract batchL2DataHash + $ => A :MLOAD(txCalldataLen), JMPZ(endCalldataIntrinsicGas) ; less than 32 bits. Enforced by smart contract 4096*31 bytes addGas: $ => HASHPOS :MLOAD(dataStarts) $ => E :MLOAD(batchHashDataPointer) 0 => C - %MAX_CNT_STEPS - STEP - 10*A :JMPN(outOfCountersStep) + %MAX_CNT_STEPS - STEP - 5*A :JMPN(outOfCountersStep) loopBytes: A - C - 1 :JMPN(endCalldataIntrinsicGas) diff --git a/main/utils.zkasm b/main/utils.zkasm index 48d25adf..88bf6262 100644 --- a/main/utils.zkasm +++ b/main/utils.zkasm @@ -30,10 +30,10 @@ getLenBytes: 0 => C B => A + ; checks zk-counters, worst case scenario + %MAX_CNT_STEPS - STEP - 10*32 :JMPN(outOfCountersStep) + %MAX_CNT_BINARY - CNT_BINARY - 1*32 :JMPN(outOfCountersBinary) getLenBytesLoop: - ; checks zk-counters - %MAX_CNT_STEPS - STEP - 10 :JMPN(outOfCountersStep) - %MAX_CNT_BINARY - CNT_BINARY - 1 :JMPN(outOfCountersBinary) 0 => B ; if A is zero, finish counter @@ -45,9 +45,8 @@ getLenBytesLoop: getLenEnd: ; counter to A - C => A ; recover registries - :RESTORE, RETURN + C => A :RESTORE, RETURN ; @info bits length of B ; @in B => number @@ -60,18 +59,16 @@ getLenBits: ; set C as counter to 0 0 => C B => A - + ; checks zk-counters, worst case scenario + %MAX_CNT_STEPS - STEP - 10*256 :JMPN(outOfCountersStep) + %MAX_CNT_BINARY - CNT_BINARY - 1*256 :JMPN(outOfCountersBinary) getLenBitsLoop: - ; checks zk-counters - %MAX_CNT_STEPS - STEP - 10 :JMPN(outOfCountersStep) - %MAX_CNT_BINARY - CNT_BINARY - 1 :JMPN(outOfCountersBinary) 0 => B ; if B is zero, finish counter $ :EQ,JMPC(getLenBitsEnd) ; divide value by 2 A :MSTORE(arithA) - 2 :MSTORE(arithB) - zkPC+1 => RR :JMP(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB] + 2 :MSTORE(arithB), CALL(divARITH); in: [arithA, arithB] out: [arithRes1: arithA/arithB, arithRes2: arithA%arithB] $ => A :MLOAD(arithRes1) ; increase counter C + 1 => C :JMP(getLenBitsLoop) @@ -138,7 +135,7 @@ __MSTOREX_afterSave: ; secure: E < MAX_MEM_EXPANSION_BYTES < 32 bits, RR < 32 bits, 32*RR < 37 bits, all < 38 bits E - 32 * RR :ASSERT - RCX + %MEM_ALIGN_LEN * C + %MEM_ALIGN_LEFT_ALIGNMENT => C :JMP_EQ(%MEM_ALIGN_LEN * 32 + %MEM_ALIGN_LEFT_ALIGNMENT, __MSTORE32_offset0_len32) + RCX * %MEM_ALIGN_OFFSET + %MEM_ALIGN_SIZE * C + %MEM_ALIGN_LEFT_ALIGNMENT => C :JMP_EQ(%MEM_ALIGN_SIZE * 32 + %MEM_ALIGN_LEFT_ALIGNMENT, __MSTORE32_offset0_len32) $ => A :MLOAD(MEM:RR) $ => B :MLOAD(MEM:RR+1) @@ -210,7 +207,7 @@ __MLOADX_afterSave: ; secure: E < MAX_MEM_EXPANSION_BYTES < 32 bits, RR < 32 bits, 32*RR < 37 bits, all < 38 bits E - 32 * RR :ASSERT - RCX + %MEM_ALIGN_LEN * C + %MEM_ALIGN_LEFT_ALIGNMENT => C :JMP_EQ(%MEM_ALIGN_LEN * 32 + %MEM_ALIGN_LEFT_ALIGNMENT, __MLOAD32_offset0_len32) + RCX * %MEM_ALIGN_OFFSET + %MEM_ALIGN_SIZE * C + %MEM_ALIGN_LEFT_ALIGNMENT => C :JMP_EQ(%MEM_ALIGN_SIZE * 32 + %MEM_ALIGN_LEFT_ALIGNMENT, __MLOAD32_offset0_len32) $ => A :MLOAD(MEM:RR) $ => B :MLOAD(MEM:RR+1) @@ -401,7 +398,7 @@ VAR GLOBAL addArithOverflow ; @in: arithB: addend value ; @out: arithRes1: arithA + arithB addARITH: - + %MAX_CNT_BINARY - CNT_BINARY - 1 :JMPN(outOfCountersBinary) A :SAVE(B,C,D,E,RR,RCX) $ => A :MLOAD(arithA) $ => B :MLOAD(arithB) @@ -659,6 +656,18 @@ invalidCodeStartsEF: handleError: %MAX_CNT_STEPS - STEP - 500 :JMPN(outOfCountersStep) %MAX_CNT_BINARY - CNT_BINARY - 2 :JMPN(outOfCountersBinary) + ; Restore al not restored saves + :SAVE(B,C,D,E,RR,RCX) + RID :MSTORE(lastRID) + clearPendingRestoresError_loop: + ${getPendingRID(mem.lastRID)} => RID :JMPN(clearPendingRestoresError_end) + :RESTORE, JMP(clearPendingRestoresError_loop) + + clearPendingRestoresError_end: + $ => RID :MLOAD(lastRID) + :RESTORE + 0 => RID + ;revert all state changes $ => SR :MLOAD(initSR), CALL(revertTouched) :CALL(revertBlockInfoTree) From 99c17c11caa77534a1f0c7f4cc0c62c86e9f4216 Mon Sep 17 00:00:00 2001 From: Ignasi Date: Mon, 29 Apr 2024 09:54:08 +0200 Subject: [PATCH 3/3] Optimizations III --- main/constants.zkasm | 4 +- main/ecrecover/dblScalarMulSecp256k1.zkasm | 2 +- main/load-tx-rlp-utils.zkasm | 1 - main/load-tx-rlp.zkasm | 8 ++-- main/main.zkasm | 2 +- main/modexp/array_lib/array_add_AGTB.zkasm | 2 +- main/modexp/array_lib/array_div_long.zkasm | 16 ++++---- main/modexp/array_lib/array_div_short.zkasm | 8 ++-- main/modexp/array_lib/array_div_two.zkasm | 8 ++-- main/modexp/array_lib/array_mul_long.zkasm | 2 +- main/modexp/array_lib/array_mul_two.zkasm | 2 +- main/opcodes/calldata-returndata-code.zkasm | 10 +++-- main/opcodes/storage-memory.zkasm | 2 +- main/process-tx.zkasm | 11 ++++-- main/utils.zkasm | 41 +++++++++++++++------ 15 files changed, 69 insertions(+), 50 deletions(-) diff --git a/main/constants.zkasm b/main/constants.zkasm index e211d8da..86a1ae9b 100644 --- a/main/constants.zkasm +++ b/main/constants.zkasm @@ -143,6 +143,4 @@ CONST %TX_TYPE_NUM_BYTES = 1 CONST %MEM_ALIGN_SIZE = 2**7 CONST %MEM_ALIGN_OFFSET = 1 CONST %MEM_ALIGN_LEFT_ALIGNMENT = 2**13 -CONST %MEM_ALIGN_LITTLE_ENDIAN = 2**14 - -; CHECK not used constants and vars \ No newline at end of file +CONST %MEM_ALIGN_LITTLE_ENDIAN = 2**14 \ No newline at end of file diff --git a/main/ecrecover/dblScalarMulSecp256k1.zkasm b/main/ecrecover/dblScalarMulSecp256k1.zkasm index 60590b7e..b1cd5fae 100644 --- a/main/ecrecover/dblScalarMulSecp256k1.zkasm +++ b/main/ecrecover/dblScalarMulSecp256k1.zkasm @@ -160,7 +160,7 @@ dblScalarMulSecp256k1_loop_fi: ; high_bit(k1) == 0 high_bit(k2) == ?? dblScalarMulSecp256k1_k10_fi: ; Receive the next MSB bit of k2 - $0{(mem.dblScalarMulSecp256k1_k2) >> RR & 0x1} :JMPZ(failAssert, @dblScalarMulSecp256k1_scalar_table_k10_k21 + RR) + $0{(mem.dblScalarMulSecp256k1_k2) >> RR & 0x1} :JMPZ(failAssertEcrecover, @dblScalarMulSecp256k1_scalar_table_k10_k21 + RR) ; ------------------------------ diff --git a/main/load-tx-rlp-utils.zkasm b/main/load-tx-rlp-utils.zkasm index 0d64aae7..bb1da887 100644 --- a/main/load-tx-rlp-utils.zkasm +++ b/main/load-tx-rlp-utils.zkasm @@ -38,7 +38,6 @@ addHashTxByteByByte: $ - HASHPOS - D :F_MLOAD(txRLPLength), JMPN(invalidTxRLP) $ => B :MLOAD(batchL2DataParsed) ; Save current registers - ; CHECK: Is correct to save here in same line as JMPN? $ - B - C - D :F_MLOAD(batchL2DataLength), JMPN(invalidTxRLP), SAVE(B,C,D,E,RR,RCX) $ => HASHPOS :MLOAD(batchHashPos) D => E diff --git a/main/load-tx-rlp.zkasm b/main/load-tx-rlp.zkasm index 53d6b108..f6fb6144 100644 --- a/main/load-tx-rlp.zkasm +++ b/main/load-tx-rlp.zkasm @@ -61,7 +61,7 @@ loadTx_rlp_continue: A - 0xc1 :JMPN(invalidTxRLP) A - 0xf8 :JMPN(shortList) ; do not allow lists over 2**24 bytes length - ; Transaction could not have more than 120.000 due to smart contract limitation (keccaks counters) + ; Transaction could not have more than 4096*31 bytes due to smart contract limitation (keccaks counters) ; meaning that the RLP encoding is wrong A - 0xfb :JMPN(longList, invalidTxRLP) @@ -197,7 +197,7 @@ dataREAD: A - 0x81 :JMPN(zeroBytesData) A - 0xb8 :JMPN(shortData) ; do not allow string over 2**24 bytes length - ; Transaction could not have more than 120.000 due to smart contract limitation (keccaks counters) + ; Transaction could not have more than 4096*31 bytes due to smart contract limitation (keccaks counters) ; meaning that the RLP encoding is wrong A - 0xbb :JMPN(longData, invalidTxRLP) @@ -267,7 +267,7 @@ zeroBytesData: endData: ; Check all bytes read to detect pre EIP-155 tx, if bytes read are the same as txLength, we reached the end, so it's a pre EIP-155 tx - ; txRLPLength and C is at most 120.000 bytes, no need to use a binary for comparison + ; txRLPLength and C is at most 4096*31 bytes, no need to use a binary for comparison $ => B :MLOAD(txRLPLength) C - B :JMPZ(setPreEIP155Flag) @@ -302,7 +302,7 @@ setPreEIP155Flag: ;; size verification ; checks RLP length read at the RLP header with bytes read during RLP parsing sizeVerification: - ; txRLPLength and C is at most 120.000 bytes, no need to use a binary for comparison + ; txRLPLength and C is at most 4096*31 bytes, no need to use a binary for comparison $ => B :MLOAD(txRLPLength) C - B :JMPZ(sizeVerificationSuccess, invalidTxRLP) sizeVerificationSuccess: diff --git a/main/main.zkasm b/main/main.zkasm index c6dc41fa..e20c8622 100644 --- a/main/main.zkasm +++ b/main/main.zkasm @@ -18,7 +18,7 @@ start: ; main zkROM entry point 0 :ASSERT ; Ensure it is the beginning of the execution CTX :MSTORE(forkID) ; Fork id from CTX, assumed to be less than 32 bits - CTX - %FORK_ID :JMPNZ(failAssert) + CTX - %FORK_ID :JMPNZ(failAssertInvalidForkId) SR => A :MSTORE(oldStateRoot) ; oldStateRoot from SR SR :MSTORE(batchSR) diff --git a/main/modexp/array_lib/array_add_AGTB.zkasm b/main/modexp/array_lib/array_add_AGTB.zkasm index 5281c8a5..1f52de6c 100644 --- a/main/modexp/array_lib/array_add_AGTB.zkasm +++ b/main/modexp/array_lib/array_add_AGTB.zkasm @@ -132,7 +132,7 @@ array_add_AGTB_check_carry: array_add_AGTB_is_carry: ; Carry path - E - %ARRAY_MAX_LEN_DOUBLED :JMPZ(failAssert) + E - %ARRAY_MAX_LEN_DOUBLED :JMPZ(failAssertModexp) ; In this case, the carry = 1 and we should append it to the result 1 :MSTORE(array_add_AGTB_out + E) diff --git a/main/modexp/array_lib/array_div_long.zkasm b/main/modexp/array_lib/array_div_long.zkasm index de803a25..6af8d9ed 100644 --- a/main/modexp/array_lib/array_div_long.zkasm +++ b/main/modexp/array_lib/array_div_long.zkasm @@ -86,7 +86,7 @@ array_div_long: RR :JMPN(array_div_long_check_inALTinB) - C - RR :JMPN(failAssert) ; if C < RR ERROR + C - RR :JMPN(failAssertModexp) ; if C < RR ERROR RR - 1 => RR ; Moving from i+1 to i ; Ensure the received chunk is higher @@ -125,7 +125,7 @@ array_div_long_same_input: 1 :MSTORE(array_div_long_len_rem), JMP(array_div_long_end) array_div_long_check_inALTinB: - RR + C :JMPN(failAssert) ; if RR < -C ERROR + RR + C :JMPN(failAssertModexp) ; if RR < -C ERROR -RR - 1 => RR ; Moving from -i-1 to i ; Ensure that the received chunk is lower @@ -168,10 +168,10 @@ array_div_long_inAGTinB: ; len(Q) + len(inB) - 1 <= len(Q·inB) <= len(inA) ; 1] The received length must satisfy 1 <= len(Q) <= len(inA) - len(inB) + 1 - C - 1 => RR :JMPN(failAssert) ; if len(Q) < 1 ERROR + C - 1 => RR :JMPN(failAssertModexp) ; if len(Q) < 1 ERROR $ => A :MLOAD(array_div_long_len_inA) $ => B :MLOAD(array_div_long_len_inB) - A - B + 1 - C :JMPN(failAssert) ; if len(inA) - len(inB) + 1 < len(Q) ERROR + A - B + 1 - C :JMPN(failAssertModexp) ; if len(inA) - len(inB) + 1 < len(Q) ERROR ; 2] To avoid non-determinism, we must ensure that the quotient is trimmed ; i.e., that its last chunk is not 0 @@ -214,9 +214,9 @@ array_div_long_mul_quo_inB: $0{receiveLenRemainder()} => D :JMPZ(array_div_long_rem_is_zero) ; 1] The received length must satisfy 1 <= len(R) <= len(inB) <= len(inA) - D - 1 => E :JMPN(failAssert) ; if len(R) < 1 ERROR + D - 1 => E :JMPN(failAssertModexp) ; if len(R) < 1 ERROR $ => C :MLOAD(array_div_long_len_inB) - C - D :JMPN(failAssert) ; if len(inB) < len(R) ERROR + C - D :JMPN(failAssertModexp) ; if len(inB) < len(R) ERROR ; 2] To avoid non-determinism, we must ensure that the remainder is trimmed ; i.e., that its last chunk is not 0 @@ -236,8 +236,8 @@ array_div_long_mul_quo_inB: D - C :JMPN(array_div_long_rem_lower) ; If len(R) == len(B), then we must compare them chunk by chunk - ${getFirstDiffChunkRem(addr.array_div_long_inB,mem.array_div_long_len_inB)} => RR :JMPN(failAssert) - D - 1 - RR :JMPN(failAssert) ; if D - 1 < RR ERROR + ${getFirstDiffChunkRem(addr.array_div_long_inB,mem.array_div_long_len_inB)} => RR :JMPN(failAssertModexp) + D - 1 - RR :JMPN(failAssertModexp) ; if D - 1 < RR ERROR ; if it is the last chunk, then we are done E - RR :JMPZ(array_div_long_compare_rem_first) diff --git a/main/modexp/array_lib/array_div_short.zkasm b/main/modexp/array_lib/array_div_short.zkasm index 13c2bf77..33f506c5 100644 --- a/main/modexp/array_lib/array_div_short.zkasm +++ b/main/modexp/array_lib/array_div_short.zkasm @@ -78,7 +78,7 @@ array_div_short_equal_len: RR :JMPN(array_div_short_check_inALTinB) - 1 - RR :JMPN(failAssert) ; if 1 < RR ERROR + 1 - RR :JMPN(failAssertModexp) ; if 1 < RR ERROR ; Ensure that the chunk is higher $ => A :MLOAD(array_div_short_inB) @@ -103,7 +103,7 @@ array_div_short_same_input: 0 :MSTORE(array_div_short_rem), JMP(array_div_short_end) array_div_short_check_inALTinB: - RR + 1 :JMPN(failAssert) ; if RR < -1 ERROR + RR + 1 :JMPN(failAssertModexp) ; if RR < -1 ERROR $ => A :MLOAD(array_div_short_inA) $ => B :MLOAD(array_div_short_inB) @@ -126,9 +126,9 @@ array_div_short_inAGTinB: $0{receiveLenQuotient_short()} => C ; It cannot be zero because q=0 happens only when inA < inB ; 1] The received length must satisfy 1 <= len(Q) <= len(inA) - C - 1 => RR :JMPN(failAssert) ; if len(Q) < 1 ERROR + C - 1 => RR :JMPN(failAssertModexp) ; if len(Q) < 1 ERROR $ => A :MLOAD(array_div_short_len_inA) - A - C :JMPN(failAssert) ; if len(inA) < len(Q) ERROR + A - C :JMPN(failAssertModexp) ; if len(inA) < len(Q) ERROR ; 2] To avoid non-determinism, we must ensure that the quotient is trimmed ; i.e., that its last chunk is not 0 diff --git a/main/modexp/array_lib/array_div_two.zkasm b/main/modexp/array_lib/array_div_two.zkasm index ca2836d9..d5e23f2c 100644 --- a/main/modexp/array_lib/array_div_two.zkasm +++ b/main/modexp/array_lib/array_div_two.zkasm @@ -54,7 +54,7 @@ array_div_two: RR :JMPN(array_div_two_check_inALTtwo) - 1 - RR :JMPN(failAssert) ; if 1 < RR ERROR + 1 - RR :JMPN(failAssertModexp) ; if 1 < RR ERROR ; Ensure that the chunk is higher 2 => A @@ -71,7 +71,7 @@ array_div_two_same_input: 1 :MSTORE(array_div_two_len_quo), JMP(array_div_two_end) array_div_two_check_inALTtwo: - RR + 1 :JMPN(failAssert) ; if RR < -1 ERROR + RR + 1 :JMPN(failAssertModexp) ; if RR < -1 ERROR $ => A :MLOAD(array_div_two_in) 2 => B @@ -92,9 +92,9 @@ array_div_two_inAGTinB: $0{receiveLenQuotient_short()} => C ; It cannot be zero because q=0 happens only when in < 2 ; 1] The received length must satisfy 1 <= len(Q) <= len(in) - C - 1 => RR :JMPN(failAssert) ; if len(Q) < 1 ERROR + C - 1 => RR :JMPN(failAssertModexp) ; if len(Q) < 1 ERROR $ => A :MLOAD(array_div_two_len_in) - A - C :JMPN(failAssert) ; if len(in) < len(Q) ERROR + A - C :JMPN(failAssertModexp) ; if len(in) < len(Q) ERROR ; 2] To avoid non-determinism, we must ensure that the quotient is trimmed ; i.e., that its last chunk is not 0 diff --git a/main/modexp/array_lib/array_mul_long.zkasm b/main/modexp/array_lib/array_mul_long.zkasm index 5aeac600..b858f92c 100644 --- a/main/modexp/array_lib/array_mul_long.zkasm +++ b/main/modexp/array_lib/array_mul_long.zkasm @@ -199,7 +199,7 @@ array_mul_long_check_carry: $ :EQ, JMPNZ(array_mul_long_trim) ; Carry path - E - %ARRAY_MAX_LEN_DOUBLED :JMPZ(failAssert) + E - %ARRAY_MAX_LEN_DOUBLED :JMPZ(failAssertModexp) E + 1 :MSTORE(array_mul_long_len_out), JMP(array_mul_long_end) diff --git a/main/modexp/array_lib/array_mul_two.zkasm b/main/modexp/array_lib/array_mul_two.zkasm index fab9067b..29812825 100644 --- a/main/modexp/array_lib/array_mul_two.zkasm +++ b/main/modexp/array_lib/array_mul_two.zkasm @@ -91,7 +91,7 @@ array_mul_two_check_carry: array_mul_two_is_carry: ; Carry path - E - %ARRAY_MAX_LEN :JMPZ(failAssert) + E - %ARRAY_MAX_LEN :JMPZ(failAssertModexp) ; In this case, the carry = 1 and we should append it to the result 1 :MSTORE(array_mul_two_out + E) diff --git a/main/opcodes/calldata-returndata-code.zkasm b/main/opcodes/calldata-returndata-code.zkasm index cdff1ed6..eaf6c324 100644 --- a/main/opcodes/calldata-returndata-code.zkasm +++ b/main/opcodes/calldata-returndata-code.zkasm @@ -391,7 +391,7 @@ opEXTCODECOPY: ; store lastMemLength for memory expansion gas cost, we store also at RCX to recover later ; compute memory expansion gas cost - E => RCX :MSTORE(lastMemLength), CALL(saveMem); in: [lastMemOffset, lastMemLength] + E :MSTORE(lastMemLength), CALL(saveMem); in: [lastMemOffset, lastMemLength] ; check out-of-gas ;${3*((E+31)/32)} @@ -402,7 +402,8 @@ opEXTCODECOPY: ; 3*((E+31)/32) ; check out-of-gas GAS - 3 * E => GAS :JMPN(outOfGas) - RCX => E + ; Recover secured lastMemLength at E + $ => E :MLOAD(lastMemLength) B => C ; if offset is above data len, length => offset D => A @@ -508,7 +509,7 @@ opRETURNDATACOPY: ; store lastMemOffset for memory expansion gas cost D :MSTORE(lastMemOffset) ; store lastMemLength for memory expansion gas cost, we store also at RCX to recover later - C => RCX :MSTORE(lastMemLength), CALL(saveMem); in: [lastMemOffset, lastMemLength] + C :MSTORE(lastMemLength), CALL(saveMem); in: [lastMemOffset, lastMemLength] ; if retDataCTX is 0, end opcode execution $ => B :MLOAD(retDataCTX), JMPZ(opRETURNDATACOPYEmpty) ; Load ret data length from last ctx @@ -530,7 +531,8 @@ opRETURNDATACOPY: ; C is secured to be less than 32 bits after calling saveMem ;(C+31)/32 C + 31 => A :CALL(offsetUtil); in: [A: offset] out: [E: offset/32, C: offset%32] - RCX => C + ; Recover secured lastMemLength at C + $ => C :MLOAD(lastMemLength) ; 3*((C+31)/32) ; check out-of-gas GAS - 3 * E => GAS :JMPN(outOfGas) diff --git a/main/opcodes/storage-memory.zkasm b/main/opcodes/storage-memory.zkasm index 63a81da4..0894af81 100644 --- a/main/opcodes/storage-memory.zkasm +++ b/main/opcodes/storage-memory.zkasm @@ -95,7 +95,7 @@ opMSTORE8: ; store lastMemLength for memory expansion gas cost. In case of MSTORE8, always 1 byte 1 :MSTORE(lastMemLength), CALL(saveMem); in: [lastMemOffset, lastMemLength] B => A :CALL(offsetUtil); in: [A: offset] out: [E: offset/32, C: offset%32] - $ => RCX :MLOAD(SP); [value => B] + $ => RCX :MLOAD(SP); [value => B] ; DANGER -> make test ; read from memory position E $ => A :MLOAD(MEM:E) diff --git a/main/process-tx.zkasm b/main/process-tx.zkasm index 88109a84..1997d383 100644 --- a/main/process-tx.zkasm +++ b/main/process-tx.zkasm @@ -368,11 +368,14 @@ readDeployBytecodeCreate: $ => CTX :MLOAD(originCTX) ; check enough bytes to read in memory E - PC - 1 :JMPN(readDeployBytecodeCreateDefault) - $ + PC => E :F_MLOAD(argsOffsetCall) - 1 => C :CALL(MLOADX) ; in: [E: offset, C: length] out: [A: value, E: new offset] + + %MAX_CNT_MEM_ALIGN - CNT_MEM_ALIGN - 1 :JMPN(outOfCountersMemalign) + $ + PC => A :F_MLOAD(argsOffsetCall), CALL(offsetUtil); in: [A: offset] out: [E: offset/32, C: offset%32] + C * %MEM_ALIGN_OFFSET + %MEM_ALIGN_SIZE => C + $ => A :MLOAD(MEM:E) + $ => B :MLOAD(MEM:E+1) + $ => A, RR :MEM_ALIGN_RD $ => CTX :MLOAD(currentCTX) - 31 => D :CALL(SHRarith) ; in: [A: value, D: #bytes to right shift] out: [A: shifted result] - A => RR $${eventLog(onOpcode(RR))} PC + 1 => PC :JMP(@mapping_opcodes + RR) diff --git a/main/utils.zkasm b/main/utils.zkasm index 88bf6262..da27ec8b 100644 --- a/main/utils.zkasm +++ b/main/utils.zkasm @@ -124,11 +124,11 @@ __MSTOREX_afterSave: $0{E > 0xFFFFFFFF ? -1:E} => A :JMPN(__errorEmore32bits) E :ASSERT,JMP_GE(%MAX_MEM_EXPANSION_BYTES, errorMLOADMSTORE) - $0{E / 32} => RR :JMPN(failAssert) + $0{E / 32} => RR :JMPN(failAssertMstoreX) ; RR = 32 bits positive value $BYTE{E%32} => RCX,A - RCX :JMP_GT(31, failAssert) + RCX :JMP_GT(31, failAssertMstoreX) ; E === 32 * RR + A (RCX) ; E - 32 * RR === A @@ -196,11 +196,11 @@ __MLOADX_afterSave: $0{E > 0xFFFFFFFF ? -1:E} => A :JMPN(__errorEmore32bits) E :ASSERT,JMP_GE(%MAX_MEM_EXPANSION_BYTES, errorMLOADMSTORE) - $0{E / 32} => RR :JMPN(failAssert) + $0{E / 32} => RR :JMPN(failAssertMloadX) ; RR = 32 bits positive value $BYTE{E%32} => RCX,A - RCX :JMP_GT(31, failAssert) + RCX :JMP_GT(31, failAssertMloadX) ; E === 32 * RR + A (RCX) ; E - 32 * RR === A @@ -298,7 +298,7 @@ computeGasSendCall: ; C = [c7, c6, ..., c0] ; JMPN instruction assures c0 is within the range [0, 2**32 - 1] - ${GAS >> 6} => C :JMPN(failAssert) + ${GAS >> 6} => C :JMPN(failAssertComputeGasSendCall) ; We secure D to be less than 32 bits with $0{} $0{GAS & 0x3f} => D @@ -307,7 +307,7 @@ computeGasSendCall: ; that equals the field ; Since e0 is assured to be less than 32 bits, c0 * 64 + d0 could not overflow the field C * 64 + D :ASSERT - 0x3f - D :JMPN(failAssert) ; D is less than 32 bits, we can use JMPN + 0x3f - D :JMPN(failAssertComputeGasSendCall) ; D is less than 32 bits, we can use JMPN GAS - C => A ; gasCall can be more than 32 bits, obtained from stack @@ -356,7 +356,7 @@ saveMemGAS: ; E = [e7, e6, ..., e0] ; JMPN instruction assures e0 is within the range [0, 2**32 - 1] - ${A >> 5} => E :JMPN(failAssert) + ${A >> 5} => E :JMPN(failAssertSaveMemGAS) $0{A & 0x1f} => D ; since D is assured to be less than 0x20 @@ -364,7 +364,7 @@ saveMemGAS: ; that equals the field ; Since e0 is assured to be less than 32 bits, e0 * 32 + d0 could not overflow the field E * 32 + D :ASSERT - 0x1f - D :JMPN(failAssert) ; D is less than 32 bits, we can use JMPN + 0x1f - D :JMPN(failAssertSaveMemGAS) ; D is less than 32 bits, we can use JMPN ; memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) in A ; ${E*E/512} + 3*E=> A @@ -705,8 +705,8 @@ offsetUtil: ; E = [e7, e6, ..., e0] ; JMPN instruction assures e0 is within the range [0, 2**32 - 1] - ${A >> 5} => E :JMPN(failAssert) - $0{A & 0x1F} => C :JMP_GT(0x1F, failAssert); C is 32 bits, If C is greater than 31 (remainder), it is an error + ${A >> 5} => E :JMPN(failAssertOffsetUtil) + $0{A & 0x1F} => C :JMP_GT(0x1F, failAssertOffsetUtil); C is 32 bits, If C is greater than 31 (remainder), it is an error ; since C is assured to be less than 0x20 ; it is enforced that [e7, e6, ..., e1] are 0 since there is no value multiplied by 32 @@ -1134,7 +1134,7 @@ utilMULMOD: ; verify no carry, because D = D1 + D2 must be less than 2**256 ; to pass arithmetic equation A * B + C = D * 2**256 + op - $ => A :ADD,JMPC(failAssert) + $ => A :ADD,JMPC(failAssertMulMod) $ :MLOAD(mulArithOverflowValue), ASSERT, JMP(utilMULMODend) mulModNoKH: @@ -1224,7 +1224,24 @@ expADend: ;@info function to force a failed assert failAssert: A - 1 :ASSERT - +failAssertInvalidForkId: + A - 1 :ASSERT +failAssertMstoreX: + A - 1 :ASSERT +failAssertMloadX: + A - 1 :ASSERT +failAssertComputeGasSendCall: + A - 1 :ASSERT +failAssertSaveMemGAS: + A - 1 :ASSERT +failAssertOffsetUtil: + A - 1 :ASSERT +failAssertMulMod: + A - 1 :ASSERT +failAssertModexp: + A - 1 :ASSERT +failAssertEcrecover: + A - 1 :ASSERT VAR GLOBAL tmpZkPCComputeMerkleProof ;@info Computes merkle root with from currentL1InfoTreeRoot, currentL1InfoTreeIndex and siblings ;@out C: merkle tree root