From 81a65a7c5d418294fb9e7bf763a40750cea634cd Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Sun, 24 Jul 2022 11:01:29 +0800 Subject: [PATCH 1/5] feat: initial changes --- src/zkevm_specs/evm/execution/__init__.py | 2 + src/zkevm_specs/evm/execution/calldatacopy.py | 2 +- src/zkevm_specs/evm/execution/codecopy.py | 2 +- src/zkevm_specs/evm/execution/log.py | 2 +- src/zkevm_specs/evm/execution/sha3.py | 39 +++++++++++ src/zkevm_specs/evm/instruction.py | 7 +- src/zkevm_specs/evm/table.py | 64 +++++++++++++------ src/zkevm_specs/evm/typing.py | 23 +++++++ 8 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 src/zkevm_specs/evm/execution/sha3.py diff --git a/src/zkevm_specs/evm/execution/__init__.py b/src/zkevm_specs/evm/execution/__init__.py index 8aedfbbf9..d1b4db432 100644 --- a/src/zkevm_specs/evm/execution/__init__.py +++ b/src/zkevm_specs/evm/execution/__init__.py @@ -33,6 +33,7 @@ from .selfbalance import * from .extcodehash import * from .log import * +from .sha3 import sha3 from .shr import shr from .bitwise import not_opcode from .sdiv_smod import sdiv_smod @@ -63,6 +64,7 @@ ExecutionState.PUSH: push, ExecutionState.SCMP: scmp, ExecutionState.GAS: gas, + ExecutionState.SHA3: sha3, ExecutionState.SLOAD: sload, ExecutionState.SSTORE: sstore, ExecutionState.SELFBALANCE: selfbalance, diff --git a/src/zkevm_specs/evm/execution/calldatacopy.py b/src/zkevm_specs/evm/execution/calldatacopy.py index b225e2791..1b6185344 100644 --- a/src/zkevm_specs/evm/execution/calldatacopy.py +++ b/src/zkevm_specs/evm/execution/calldatacopy.py @@ -39,7 +39,7 @@ def calldatacopy(instruction: Instruction): FQ(instruction.curr.is_root), FQ(CopyDataTypeTag.TxCalldata), FQ(CopyDataTypeTag.Memory) ) if instruction.is_zero(length) == 0: - copy_rwc_inc = instruction.copy_lookup( + copy_rwc_inc, _ = instruction.copy_lookup( src_id, CopyDataTypeTag(src_type.n), instruction.curr.call_id, diff --git a/src/zkevm_specs/evm/execution/codecopy.py b/src/zkevm_specs/evm/execution/codecopy.py index 60486c408..56223ffdc 100644 --- a/src/zkevm_specs/evm/execution/codecopy.py +++ b/src/zkevm_specs/evm/execution/codecopy.py @@ -24,7 +24,7 @@ def codecopy(instruction: Instruction): gas_cost = instruction.memory_copier_gas_cost(size, memory_expansion_gas_cost) if instruction.is_zero(size) == FQ(0): - copy_rwc_inc = instruction.copy_lookup( + copy_rwc_inc, _ = instruction.copy_lookup( instruction.curr.code_hash, CopyDataTypeTag.Bytecode, instruction.curr.call_id, diff --git a/src/zkevm_specs/evm/execution/log.py b/src/zkevm_specs/evm/execution/log.py index e6b9da75c..a2935b0f6 100644 --- a/src/zkevm_specs/evm/execution/log.py +++ b/src/zkevm_specs/evm/execution/log.py @@ -63,7 +63,7 @@ def log(instruction: Instruction): instruction.constrain_bool(FQ(diff)) if instruction.is_zero(msize) == 0 and is_persistent == 1: - copy_rwc_inc = instruction.copy_lookup( + copy_rwc_inc, _ = instruction.copy_lookup( instruction.curr.call_id, CopyDataTypeTag.Memory, tx_id, diff --git a/src/zkevm_specs/evm/execution/sha3.py b/src/zkevm_specs/evm/execution/sha3.py new file mode 100644 index 000000000..41e29e915 --- /dev/null +++ b/src/zkevm_specs/evm/execution/sha3.py @@ -0,0 +1,39 @@ +from ..instruction import Instruction, Transition +from zkevm_specs.util import FQ, RLC + + +def sha3(instruction: Instruction): + opcode = instruction.opcode_lookup(True) + + # byte offset in memory. + offset = instruction.stack_pop() + # byte size to read in memory. + size = instruction.stack_pop() + + # convert RLC encoded stack elements to FQ. + memory_offset, length = instruction.memory_offset_and_length(offset, size) + + # calculate memory expansion gas costs. + next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( + memory_offset, length + ) + gas_cost = instruction.memory_copier_gas_cost(length, memory_expansion_gas_cost) + + # TODO(rohit): lookup copy table (copy_rwc_inc and rlc_acc) + copy_rwc_inc = FQ(0) + # TODO(rohit): lookup keccak table (keccak-256 of rlc_acc) + keccak256_rlc_acc = FQ(0) + + instruction.constrain_equal( + keccak256_rlc_acc, + instruction.stack_push(), + ) + + instruction.step_state_transition_in_same_context( + opcode, + rw_counter=Transition.delta(instruction.rw_counter_offset + copy_rwc_inc), + program_counter=Transition.delta(1), + stack_pointer=Transition.delta(2), + memory_size=Transition.to(next_memory_size), + dynamic_gas_cost=gas_cost, + ) diff --git a/src/zkevm_specs/evm/instruction.py b/src/zkevm_specs/evm/instruction.py index 371578e39..1aaf91104 100644 --- a/src/zkevm_specs/evm/instruction.py +++ b/src/zkevm_specs/evm/instruction.py @@ -1021,8 +1021,8 @@ def copy_lookup( length: Expression, rw_counter: Expression, log_id: Expression = None, - ) -> FQ: - return self.tables.copy_lookup( + ) -> Tuple[FQ, FQ]: + copy_table_row = self.tables.copy_lookup( src_id, FQ(src_type), dst_id, @@ -1033,4 +1033,5 @@ def copy_lookup( length, rw_counter, log_id, - ).rwc_inc + ) + return copy_table_row.rwc_inc, copy_table_row.value_rlc diff --git a/src/zkevm_specs/evm/table.py b/src/zkevm_specs/evm/table.py index 1bfb0b341..3366f7a2a 100644 --- a/src/zkevm_specs/evm/table.py +++ b/src/zkevm_specs/evm/table.py @@ -418,10 +418,18 @@ class CopyTableRow(TableRow): src_addr_end: FQ dst_addr: FQ length: FQ + value_rlc: FQ rw_counter: FQ rwc_inc: FQ +@dataclass(frozen=True) +class KeccakTableRow(TableRow): + idx: FQ + hash_rlc: FQ + value_rlc: FQ + + class Tables: """ A collection of lookup tables used in EVM circuit. @@ -433,6 +441,7 @@ class Tables: bytecode_table: Set[BytecodeTableRow] rw_table: Set[RWTableRow] copy_table: Set[CopyTableRow] + keccak_table: Set[KeccakTableRow] def __init__( self, @@ -441,6 +450,7 @@ def __init__( bytecode_table: Set[BytecodeTableRow], rw_table: Union[Set[Sequence[Expression]], Set[RWTableRow]], copy_circuit: Sequence[CopyCircuitRow] = None, + keccak_table: Sequence[KeccakTableRow] = None, ) -> None: self.block_table = block_table self.tx_table = tx_table @@ -451,29 +461,38 @@ def __init__( ) if copy_circuit is not None: self.copy_table = self._convert_copy_circuit_to_table(copy_circuit) + if keccak_table is not None: + self.keccak_table = set(keccak_table) def _convert_copy_circuit_to_table(self, copy_circuit: Sequence[CopyCircuitRow]): - rows = [] + rows: List[CopyTableRow] = [] for i, row in enumerate(copy_circuit): - if row.is_first != 1: - continue - assert i + 1 < len(copy_circuit), "Not enough rows in copy circuit" - next_row = copy_circuit[i + 1] - assert next_row.q_step == 0, "Invalid copy circuit" - rows.append( - CopyTableRow( - src_id=row.id, - src_type=row.tag, - dst_id=next_row.id, - dst_type=next_row.tag, - src_addr=row.addr, - src_addr_end=row.src_addr_end, - dst_addr=next_row.addr, - length=row.bytes_left, - rw_counter=row.rw_counter, - rwc_inc=row.rwc_inc_left, + # the first row and the row next to it will be used for its fields. + if row.is_first == 1: + first_row = row + assert i + 1 < len(copy_circuit), "Not enough rows in copy circuit" + next_row = copy_circuit[i + 1] + assert next_row.q_step == 0, "Invalid copy circuit" + + # update `value_rlc` when we encounter the copy event's last row. + if row.is_last == 1: + value_rlc = row.value + rows.append( + CopyTableRow( + src_id=first_row.id, + src_type=first_row.tag, + dst_id=next_row.id, + dst_type=next_row.tag, + src_addr=first_row.addr, + src_addr_end=first_row.src_addr_end, + dst_addr=next_row.addr, + length=first_row.bytes_left, + value_rlc=value_rlc, + rw_counter=first_row.rw_counter, + rwc_inc=first_row.rwc_inc_left, + ) ) - ) + return set(rows) def fixed_lookup( @@ -581,6 +600,13 @@ def copy_lookup( } return lookup(CopyTableRow, self.copy_table, query) + def keccak_lookup(self, length: Expression, value_rlc: Expression): + query = { + "idx": length - FQ(1), + "value_rlc": value_rlc, + } + return lookup(KeccakTableRow, self.keccak_table, query) + T = TypeVar("T", bound=TableRow) diff --git a/src/zkevm_specs/evm/typing.py b/src/zkevm_specs/evm/typing.py index 159fe275e..b4547febc 100644 --- a/src/zkevm_specs/evm/typing.py +++ b/src/zkevm_specs/evm/typing.py @@ -45,6 +45,7 @@ CopyDataTypeTag, CopyCircuitRow, CopyTableRow, + KeccakTableRow, ) from .opcode import get_push_size, Opcode @@ -666,6 +667,28 @@ def _append( return self +class KeccakCircuit: + rows: List[KeccakTableRow] + + def __init__(self) -> None: + self.rows = [] + + def add(self, data: bytes, r: FQ): + rows: List[KeccakTableRow] = [] + hash_rlc = RLC(keccak256(RLC(bytes(reversed(data)), r).le_bytes), r).expr() + value_rlc = FQ(0) + for i in range(len(data)): + value_rlc = value_rlc * r + data[i] + rows.append( + KeccakTableRow( + idx=FQ(i), + hash_rlc=hash_rlc, + value_rlc=value_rlc, + ) + ) + self.rows.extend(rows) + + class CopyCircuit: rows: List[CopyCircuitRow] pad_rows: List[CopyCircuitRow] From 50b7fda48dc95490bc25607b2c2ffa7674911939 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Sun, 24 Jul 2022 11:03:46 +0800 Subject: [PATCH 2/5] fix: rebase and fix return_ opcode as per new copy lookup api --- src/zkevm_specs/evm/execution/return_.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zkevm_specs/evm/execution/return_.py b/src/zkevm_specs/evm/execution/return_.py index 7bcdc7cd8..4ec78c77b 100644 --- a/src/zkevm_specs/evm/execution/return_.py +++ b/src/zkevm_specs/evm/execution/return_.py @@ -35,7 +35,7 @@ def return_(instruction: Instruction): # callee's memory to bytecode, using the copy circuit. code_hash = instruction.curr.aux_data # Load code_hash witness value from aux_data copy_length = return_length - copy_rwc_inc = instruction.copy_lookup( + copy_rwc_inc, _ = instruction.copy_lookup( instruction.curr.call_id, # src_id CopyDataTypeTag.Memory, # src_type code_hash, # dst_id @@ -62,7 +62,7 @@ def return_(instruction: Instruction): CallContextFieldTag.ReturnDataLength ) # rwc += 1 copy_length = instruction.min(return_length, caller_return_length, N_BYTES_MEMORY_ADDRESS) - copy_rwc_inc = instruction.copy_lookup( + copy_rwc_inc, _ = instruction.copy_lookup( instruction.curr.call_id, # src_id CopyDataTypeTag.Memory, # src_type instruction.next.call_id, # dst_id From 5424918914efdbfdb9382730b739b576c3d3c73b Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Sun, 24 Jul 2022 11:17:32 +0800 Subject: [PATCH 3/5] feat: add copy and keccak lookup --- src/zkevm_specs/evm/execution/sha3.py | 17 +++++++++++++---- src/zkevm_specs/evm/instruction.py | 3 +++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/zkevm_specs/evm/execution/sha3.py b/src/zkevm_specs/evm/execution/sha3.py index 41e29e915..07988c0f7 100644 --- a/src/zkevm_specs/evm/execution/sha3.py +++ b/src/zkevm_specs/evm/execution/sha3.py @@ -1,4 +1,5 @@ from ..instruction import Instruction, Transition +from ..table import CopyDataTypeTag from zkevm_specs.util import FQ, RLC @@ -19,10 +20,18 @@ def sha3(instruction: Instruction): ) gas_cost = instruction.memory_copier_gas_cost(length, memory_expansion_gas_cost) - # TODO(rohit): lookup copy table (copy_rwc_inc and rlc_acc) - copy_rwc_inc = FQ(0) - # TODO(rohit): lookup keccak table (keccak-256 of rlc_acc) - keccak256_rlc_acc = FQ(0) + copy_rwc_inc, rlc_acc = instruction.copy_lookup( + instruction.curr.call_id, + CopyDataTypeTag.Memory, + instruction.curr.call_id, + CopyDataTypeTag.RlcAcc, + memory_offset, + memory_offset + length, + FQ.zero(), + length, + instruction.curr.rw_counter, + ) + keccak256_rlc_acc = instruction.keccak_lookup(length, rlc_acc) instruction.constrain_equal( keccak256_rlc_acc, diff --git a/src/zkevm_specs/evm/instruction.py b/src/zkevm_specs/evm/instruction.py index 1aaf91104..ef8efb137 100644 --- a/src/zkevm_specs/evm/instruction.py +++ b/src/zkevm_specs/evm/instruction.py @@ -1035,3 +1035,6 @@ def copy_lookup( log_id, ) return copy_table_row.rwc_inc, copy_table_row.value_rlc + + def keccak_lookup(self, length: Expression, value_rlc: Expression) -> FQ: + return self.tables.keccak_lookup(length, value_rlc).hash_rlc From ffc13d43bd0a19b72a468c7429fcd06f1ab18620 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 25 Jul 2022 00:32:10 +0800 Subject: [PATCH 4/5] tests: WIP test for sha3 --- src/zkevm_specs/evm/typing.py | 7 +-- tests/evm/test_calldatacopy.py | 7 --- tests/evm/test_sha3.py | 79 ++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 tests/evm/test_sha3.py diff --git a/src/zkevm_specs/evm/typing.py b/src/zkevm_specs/evm/typing.py index b4547febc..45076f3a0 100644 --- a/src/zkevm_specs/evm/typing.py +++ b/src/zkevm_specs/evm/typing.py @@ -673,10 +673,10 @@ class KeccakCircuit: def __init__(self) -> None: self.rows = [] - def add(self, data: bytes, r: FQ): + def add(self, data: bytes, r: FQ) -> KeccakCircuit: rows: List[KeccakTableRow] = [] - hash_rlc = RLC(keccak256(RLC(bytes(reversed(data)), r).le_bytes), r).expr() - value_rlc = FQ(0) + hash_rlc = RLC(keccak256(RLC(bytes(reversed(data)), r, len(data)).le_bytes), r).expr() + value_rlc = FQ.zero() for i in range(len(data)): value_rlc = value_rlc * r + data[i] rows.append( @@ -687,6 +687,7 @@ def add(self, data: bytes, r: FQ): ) ) self.rows.extend(rows) + return self class CopyCircuit: diff --git a/tests/evm/test_calldatacopy.py b/tests/evm/test_calldatacopy.py index ffd1b72f1..6eb64c855 100644 --- a/tests/evm/test_calldatacopy.py +++ b/tests/evm/test_calldatacopy.py @@ -104,13 +104,6 @@ def test_calldatacopy( ) ] - rw_dictionary = ( - RWDictionary(1) - .stack_read(CALL_ID, 1021, memory_offset_rlc) - .stack_read(CALL_ID, 1022, data_offset_rlc) - .stack_read(CALL_ID, 1023, length_rlc) - .call_context_read(CALL_ID, CallContextFieldTag.TxId, TX_ID) - ) rw_dictionary = ( RWDictionary(1) .stack_read(CALL_ID, 1021, memory_offset_rlc) diff --git a/tests/evm/test_sha3.py b/tests/evm/test_sha3.py new file mode 100644 index 000000000..aeb4417d7 --- /dev/null +++ b/tests/evm/test_sha3.py @@ -0,0 +1,79 @@ +import pytest + +from zkevm_specs.evm import ( + Block, + Bytecode, + CopyCircuit, + CopyDataTypeTag, + ExecutionState, + KeccakCircuit, + RWDictionary, + Tables, +) +from zkevm_specs.copy_circuit import verify_copy_table +from zkevm_specs.util import rand_bytes, rand_fq, FQ, RLC, U64 + + +CALL_ID = 1 +TESTING_DATA = ((0x20, 0x40),) + + +@pytest.mark.parametrize("offset, length", TESTING_DATA) +def test_sha3(offset: int, length: int): + randomness = rand_fq() + + offset_rlc = RLC(offset, randomness) + length_rlc = RLC(length, randomness) + + # divide rand memory into chunks of 32 which we will push and mstore. + memory_snapshot = rand_bytes(offset + length) + memory_chunks = list() + for i in range(0, len(memory_snapshot), 32): + memory_chunks.append(memory_snapshot[i : i + 32]) + src_data = dict( + [ + (i, memory_snapshot[i] if i < len(memory_snapshot) else 0) + for i in range(offset, offset + length) + ] + ) + + bytecode = Bytecode() + for i, chunk in enumerate(memory_chunks): + bytecode.push(32 * i, n_bytes=32).push(chunk, n_bytes=32).mstore() + bytecode.push(offset_rlc, n_bytes=32).push(length_rlc, n_bytes=32).sha3().stop() + bytecode_hash = RLC(bytecode.hash(), randomness) + + rw_dictionary = ( + RWDictionary(1) + .stack_write(CALL_ID, 1023, length_rlc) + .stack_write(CALL_ID, 1022, offset_rlc) + .stack_read(CALL_ID, 1022, offset_rlc) + .stack_read(CALL_ID, 1023, length_rlc) + ) + + copy_circuit = CopyCircuit().copy( + randomness, + rw_dictionary, + CALL_ID, + CopyDataTypeTag.Memory, + FQ.zero(), + CopyDataTypeTag.RlcAcc, + offset, + offset + length, + FQ.zero(), + length, + src_data, + ) + + keccak_circuit = KeccakCircuit().add(memory_snapshot[offset : offset + length], randomness) + + tables = Tables( + block_table=set(Block().table_assignments(randomness)), + tx_table=set(), + bytecode_table=set(bytecode.table_assignments(randomness)), + rw_table=set(rw_dictionary.rws), + copy_circuit=copy_circuit.rows, + keccak_table=keccak_circuit.rows, + ) + + verify_copy_table(copy_circuit, tables, randomness) From 0f3fe7a6ceee44c7220e9a0bef86b316e34a77f5 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 25 Jul 2022 12:10:12 +0800 Subject: [PATCH 5/5] fix: sha3 tests for root tx --- src/zkevm_specs/evm/execution/sha3.py | 24 +++++---- src/zkevm_specs/evm/instruction.py | 7 ++- src/zkevm_specs/evm/typing.py | 4 +- src/zkevm_specs/util/param.py | 4 ++ src/zkevm_specs/util/testing.py | 3 +- tests/evm/test_sha3.py | 70 +++++++++++++++++++++++++-- 6 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/zkevm_specs/evm/execution/sha3.py b/src/zkevm_specs/evm/execution/sha3.py index 07988c0f7..e1b8ee722 100644 --- a/src/zkevm_specs/evm/execution/sha3.py +++ b/src/zkevm_specs/evm/execution/sha3.py @@ -1,6 +1,6 @@ from ..instruction import Instruction, Transition from ..table import CopyDataTypeTag -from zkevm_specs.util import FQ, RLC +from zkevm_specs.util import FQ, GAS_COST_COPY_SHA3, RLC def sha3(instruction: Instruction): @@ -10,16 +10,12 @@ def sha3(instruction: Instruction): offset = instruction.stack_pop() # byte size to read in memory. size = instruction.stack_pop() + # sha3 value pushed to stack. + sha3_value = instruction.stack_push() # convert RLC encoded stack elements to FQ. memory_offset, length = instruction.memory_offset_and_length(offset, size) - # calculate memory expansion gas costs. - next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( - memory_offset, length - ) - gas_cost = instruction.memory_copier_gas_cost(length, memory_expansion_gas_cost) - copy_rwc_inc, rlc_acc = instruction.copy_lookup( instruction.curr.call_id, CopyDataTypeTag.Memory, @@ -29,20 +25,28 @@ def sha3(instruction: Instruction): memory_offset + length, FQ.zero(), length, - instruction.curr.rw_counter, + instruction.curr.rw_counter + instruction.rw_counter_offset, ) keccak256_rlc_acc = instruction.keccak_lookup(length, rlc_acc) instruction.constrain_equal( keccak256_rlc_acc, - instruction.stack_push(), + sha3_value.expr(), + ) + + # calculate memory expansion gas costs. + next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( + memory_offset, length + ) + gas_cost = instruction.memory_copier_gas_cost( + length, memory_expansion_gas_cost, GAS_COST_COPY_SHA3 ) instruction.step_state_transition_in_same_context( opcode, rw_counter=Transition.delta(instruction.rw_counter_offset + copy_rwc_inc), program_counter=Transition.delta(1), - stack_pointer=Transition.delta(2), + stack_pointer=Transition.delta(1), # 2 stack reads and 1 stack write memory_size=Transition.to(next_memory_size), dynamic_gas_cost=gas_cost, ) diff --git a/src/zkevm_specs/evm/instruction.py b/src/zkevm_specs/evm/instruction.py index ef8efb137..b4d3c71f1 100644 --- a/src/zkevm_specs/evm/instruction.py +++ b/src/zkevm_specs/evm/instruction.py @@ -999,10 +999,13 @@ def memory_expansion_dynamic_length( return cast_expr(next_memory_size, FQ), cast_expr(memory_expansion_gas_cost, FQ) def memory_copier_gas_cost( - self, length: Expression, memory_expansion_gas_cost: Expression + self, + length: Expression, + memory_expansion_gas_cost: Expression, + gas_cost_copy: int = GAS_COST_COPY, ) -> FQ: word_size, _ = self.constant_divmod(length + FQ(31), FQ(32), N_BYTES_MEMORY_SIZE) - gas_cost = word_size * GAS_COST_COPY + memory_expansion_gas_cost + gas_cost = word_size * gas_cost_copy + memory_expansion_gas_cost self.range_check(gas_cost, N_BYTES_GAS) return gas_cost diff --git a/src/zkevm_specs/evm/typing.py b/src/zkevm_specs/evm/typing.py index 45076f3a0..e74419e59 100644 --- a/src/zkevm_specs/evm/typing.py +++ b/src/zkevm_specs/evm/typing.py @@ -675,14 +675,14 @@ def __init__(self) -> None: def add(self, data: bytes, r: FQ) -> KeccakCircuit: rows: List[KeccakTableRow] = [] - hash_rlc = RLC(keccak256(RLC(bytes(reversed(data)), r, len(data)).le_bytes), r).expr() + hash_rlc = RLC(keccak256(RLC(bytes(reversed(data)), r, len(data)).le_bytes), r) value_rlc = FQ.zero() for i in range(len(data)): value_rlc = value_rlc * r + data[i] rows.append( KeccakTableRow( idx=FQ(i), - hash_rlc=hash_rlc, + hash_rlc=hash_rlc.expr(), value_rlc=value_rlc, ) ) diff --git a/src/zkevm_specs/util/param.py b/src/zkevm_specs/util/param.py index 9dc37889f..73af91256 100644 --- a/src/zkevm_specs/util/param.py +++ b/src/zkevm_specs/util/param.py @@ -41,6 +41,8 @@ GAS_COST_SELF_DESTRUCT = 5000 # Gas cost of copying every word GAS_COST_COPY = 3 +# Gas cost of copying every word, specifically in the case of SHA3 opcode +GAS_COST_COPY_SHA3 = 6 # Gas cost of non-creation transaction GAS_COST_TX = 21000 # Constant gas cost of LOG @@ -72,6 +74,8 @@ MEMORY_EXPANSION_QUAD_DENOMINATOR = 512 # Coefficient of linear part of memory expansion gas cost MEMORY_EXPANSION_LINEAR_COEFF = 3 +# Coefficient of linear part of memory expansion gas cost, specifically in the case of SHA3 +MEMORY_EXPANSION_LINEAR_COEFF_SHA3 = 6 # Maximum number of bytes copied during one single iteration of CopyToMemory, i.e. the internal state used by the # CALLDATACOPY gadget diff --git a/src/zkevm_specs/util/testing.py b/src/zkevm_specs/util/testing.py index 3cf566229..3bec15a09 100644 --- a/src/zkevm_specs/util/testing.py +++ b/src/zkevm_specs/util/testing.py @@ -21,6 +21,7 @@ def div( def memory_expansion( curr_memory_size: U64, address: U64, + mem_exp_linear: int = MEMORY_EXPANSION_LINEAR_COEFF, ) -> Tuple[U64, U128]: # The memory size required for the used address address_memory_size = memory_word_size(address) @@ -34,7 +35,7 @@ def memory_expansion( # Calculate the gas cost for the memory expansion # This gas cost is the difference between the next and current memory costs - memory_gas_cost = (next_memory_size - curr_memory_size) * MEMORY_EXPANSION_LINEAR_COEFF + ( + memory_gas_cost = (next_memory_size - curr_memory_size) * mem_exp_linear + ( next_quad_memory_cost - curr_quad_memory_cost ) diff --git a/tests/evm/test_sha3.py b/tests/evm/test_sha3.py index aeb4417d7..cbf733186 100644 --- a/tests/evm/test_sha3.py +++ b/tests/evm/test_sha3.py @@ -7,11 +7,25 @@ CopyDataTypeTag, ExecutionState, KeccakCircuit, + Opcode, RWDictionary, + StepState, Tables, + verify_steps, ) from zkevm_specs.copy_circuit import verify_copy_table -from zkevm_specs.util import rand_bytes, rand_fq, FQ, RLC, U64 +from zkevm_specs.util import ( + keccak256, + memory_expansion, + memory_word_size, + rand_bytes, + rand_fq, + FQ, + GAS_COST_COPY_SHA3, + MEMORY_EXPANSION_LINEAR_COEFF_SHA3, + RLC, + U64, +) CALL_ID = 1 @@ -19,7 +33,7 @@ @pytest.mark.parametrize("offset, length", TESTING_DATA) -def test_sha3(offset: int, length: int): +def test_sha3(offset: U64, length: U64): randomness = rand_fq() offset_rlc = RLC(offset, randomness) @@ -43,20 +57,38 @@ def test_sha3(offset: int, length: int): bytecode.push(offset_rlc, n_bytes=32).push(length_rlc, n_bytes=32).sha3().stop() bytecode_hash = RLC(bytecode.hash(), randomness) + pc = len(memory_chunks) * 67 + 66 + memory_sha3 = keccak256( + RLC( + bytes(reversed(memory_snapshot[offset : offset + length])), randomness, n_bytes=length + ).le_bytes + ) + memory_sha3_rlc = RLC(memory_sha3, randomness, n_bytes=32) + next_memory_size, memory_expansion_cost = memory_expansion( + offset + length, offset + length, MEMORY_EXPANSION_LINEAR_COEFF_SHA3 + ) + gas = ( + Opcode.SHA3.constant_gas_cost() + + memory_expansion_cost + + memory_word_size(length) * GAS_COST_COPY_SHA3 + ) + rw_dictionary = ( RWDictionary(1) .stack_write(CALL_ID, 1023, length_rlc) .stack_write(CALL_ID, 1022, offset_rlc) .stack_read(CALL_ID, 1022, offset_rlc) .stack_read(CALL_ID, 1023, length_rlc) + .stack_write(CALL_ID, 1023, memory_sha3_rlc) ) + rw_counter_interim = rw_dictionary.rw_counter copy_circuit = CopyCircuit().copy( randomness, rw_dictionary, CALL_ID, CopyDataTypeTag.Memory, - FQ.zero(), + CALL_ID, CopyDataTypeTag.RlcAcc, offset, offset + length, @@ -64,6 +96,7 @@ def test_sha3(offset: int, length: int): length, src_data, ) + assert rw_dictionary.rw_counter - rw_counter_interim == length keccak_circuit = KeccakCircuit().add(memory_snapshot[offset : offset + length], randomness) @@ -77,3 +110,34 @@ def test_sha3(offset: int, length: int): ) verify_copy_table(copy_circuit, tables, randomness) + + verify_steps( + randomness=randomness, + tables=tables, + steps=[ + StepState( + execution_state=ExecutionState.SHA3, + rw_counter=3, + call_id=CALL_ID, + is_root=True, + is_create=False, + code_hash=bytecode_hash, + program_counter=pc, + stack_pointer=1022, + memory_size=next_memory_size, + gas_left=gas, + ), + StepState( + execution_state=ExecutionState.STOP, + rw_counter=rw_dictionary.rw_counter, + call_id=CALL_ID, + is_root=True, + is_create=False, + code_hash=bytecode_hash, + program_counter=pc + 1, + stack_pointer=1023, + memory_size=next_memory_size, + gas_left=0, + ), + ], + )