From 2c212fc335b1fb47a2db7564fb883d1542f7e9be Mon Sep 17 00:00:00 2001 From: Igor Aleksanov Date: Thu, 15 Aug 2024 14:16:56 +0400 Subject: [PATCH] Historical release: v0.141.1 --- crates/circuit_encodings/Cargo.toml | 8 +- crates/circuit_sequencer_api/Cargo.toml | 10 +- .../src/geometry_config.rs | 6 +- crates/zk_evm/.github/workflows/ci.yaml | 5 +- crates/zk_evm/Cargo.toml | 6 +- crates/zk_evm/eraLogo.svg | 13 - crates/zk_evm/src/opcodes/execution/add.rs | 1 - crates/zk_evm/src/opcodes/execution/binop.rs | 1 - .../zk_evm/src/opcodes/execution/context.rs | 1 - crates/zk_evm/src/opcodes/execution/div.rs | 1 - .../zk_evm/src/opcodes/execution/far_call.rs | 3 +- crates/zk_evm/src/opcodes/execution/jump.rs | 1 - crates/zk_evm/src/opcodes/execution/log.rs | 74 +- crates/zk_evm/src/opcodes/execution/mul.rs | 1 - .../zk_evm/src/opcodes/execution/near_call.rs | 1 - crates/zk_evm/src/opcodes/execution/noop.rs | 1 - crates/zk_evm/src/opcodes/execution/ptr.rs | 1 - crates/zk_evm/src/opcodes/execution/ret.rs | 30 +- crates/zk_evm/src/opcodes/execution/shift.rs | 1 - crates/zk_evm/src/opcodes/execution/sub.rs | 1 - crates/zk_evm/src/opcodes/execution/uma.rs | 3 +- crates/zk_evm/src/opcodes/parsing.rs | 1 - crates/zk_evm/src/testing/mod.rs | 16 +- crates/zk_evm/src/testing/tests/mod.rs | 57 +- .../testing/tests/precompiles/keccak256.rs | 226 ++- .../src/testing/tests/precompiles/mod.rs | 8 +- crates/zk_evm/src/testing/tests/trivial.rs | 11 +- crates/zk_evm/src/tests/assembly.rs | 468 ----- crates/zk_evm/src/tests/mod.rs | 1750 ----------------- crates/zk_evm/src/vm_state/cycle.rs | 27 +- crates/zk_evm/src/vm_state/helpers.rs | 6 +- crates/zk_evm/src/vm_state/mem_ops.rs | 6 +- crates/zk_evm/src/vm_state/mod.rs | 2 - crates/zk_evm/src/vm_state/pending_port.rs | 26 - crates/zk_evm_abstractions/.gitignore | 6 +- crates/zk_evm_abstractions/CONTRIBUTING.md | 45 +- crates/zk_evm_abstractions/Cargo.toml | 5 +- .../src/precompiles/ecrecover.rs | 35 +- .../src/precompiles/keccak256.rs | 212 +- .../src/precompiles/mod.rs | 16 +- .../zkevm_circuits/.github/workflows/ci.yaml | 2 +- crates/zkevm_circuits/Cargo.toml | 8 +- crates/zkevm_circuits/cs_derive/.gitignore | 3 - crates/zkevm_circuits/cs_derive/Cargo.toml | 15 - crates/zkevm_circuits/cs_derive/src/.DS_Store | Bin 6148 -> 0 bytes .../cs_derive/src/allocatable/mod.rs | 283 --- crates/zkevm_circuits/cs_derive/src/lib.rs | 100 - .../cs_derive/src/selectable/mod.rs | 105 - crates/zkevm_circuits/cs_derive/src/utils.rs | 167 -- .../cs_derive/src/var_length_encodable/mod.rs | 110 -- .../cs_derive/src/witness_hook/mod.rs | 124 -- .../src/base_structures/register/mod.rs | 20 + .../src/code_unpacker_sha256/mod.rs | 8 +- .../src/demux_log_queue/input.rs | 15 + .../zkevm_circuits/src/demux_log_queue/mod.rs | 8 +- .../zkevm_circuits/src/ecrecover/baseline.rs | 14 +- .../src/ecrecover/decomp_table.rs | 10 +- crates/zkevm_circuits/src/ecrecover/mod.rs | 2 +- .../src/ecrecover/naf_abs_div2_table.rs | 2 +- .../src/ecrecover/new_optimized.rs | 668 ++++++- .../secp256k1/fixed_base_mul_table.rs | 81 +- crates/zkevm_circuits/src/eip_4844/input.rs | 66 + crates/zkevm_circuits/src/eip_4844/mod.rs | 772 ++++++++ .../keccak256_round_function/buffer/mod.rs | 164 ++ .../src/keccak256_round_function/input.rs | 12 +- .../src/keccak256_round_function/mod.rs | 852 ++++++-- crates/zkevm_circuits/src/lib.rs | 2 + crates/zkevm_circuits/src/log_sorter/mod.rs | 8 +- .../main_vm/opcodes/call_ret_impl/far_call.rs | 9 + .../src/main_vm/opcodes/call_ret_impl/ret.rs | 18 +- .../zkevm_circuits/src/main_vm/opcodes/log.rs | 8 +- .../zkevm_circuits/src/main_vm/pre_state.rs | 34 +- crates/zkevm_circuits/src/main_vm/utils.rs | 5 +- .../zkevm_circuits/src/ram_permutation/mod.rs | 8 +- .../zkevm_circuits/src/scheduler/auxiliary.rs | 1 + .../src/scheduler/block_header/mod.rs | 11 + crates/zkevm_circuits/src/scheduler/input.rs | 7 + crates/zkevm_circuits/src/scheduler/mod.rs | 295 ++- .../src/sort_decommittment_requests/mod.rs | 8 +- .../storage_validity_by_grand_product/mod.rs | 13 +- crates/zkevm_circuits/src/utils.rs | 20 +- crates/zkevm_opcode_defs/Cargo.toml | 10 +- crates/zkevm_opcode_defs/deny.toml | 4 +- .../src/definitions/abi/precompile_call.rs | 34 +- .../zkevm_opcode_defs/src/definitions/add.rs | 8 + .../zkevm_opcode_defs/src/definitions/all.rs | 42 + .../src/definitions/binop.rs | 8 + .../src/definitions/context.rs | 8 + .../zkevm_opcode_defs/src/definitions/div.rs | 8 + .../src/definitions/far_call.rs | 8 + .../src/definitions/invalid_opcode.rs | 8 + .../zkevm_opcode_defs/src/definitions/jump.rs | 8 + .../zkevm_opcode_defs/src/definitions/log.rs | 14 +- .../zkevm_opcode_defs/src/definitions/mul.rs | 8 + .../src/definitions/near_call.rs | 8 + .../zkevm_opcode_defs/src/definitions/noop.rs | 8 + .../src/definitions/opcode_trait.rs | 2 + .../zkevm_opcode_defs/src/definitions/ptr.rs | 8 + .../zkevm_opcode_defs/src/definitions/ret.rs | 8 + .../src/definitions/shift.rs | 8 + .../zkevm_opcode_defs/src/definitions/sub.rs | 8 + .../zkevm_opcode_defs/src/definitions/uma.rs | 8 + crates/zkevm_opcode_defs/src/utils.rs | 10 + 103 files changed, 3446 insertions(+), 3940 deletions(-) delete mode 100644 crates/zk_evm/eraLogo.svg delete mode 100644 crates/zk_evm/src/tests/assembly.rs delete mode 100644 crates/zk_evm/src/tests/mod.rs delete mode 100644 crates/zk_evm/src/vm_state/pending_port.rs delete mode 100644 crates/zkevm_circuits/cs_derive/.gitignore delete mode 100644 crates/zkevm_circuits/cs_derive/Cargo.toml delete mode 100644 crates/zkevm_circuits/cs_derive/src/.DS_Store delete mode 100644 crates/zkevm_circuits/cs_derive/src/allocatable/mod.rs delete mode 100644 crates/zkevm_circuits/cs_derive/src/lib.rs delete mode 100644 crates/zkevm_circuits/cs_derive/src/selectable/mod.rs delete mode 100644 crates/zkevm_circuits/cs_derive/src/utils.rs delete mode 100644 crates/zkevm_circuits/cs_derive/src/var_length_encodable/mod.rs delete mode 100644 crates/zkevm_circuits/cs_derive/src/witness_hook/mod.rs create mode 100644 crates/zkevm_circuits/src/eip_4844/input.rs create mode 100644 crates/zkevm_circuits/src/eip_4844/mod.rs create mode 100644 crates/zkevm_circuits/src/keccak256_round_function/buffer/mod.rs diff --git a/crates/circuit_encodings/Cargo.toml b/crates/circuit_encodings/Cargo.toml index 949b413..c8a0b51 100644 --- a/crates/circuit_encodings/Cargo.toml +++ b/crates/circuit_encodings/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "circuit_encodings" -version = "0.140.1" +version = "0.141.1" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -13,12 +13,14 @@ description = "ZKsync Era circuits encodings" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -zkevm_circuits = { version = "0.140", path = "../zkevm_circuits" } # Not pinned, because it's an old version used only by MultiVM. -zk_evm = { version = "0.140", path = "../zk_evm" } # Not pinned, because it's an old version used only by MultiVM +zkevm_circuits = { version = "0.141", path = "../zkevm_circuits" } # Not pinned, because it's an old dependency used by MultiVM +zk_evm = { version = "0.141", path = "../zk_evm" } # Not pinned, because it's an old dependency used by MultiVM + derivative = "2.2" serde = {version = "1", features = ["derive"]} + [features] default = [] log_tracing = ["zkevm_circuits/log_tracing"] diff --git a/crates/circuit_sequencer_api/Cargo.toml b/crates/circuit_sequencer_api/Cargo.toml index 4618212..dff6e06 100644 --- a/crates/circuit_sequencer_api/Cargo.toml +++ b/crates/circuit_sequencer_api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "circuit_sequencer_api" -version = "0.140.1" +version = "0.141.1" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -13,16 +13,14 @@ description = "ZKsync Era circuits API for sequencer" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -circuit_encodings = { version = "0.140", path = "../circuit_encodings" } # Not pinned, because it's an old version used only by MultiVM - -zk_evm = { version = "0.140", path = "../zk_evm" } # Not pinned, because it's an old version used only by MultiVM -bellman = { package = "bellman_ce", version = "0" } # Not pinned, because it's an old version used only by MultiVM +circuit_encodings = { version = "0.141", path = "../circuit_encodings" } # Not pinned, because it's an old dependency used by MultiVM +zk_evm = { version = "0.141", path = "../zk_evm" } # Not pinned, because it's an old dependency used by MultiVM +bellman = { package = "bellman_ce", version = "0" } # Not pinned, because it's an old dependency used by MultiVM derivative = "2.2" serde = {version = "1", features = ["derive"]} rayon = "1.10" - [features] default = [] diff --git a/crates/circuit_sequencer_api/src/geometry_config.rs b/crates/circuit_sequencer_api/src/geometry_config.rs index b1096d7..9c3ff16 100644 --- a/crates/circuit_sequencer_api/src/geometry_config.rs +++ b/crates/circuit_sequencer_api/src/geometry_config.rs @@ -4,7 +4,7 @@ use crate::toolset::GeometryConfig; pub const fn get_geometry_config() -> GeometryConfig { GeometryConfig { - cycles_per_vm_snapshot: 5692, + cycles_per_vm_snapshot: 5585, cycles_code_decommitter_sorter: 117500, cycles_per_log_demuxer: 58750, cycles_per_storage_sorter: 46921, @@ -12,9 +12,9 @@ pub const fn get_geometry_config() -> GeometryConfig { cycles_per_ram_permutation: 136714, cycles_per_code_decommitter: 2845, cycles_per_storage_application: 33, - cycles_per_keccak256_circuit: 672, + cycles_per_keccak256_circuit: 293, cycles_per_sha256_circuit: 2206, - cycles_per_ecrecover_circuit: 2, + cycles_per_ecrecover_circuit: 7, limit_for_l1_messages_pudata_hasher: 774, } } diff --git a/crates/zk_evm/.github/workflows/ci.yaml b/crates/zk_evm/.github/workflows/ci.yaml index 8b2b519..9b8e22e 100644 --- a/crates/zk_evm/.github/workflows/ci.yaml +++ b/crates/zk_evm/.github/workflows/ci.yaml @@ -10,9 +10,10 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly-2023-04-17 + toolchain: stable - run: cargo build --verbose --all-features - - run: RUSTFLAGS="-Awarnings" cargo test --verbose --all-features +# OOMs on ubuntu-latest. +# - run: RUSTFLAGS="-Awarnings" cargo test --verbose --all-features formatting: name: cargo fmt diff --git a/crates/zk_evm/Cargo.toml b/crates/zk_evm/Cargo.toml index ee2bf35..dba036d 100644 --- a/crates/zk_evm/Cargo.toml +++ b/crates/zk_evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zk_evm" -version = "0.140.0" +version = "0.141.0" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -18,8 +18,8 @@ serde_json = "1.0" anyhow = "1.0" num = { version = "0.4"} static_assertions = "1" -zkevm_opcode_defs = { version = "=0.132.0", path = "../zkevm_opcode_defs" } -zk_evm_abstractions = { version = "=0.140.0", path = "../zk_evm_abstractions" } +zkevm_opcode_defs = { version = "=0.141.0", path = "../zkevm_opcode_defs" } +zk_evm_abstractions = { version = "=0.141.0", path = "../zk_evm_abstractions" } lazy_static = "1.4" [dev-dependencies] diff --git a/crates/zk_evm/eraLogo.svg b/crates/zk_evm/eraLogo.svg deleted file mode 100644 index 6ec790c..0000000 --- a/crates/zk_evm/eraLogo.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/crates/zk_evm/src/opcodes/execution/add.rs b/crates/zk_evm/src/opcodes/execution/add.rs index d53ca88..5f629be 100644 --- a/crates/zk_evm/src/opcodes/execution/add.rs +++ b/crates/zk_evm/src/opcodes/execution/add.rs @@ -2,7 +2,6 @@ use super::*; impl> DecodedOpcode { pub fn add_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/binop.rs b/crates/zk_evm/src/opcodes/execution/binop.rs index e31a421..9a64c00 100644 --- a/crates/zk_evm/src/opcodes/execution/binop.rs +++ b/crates/zk_evm/src/opcodes/execution/binop.rs @@ -3,7 +3,6 @@ use zkevm_opcode_defs::{BinopOpcode, Opcode}; impl> DecodedOpcode { pub fn binop_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/context.rs b/crates/zk_evm/src/opcodes/execution/context.rs index a6a3f9f..8e0b3cc 100644 --- a/crates/zk_evm/src/opcodes/execution/context.rs +++ b/crates/zk_evm/src/opcodes/execution/context.rs @@ -4,7 +4,6 @@ use zkevm_opcode_defs::{ContextOpcode, Opcode}; impl> DecodedOpcode { pub fn context_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/div.rs b/crates/zk_evm/src/opcodes/execution/div.rs index f09d9b9..b5e834f 100644 --- a/crates/zk_evm/src/opcodes/execution/div.rs +++ b/crates/zk_evm/src/opcodes/execution/div.rs @@ -2,7 +2,6 @@ use super::*; impl> DecodedOpcode { pub fn div_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/far_call.rs b/crates/zk_evm/src/opcodes/execution/far_call.rs index 412f910..7d1e040 100644 --- a/crates/zk_evm/src/opcodes/execution/far_call.rs +++ b/crates/zk_evm/src/opcodes/execution/far_call.rs @@ -33,7 +33,6 @@ use zkevm_opcode_defs::{FarCallABI, FarCallForwardPageType, FarCallOpcode, FatPo impl> DecodedOpcode { pub fn far_call_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -96,6 +95,7 @@ impl> DecodedOpcode { let remaining_ergs = current_stack.ergs_remaining; let current_context_u128 = current_stack.context_u128_value; + #[allow(dropping_references)] drop(current_stack); let timestamp_for_storage_read = vm_state.timestamp_for_first_decommit_or_precompile_read(); @@ -368,6 +368,7 @@ impl> DecodedOpcode { FarCallForwardPageType::ForwardFatPointer => 0u32, }; + #[allow(dropping_references)] drop(current_stack_mut); // MEMORY_GROWTH_ERGS_PER_BYTE is always 1 diff --git a/crates/zk_evm/src/opcodes/execution/jump.rs b/crates/zk_evm/src/opcodes/execution/jump.rs index 85cc4dc..4c314df 100644 --- a/crates/zk_evm/src/opcodes/execution/jump.rs +++ b/crates/zk_evm/src/opcodes/execution/jump.rs @@ -2,7 +2,6 @@ use super::*; impl> DecodedOpcode { pub fn jump_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/log.rs b/crates/zk_evm/src/opcodes/execution/log.rs index d99ca78..d2708e2 100644 --- a/crates/zk_evm/src/opcodes/execution/log.rs +++ b/crates/zk_evm/src/opcodes/execution/log.rs @@ -1,9 +1,7 @@ use super::*; use zk_evm_abstractions::queries::LogQuery; -use zkevm_opcode_defs::{ - LogOpcode, Opcode, PrecompileCallABI, PrecompileCallInnerABI, FIRST_MESSAGE_FLAG_IDX, -}; +use zkevm_opcode_defs::{LogOpcode, Opcode, PrecompileCallABI, FIRST_MESSAGE_FLAG_IDX}; use zkevm_opcode_defs::system_params::{ EVENT_AUX_BYTE, L1_MESSAGE_AUX_BYTE, PRECOMPILE_AUX_BYTE, STORAGE_AUX_BYTE, @@ -11,7 +9,6 @@ use zkevm_opcode_defs::system_params::{ impl> DecodedOpcode { pub fn log_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -78,6 +75,8 @@ impl> DecodedOpcode { let current_context = vm_state.local_state.callstack.get_current_stack(); let address = current_context.this_address; let shard_id = current_context.this_shard_id; + + #[allow(dropping_references)] drop(current_context); // we do not need all the values here, but we DO need the written value @@ -156,7 +155,10 @@ impl> DecodedOpcode { let current_context = vm_state.local_state.callstack.get_current_stack(); let address = current_context.this_address; let shard_id = current_context.this_shard_id; + + #[allow(dropping_references)] drop(current_context); + match inner_variant { LogOpcode::StorageRead => { assert!(not_enough_power == false); @@ -261,51 +263,42 @@ impl> DecodedOpcode { return; } - let precompile_abi = PrecompileCallABI::from_u256(src0); - let PrecompileCallABI { - input_memory_offset, - input_memory_length, - output_memory_offset, - output_memory_length, - per_precompile_interpreted, - } = precompile_abi; - + let mut precompile_abi = PrecompileCallABI::from_u256(src0); // normal execution vm_state .local_state .callstack .get_current_stack_mut() .ergs_remaining = ergs_remaining; - let memory_page_to_read = CallStackEntry::::heap_page_from_base( - vm_state - .local_state - .callstack - .get_current_stack() - .base_memory_page, - ); - let memory_page_to_write = CallStackEntry::::heap_page_from_base( - vm_state - .local_state - .callstack - .get_current_stack() - .base_memory_page, - ); + if precompile_abi.memory_page_to_read == 0 { + let memory_page_to_read = CallStackEntry::::heap_page_from_base( + vm_state + .local_state + .callstack + .get_current_stack() + .base_memory_page, + ); + precompile_abi.memory_page_to_read = memory_page_to_read.0; + } + + if precompile_abi.memory_page_to_write == 0 { + let memory_page_to_write = CallStackEntry::::heap_page_from_base( + vm_state + .local_state + .callstack + .get_current_stack() + .base_memory_page, + ); + precompile_abi.memory_page_to_write = memory_page_to_write.0; + } let timestamp_to_read = vm_state.timestamp_for_first_decommit_or_precompile_read(); + debug_assert!(timestamp_to_read == timestamp_for_log); let timestamp_to_write = vm_state.timestamp_for_second_decommit_or_precompile_write(); - assert!(timestamp_to_read.0 + 1 == timestamp_to_write.0); - - let precompile_inner_abi = PrecompileCallInnerABI { - input_memory_offset, - input_memory_length, - output_memory_offset, - output_memory_length, - memory_page_to_read: memory_page_to_read.0, - memory_page_to_write: memory_page_to_write.0, - precompile_interpreted_data: per_precompile_interpreted, - }; - let precompile_inner_abi = precompile_inner_abi.to_u256(); + debug_assert!(timestamp_to_read.0 + 1 == timestamp_to_write.0); + + let precompile_abi_encoded = precompile_abi.to_u256(); let query = LogQuery { timestamp: timestamp_for_log, @@ -313,13 +306,14 @@ impl> DecodedOpcode { aux_byte: PRECOMPILE_AUX_BYTE, shard_id, address, - key: precompile_inner_abi, + key: precompile_abi_encoded, read_value: U256::zero(), written_value: U256::zero(), rw_flag: false, rollback: false, is_service: is_first_message, }; + vm_state.call_precompile(vm_state.local_state.monotonic_cycle_counter, query); let result = PrimitiveValue { value: U256::from(1u64), diff --git a/crates/zk_evm/src/opcodes/execution/mul.rs b/crates/zk_evm/src/opcodes/execution/mul.rs index 21f4415..253e8bd 100644 --- a/crates/zk_evm/src/opcodes/execution/mul.rs +++ b/crates/zk_evm/src/opcodes/execution/mul.rs @@ -2,7 +2,6 @@ use super::*; impl> DecodedOpcode { pub fn mul_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/near_call.rs b/crates/zk_evm/src/opcodes/execution/near_call.rs index fbe7eed..714eb3f 100644 --- a/crates/zk_evm/src/opcodes/execution/near_call.rs +++ b/crates/zk_evm/src/opcodes/execution/near_call.rs @@ -4,7 +4,6 @@ use super::*; impl> DecodedOpcode { pub fn near_call_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/noop.rs b/crates/zk_evm/src/opcodes/execution/noop.rs index 1b2b8f8..f7c3e53 100644 --- a/crates/zk_evm/src/opcodes/execution/noop.rs +++ b/crates/zk_evm/src/opcodes/execution/noop.rs @@ -2,7 +2,6 @@ use super::*; impl> DecodedOpcode { pub fn noop_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/ptr.rs b/crates/zk_evm/src/opcodes/execution/ptr.rs index 3623954..244b319 100644 --- a/crates/zk_evm/src/opcodes/execution/ptr.rs +++ b/crates/zk_evm/src/opcodes/execution/ptr.rs @@ -4,7 +4,6 @@ use zkevm_opcode_defs::{FatPointer, Opcode, PtrOpcode}; impl> DecodedOpcode { pub fn ptr_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/ret.rs b/crates/zk_evm/src/opcodes/execution/ret.rs index 0803f21..76e71df 100644 --- a/crates/zk_evm/src/opcodes/execution/ret.rs +++ b/crates/zk_evm/src/opcodes/execution/ret.rs @@ -7,7 +7,6 @@ use zkevm_opcode_defs::{FatPointer, Opcode, RetABI, RetForwardPageType, RetOpcod impl> DecodedOpcode { pub fn ret_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -28,10 +27,19 @@ impl> DecodedOpcode { vm_state.local_state.flags.reset(); let PrimitiveValue { - value: src0, - is_pointer: src0_is_ptr, + value: mut src0, + is_pointer: mut src0_is_ptr, } = src0; + // on panic, we should never return any data. in this case, zero out src0 data + match inner_variant { + RetOpcode::Panic => { + src0 = U256::default(); + src0_is_ptr = false; + } + _ => {} + } + let ret_abi = RetABI::from_u256(src0); // we want to mark with one that was will become a new current (taken from stack) @@ -128,9 +136,9 @@ impl> DecodedOpcode { } } } - RetOpcode::Panic => { - memory_quasi_fat_pointer = FatPointer::empty(); - } + // data should be zeroed out for panic case, both for caller case and malformed + // pointer case + _ => {} } // potentially pay for memory growth @@ -180,6 +188,8 @@ impl> DecodedOpcode { // we do nothing with it later on, so just keep returndata page, and set zeroes for other Some(memory_quasi_fat_pointer) }; + + #[allow(dropping_references)] drop(current_callstack); // done with exceptions, so we can pop the callstack entry @@ -240,6 +250,14 @@ impl> DecodedOpcode { // just use a saved value } + // grow memory on near call + if finished_callstack.is_local_frame == true { + assert!(finished_callstack.heap_bound >= next_context.heap_bound); + assert!(finished_callstack.aux_heap_bound >= next_context.aux_heap_bound); + next_context.heap_bound = finished_callstack.heap_bound; + next_context.aux_heap_bound = finished_callstack.aux_heap_bound; + } + // and set flag on panic if inner_variant == RetOpcode::Panic { vm_state.local_state.flags.overflow_or_less_than_flag = true; diff --git a/crates/zk_evm/src/opcodes/execution/shift.rs b/crates/zk_evm/src/opcodes/execution/shift.rs index 010181a..b51858b 100644 --- a/crates/zk_evm/src/opcodes/execution/shift.rs +++ b/crates/zk_evm/src/opcodes/execution/shift.rs @@ -6,7 +6,6 @@ use zkevm_opcode_defs::{Opcode, ShiftOpcode}; impl> DecodedOpcode { pub fn shift_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/sub.rs b/crates/zk_evm/src/opcodes/execution/sub.rs index 19aec14..bf18f35 100644 --- a/crates/zk_evm/src/opcodes/execution/sub.rs +++ b/crates/zk_evm/src/opcodes/execution/sub.rs @@ -2,7 +2,6 @@ use super::*; impl> DecodedOpcode { pub fn sub_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/opcodes/execution/uma.rs b/crates/zk_evm/src/opcodes/execution/uma.rs index 72b07b7..f05c990 100644 --- a/crates/zk_evm/src/opcodes/execution/uma.rs +++ b/crates/zk_evm/src/opcodes/execution/uma.rs @@ -24,7 +24,6 @@ bitflags! { impl> DecodedOpcode { pub fn uma_opcode_apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -216,6 +215,8 @@ impl> DecodedOpcode { exceptions.set(UMAExceptionFlags::NOT_ENOUGH_ERGS_TO_GROW_MEMORY, true); } current_callstack_mut.ergs_remaining = ergs_after_memory_growth; + + #[allow(dropping_references)] drop(current_callstack_mut); // we will set panic if any exception was triggered diff --git a/crates/zk_evm/src/opcodes/parsing.rs b/crates/zk_evm/src/opcodes/parsing.rs index 6db4127..da73244 100644 --- a/crates/zk_evm/src/opcodes/parsing.rs +++ b/crates/zk_evm/src/opcodes/parsing.rs @@ -45,7 +45,6 @@ impl> DecodedOpcode { } pub fn apply< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, diff --git a/crates/zk_evm/src/testing/mod.rs b/crates/zk_evm/src/testing/mod.rs index 45f82b8..205c0a6 100644 --- a/crates/zk_evm/src/testing/mod.rs +++ b/crates/zk_evm/src/testing/mod.rs @@ -8,20 +8,10 @@ pub mod simple_tracer; pub mod storage; use self::storage::InMemoryStorage; -use crate::vm_state::VmState; use crate::witness_trace::DummyTracer; use zk_evm_abstractions::precompiles::DefaultPrecompilesProcessor; use zk_evm_abstractions::queries::LogQuery; -pub type BasicTestingVmState = VmState< - InMemoryStorage, - SimpleMemory, - InMemoryEventSink, - DefaultPrecompilesProcessor, - SimpleDecommitter, - DummyTracer, ->; - pub struct BasicTestingTools { pub storage: InMemoryStorage, pub memory: SimpleMemory, @@ -50,7 +40,7 @@ pub fn create_default_testing_tools() -> BasicTestingTools { } pub fn get_final_net_states( - vm: BasicTestingVmState, + tools: BasicTestingTools, ) -> ( Vec, [HashMap>; NUM_SHARDS], @@ -59,12 +49,12 @@ pub fn get_final_net_states( Vec, SimpleMemory, ) { - let BasicTestingVmState { + let BasicTestingTools:: { storage, event_sink, memory, .. - } = vm; + } = tools; let final_storage_state = storage.inner.clone(); let (full_storage_access_history, _per_slot_history) = storage.flatten_and_net_history(); diff --git a/crates/zk_evm/src/testing/tests/mod.rs b/crates/zk_evm/src/testing/tests/mod.rs index 16ed0bb..360dbbf 100644 --- a/crates/zk_evm/src/testing/tests/mod.rs +++ b/crates/zk_evm/src/testing/tests/mod.rs @@ -1,59 +1,6 @@ use super::*; use zk_evm_abstractions::aux::MemoryPage; -use zkevm_opcode_defs::system_params::BOOTLOADER_FORMAL_ADDRESS; -use crate::vm_state::*; -use crate::{ - block_properties::BlockProperties, - reference_impls::{event_sink::InMemoryEventSink, memory::SimpleMemory}, - testing::storage::InMemoryStorage, - vm_state::VmState, - witness_trace::DummyTracer, -}; - -pub fn create_default_block_info() -> BlockProperties { - BlockProperties { - default_aa_code_hash: U256::zero(), - zkporter_is_available: true, - } -} - -pub fn create_initial_vm_state_for_basic_testing( - tools: BasicTestingTools, - block_properties: BlockProperties, -) -> BasicTestingVmState { - let mut vm = VmState::empty_state( - tools.storage, - tools.memory, - tools.event_sink, - tools.precompiles_processor, - tools.decommittment_processor, - tools.witness_tracer, - block_properties, - ); - - let bootloader_context = CallStackEntry { - this_address: *BOOTLOADER_FORMAL_ADDRESS, - msg_sender: Address::zero(), - code_address: *BOOTLOADER_FORMAL_ADDRESS, - base_memory_page: MemoryPage(zkevm_opcode_defs::BOOTLOADER_BASE_PAGE), - code_page: MemoryPage(zkevm_opcode_defs::BOOTLOADER_CODE_PAGE), - sp: 0u16, - pc: 0u16, - exception_handler_location: 0u16, - ergs_remaining: zkevm_opcode_defs::system_params::VM_INITIAL_FRAME_ERGS, - this_shard_id: 0u8, - caller_shard_id: 0u8, - code_shard_id: 0u8, - is_static: false, - is_local_frame: false, - context_u128_value: 0, - heap_bound: 0u32, - aux_heap_bound: 0u32, - }; - - vm.push_bootloader_context(0, bootloader_context); - - vm -} +#[cfg(test)] +mod precompiles; diff --git a/crates/zk_evm/src/testing/tests/precompiles/keccak256.rs b/crates/zk_evm/src/testing/tests/precompiles/keccak256.rs index ee1e743..d4e62e2 100644 --- a/crates/zk_evm/src/testing/tests/precompiles/keccak256.rs +++ b/crates/zk_evm/src/testing/tests/precompiles/keccak256.rs @@ -1,138 +1,196 @@ use super::*; -use crate::precompiles::keccak256::*; - -fn pad_and_fill_memory(input: &[u8], page: u32, memory: &mut M) -> u16 { - let mut padded = vec![]; - padded.extend_from_slice(input); - - let block_size = KECCAK_RATE_IN_U64_WORDS * 8; - let last_block_size = padded.len() % block_size; - let padlen = block_size - last_block_size; - if padlen == 1 { - padded.push(0x81); - } else { - padded.push(0x01); - padded.extend(std::iter::repeat(0u8).take(padlen - 2)); - padded.push(0x80); - } - - assert_eq!(padded.len() % block_size, 0); - - let num_rounds = padded.len() / block_size; - - let mut num_rounds_u256 = U256::zero(); - num_rounds_u256.0[0] = num_rounds as u64; - println!("Num rounds = {}", num_rounds_u256); - let mut location = MemoryLocation {page: MemoryPage(page), index: MemoryIndex(0)}; - let num_rounds_query = MemoryQuery { - timestamp: Timestamp(0u32), - location, - value: num_rounds_u256, - rw_flag: true - }; - - let _ = memory.execute_partial_query(num_rounds_query); - - let total_len_as_u64_words = padded.len() / 8; - let mut num_words = total_len_as_u64_words / 4; - if total_len_as_u64_words % 4 != 0 { - num_words += 1; - } - - let mut chunk_iter = padded.chunks_exact(8); - - for _word in 0..num_words { - location.index.0 += 1; - let mut value = U256::zero(); - for i in (0..4).rev() { - if let Some(chunk) = chunk_iter.next() { - let as_u64 = u64::from_be_bytes(chunk.try_into().unwrap()); - value.0[i] = as_u64; +use zk_evm_abstractions::auxiliary::*; +use zk_evm_abstractions::queries::MemoryQuery; +use zk_evm_abstractions::vm::Memory; +use zk_evm_abstractions::vm::MemoryType; +use zk_evm_abstractions::vm::PrecompilesProcessor; +use zkevm_opcode_defs::PrecompileCallABI; + +fn bytes_to_u256_words(input: Vec, unalignement: usize) -> Vec { + let mut result = vec![]; + let mut it = std::iter::repeat(0xffu8) + .take(unalignement) + .chain(input.into_iter()); + 'outer: loop { + let mut done = false; + let mut buffer = [0u8; 32]; + for (idx, dst) in buffer.iter_mut().enumerate() { + if let Some(src) = it.next() { + *dst = src; + } else { + done = true; + if idx == 0 { + break 'outer; + } + break; } } + let el = U256::from_big_endian(&buffer); + result.push(el); + if done { + break 'outer; + } + } - let data_query = MemoryQuery { + result +} + +fn pad_and_fill_memory( + input: &[u8], + page: u32, + memory: &mut M, + unalignment: u32, +) -> u32 { + let input = input.to_vec(); + let words = bytes_to_u256_words(input, unalignment as usize); + let mut index = 0u32; + let num_words = words.len() as u32; + + for word in words.into_iter() { + let location = MemoryLocation { + page: MemoryPage(page), + index: MemoryIndex(index), + memory_type: MemoryType::Heap, + }; + let num_rounds_query = MemoryQuery { timestamp: Timestamp(0u32), location, - value, - rw_flag: true + value: word, + value_is_pointer: false, + rw_flag: true, }; - let _ = memory.execute_partial_query(data_query); + let _ = memory.execute_partial_query(1, num_rounds_query); + index += 1; } - assert!(chunk_iter.remainder().len() == 0); - - (1 + num_words) as u16 + num_words } use sha3::Digest; use sha3::Keccak256; -fn run_keccak256_test_inner(input: &[u8]) -> (Vec<[u8; 32]>, std::ops::Range) { +fn run_keccak256_test_inner( + input: &[u8], + unalignment: u32, +) -> (Vec<[u8; 32]>, std::ops::Range) { let mut memory = SimpleMemory::new(); + + let input_memory_page = 4u32; + let output_memory_page = 4u32; + + memory.heaps.push(( + (input_memory_page, vec![U256::zero(); 1 << 10]), + (0, vec![U256::zero(); 0]), + )); + memory.page_numbers_indirections.insert( + input_memory_page, + reference_impls::memory::Indirection::Heap(1), + ); let mut precompiles_processor = DefaultPrecompilesProcessor::; let mut hasher = Keccak256::default(); hasher.update(input); let result = hasher.finalize(); - let bytes: &[u8] = result.as_ref(); - println!("{}", hex::encode(bytes)); + let expected_output: &[u8] = result.as_ref(); // fill the memory - let num_words_used = pad_and_fill_memory(input, 0u32, &mut memory); - - let precompile_call_params = PrecompileCallParams { - input_location: MemoryLocation {page: MemoryPage(0u32), index: MemoryIndex(0u16)}, - output_location: MemoryLocation {page: MemoryPage(0u32), index: MemoryIndex(num_words_used)}, - timestamp_for_input_read: Timestamp(1u32), - timestamp_for_output_write: Timestamp(2u32), + let num_words_used = pad_and_fill_memory(input, input_memory_page, &mut memory, unalignment); + let input_byte_offset = unalignment; + let input_length = input.len(); + + let precompile_abi = PrecompileCallABI { + input_memory_offset: input_byte_offset, + input_memory_length: input_length as u32, + output_memory_offset: num_words_used as u32, + output_memory_length: 0, + memory_page_to_read: input_memory_page, + memory_page_to_write: output_memory_page, + precompile_interpreted_data: 0, }; - let address = Address::from_low_u64_be(KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS as u64); + let address = + *zkevm_opcode_defs::system_params::KECCAK256_ROUND_FUNCTION_PRECOMPILE_FORMAL_ADDRESS; let precompile_query = LogQuery { - timestamp: precompile_call_params.timestamp_for_input_read, + timestamp: Timestamp(1), tx_number_in_block: 0, shard_id: 0, - aux_byte: PRECOMPILE_AUX_BYTE, + aux_byte: zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE, address, - key: U256::zero(), - read_value: precompile_call_params.encode_into_log_value(), + key: precompile_abi.to_u256(), + read_value: U256::zero(), written_value: U256::zero(), rw_flag: false, rollback: false, is_service: false, }; - let _ = precompiles_processor.execute_precompile(precompile_query, &mut memory); + let _ = precompiles_processor.execute_precompile(4, precompile_query, &mut memory); - let range = 0u16..(num_words_used + 1); - let content = memory.dump_page_content(0u32, range.clone()); - let expected_output = content.last().copied().unwrap(); + let range = 0u32..(num_words_used + 1); + let content = memory.dump_page_content(output_memory_page, range.clone()); + let output = content.last().copied().unwrap(); - assert_eq!(&expected_output[..], bytes); + dbg!(hex::encode(&expected_output)); + dbg!(hex::encode(&output)); + + assert_eq!(&expected_output[..], &output[..]); (content, range) } #[test] fn test_empty_keccak256() { - let (content, range) = run_keccak256_test_inner(&[]); + let (content, range) = run_keccak256_test_inner(&[], 0); pretty_print_memory_dump(&content, range); } #[test] -fn test_few_rounds_of_keccak256() { - let data = vec![255u8; 256]; - let (content, range) = run_keccak256_test_inner(&data); +fn test_empty_keccak256_unaligned() { + let (content, range) = run_keccak256_test_inner(&[], 31); pretty_print_memory_dump(&content, range); } #[test] -fn test_very_long_keccak256() { - let data = vec![255u8; 10_000]; - let (content, range) = run_keccak256_test_inner(&data); +fn test_one_round_of_keccak256() { + let data = vec![123u8; 50]; + let (content, range) = run_keccak256_test_inner(&data, 0); pretty_print_memory_dump(&content, range); -} \ No newline at end of file +} + +#[test] +fn test_one_round_of_keccak256_unaligned() { + let data = vec![123u8; 50]; + let (content, range) = run_keccak256_test_inner(&data, 31); + pretty_print_memory_dump(&content, range); +} + +#[test] +fn test_one_round_of_keccak256_with_full_paddings() { + let data = vec![123u8; 136]; + let (content, range) = run_keccak256_test_inner(&data, 0); + pretty_print_memory_dump(&content, range); +} + +#[test] +fn test_one_round_of_keccak256_with_full_paddings_unaligned() { + let data = vec![123u8; 136]; + let (content, range) = run_keccak256_test_inner(&data, 31); + pretty_print_memory_dump(&content, range); +} + +#[test] +fn test_two_rounds_of_keccak256() { + let data = vec![123u8; 200]; + let (content, range) = run_keccak256_test_inner(&data, 0); + pretty_print_memory_dump(&content, range); +} + +#[test] +fn test_two_rounds_of_keccak256_unaligned() { + let data = vec![123u8; 200]; + let (content, range) = run_keccak256_test_inner(&data, 31); + pretty_print_memory_dump(&content, range); +} diff --git a/crates/zk_evm/src/testing/tests/precompiles/mod.rs b/crates/zk_evm/src/testing/tests/precompiles/mod.rs index 21ad581..0706c4c 100644 --- a/crates/zk_evm/src/testing/tests/precompiles/mod.rs +++ b/crates/zk_evm/src/testing/tests/precompiles/mod.rs @@ -1,14 +1,14 @@ use super::*; mod keccak256; -mod sha256; -mod ecrecover; +// mod sha256; +// mod ecrecover; -fn pretty_print_memory_dump(content: &Vec<[u8; 32]>, range: std::ops::Range) { +fn pretty_print_memory_dump(content: &Vec<[u8; 32]>, range: std::ops::Range) { println!("Memory dump:"); println!("-----------------------------------------"); for (cont, index) in content.into_iter().zip(range.into_iter()) { println!("{:04x}: 0x{}", index, hex::encode(cont)); } println!("-----------------------------------------"); -} \ No newline at end of file +} diff --git a/crates/zk_evm/src/testing/tests/trivial.rs b/crates/zk_evm/src/testing/tests/trivial.rs index f8f2988..ac0f3ed 100644 --- a/crates/zk_evm/src/testing/tests/trivial.rs +++ b/crates/zk_evm/src/testing/tests/trivial.rs @@ -1,13 +1,12 @@ -use zkevm_opcode_defs::BOOTLOADER_CODE_PAGE; use super::*; use super::simple_tracer::*; #[test] fn run_dummy_log_and_unmapped_noop() { - let tools = create_default_testing_tools(); + let mut tools = create_default_testing_tools(); let block_properties = create_default_block_info(); - let mut vm = create_initial_vm_state_for_basic_testing(tools, block_properties); + let mut vm = create_initial_vm_state_for_basic_testing(&mut tools, &block_properties); let tracing_closure = |state: &VmState<_, _, _, _, _, _>, aux: AuxTracingInformation, cycle_idx: u32| { println!("------------------------------------------------------------"); @@ -42,7 +41,7 @@ fn run_dummy_log_and_unmapped_noop() { vm.cycle(&mut debug_tracer); vm.cycle(&mut debug_tracer); - let (full_storage_access_history, storage_pre_shard, events_log_history, events, l1_messages, _) = get_final_net_states(vm); + let (full_storage_access_history, storage_pre_shard, events_log_history, events, l1_messages, _) = get_final_net_states!(vm, tools); println!("------------------------------------------------------"); println!("Storage log access history:"); @@ -61,10 +60,10 @@ fn run_dummy_log_and_unmapped_noop() { fn check_nop_moves_sp() { let mut tools = create_default_testing_tools(); let block_properties = create_default_block_info(); - let mut vm = create_initial_vm_state_for_basic_testing(tools, block_properties); + let mut vm = create_initial_vm_state_for_basic_testing(&mut tools, &block_properties); let default_tracing_tool = DefaultTracingClosure::new(); - let mut debug_tracer = ClosureBasedTracer::new(|a, b, c| default_tracing_tool.trace(a, b, c)); + let mut debug_tracer = ClosureBasedTracer::new(|a, b, c| default_tracing_tool.trace(a, b, c)); // manually encode LE let opcode = "000100005000002000"; diff --git a/crates/zk_evm/src/tests/assembly.rs b/crates/zk_evm/src/tests/assembly.rs deleted file mode 100644 index e9d9907..0000000 --- a/crates/zk_evm/src/tests/assembly.rs +++ /dev/null @@ -1,468 +0,0 @@ -use crate::tests::set_account_type; -use crate::vm_runner::{run_vm, VmLaunchOption, VmSnapshot}; -use crate::VmExecutionResult; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::num::ParseIntError; -use std::path::PathBuf; -use std::str::FromStr; - -#[test] -fn factorial_5() { - let assembly = - zkevm_assembly::Assembly::try_from(PathBuf::from("examples/factorial.sasm")).unwrap(); - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(5)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[0], U256::from(120)); //5! = 120 -} - -#[test] -fn errors_in_contract() { - // You can find the exact contract code in etc/contracts-test-data/error-contract/SimpleRequire.sol - let assembly = - zkevm_assembly::Assembly::try_from(PathBuf::from("examples/require.zasm")).unwrap(); - let short_calldata = decode_hex("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e6136e38").unwrap(); - let long_calldata = decode_hex("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5fa981").unwrap(); - let new_error_calldata = decode_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058f822ca").unwrap(); - - let mut storage = HashMap::new(); - let storage_key = StorageKey::UserKey( - AccountTreeId::Rollup(Address::default()), - AccountTreeId::Rollup(Address::default()), - H256::from_str("0xc49821d8653c58e43c7c6b0f17e15eb7f774bf3333419e9bedb01be51ae97e7c") - .unwrap(), - ); - storage.insert(storage_key, u32_to_h256(1)); - set_account_type(&mut storage, &Address::default(), &AccountType::ZkRollup); - let VmSnapshot { - execution_result: VmExecutionResult { revert_reason, .. }, - .. - } = run_vm( - assembly.clone(), - short_calldata, - storage.clone(), - vec![], - None, - VmLaunchOption::Default, - usize::MAX, - ); - let revert_reason = revert_reason.unwrap(); - assert_eq!(revert_reason.require_msg().unwrap(), "short"); - assert_eq!(hex::encode(revert_reason.complete_byte_message()),"08c379a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000573686f7274000000000000000000000000000000000000000000000000000000"); - let VmSnapshot { - execution_result: VmExecutionResult { revert_reason, .. }, - .. - } = run_vm( - assembly.clone(), - long_calldata, - storage.clone(), - vec![], - None, - VmLaunchOption::Default, - usize::MAX, - ); - assert_eq!(revert_reason.unwrap().require_msg().unwrap(), "llonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextblonglonglongtextbonglonglongtextb"); - let VmSnapshot { - execution_result: VmExecutionResult { revert_reason, .. }, - .. - } = run_vm( - assembly.clone(), - new_error_calldata, - storage.clone(), - vec![], - None, - VmLaunchOption::Default, - usize::MAX, - ); - //Correct error encoded with abi - assert_eq!(hex::encode(revert_reason.unwrap().msg), "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000046461746100000000000000000000000000000000000000000000000000000000"); -} - -#[test] -fn factorial_rec_5() { - let assembly = - zkevm_assembly::Assembly::try_from(PathBuf::from("examples/factorial_rec.sasm")).unwrap(); - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(5)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[0], U256::from(120)); //5! = 120 -} - -#[test] -#[ignore] // ignore because fibonacci.sasm is generated with compiler with old sp handling -fn fibonacci_23() { - vlog::init(); - let assembly = - zkevm_assembly::Assembly::try_from(PathBuf::from("examples/fibonacci.sasm")).unwrap(); - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(5)], - None, - VmLaunchOption::Label(String::from("three")), - usize::MAX, - ); - - assert_eq!(registers[0], U256::from(63245986)); //23th fibonacci number = 17711 -} - -#[test] -#[ignore] -fn test_increase_storage() { - vlog::init(); - let asm_text = r#" - .text - .file "main" - .type reverse,@function -reverse: - push #33, r0 - add r1, r0, r4 - sfll #0, r5, r5 - sflh #0, r5, r5 - sfll #320, r3, r3 - sflh #0, r3, r3 - sfll #736, r0, r1 - add r1, r3, r3 - mov r3, 22(sp) - sfll #288, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 7(sp) - sfll #224, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 9(sp) - sfll #192, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 8(sp) - sfll #160, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 6(sp) - sfll #128, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 5(sp) - sfll #96, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 4(sp) - sfll #64, r3, r3 - sflh #0, r3, r3 - add r1, r3, r3 - mov r3, 3(sp) - sfll #32, r6, r6 - sflh #0, r6, r6 - div r2, r6, r3, r0 - mov r6, 23(sp) - add r1, r6, r2 - mov r2, 2(sp) - sfll #256, r6, r6 - sflh #0, r6, r6 - add r1, r6, r1 - mov r1, 1(sp) - add 1088, r4, r1 - mov r1, 11(sp) - mov 35(sp-r3), r2 - mov 36(sp-r3), r1 - mov r1, 13(sp) - mov 37(sp-r3), r1 - mov r1, 20(sp) - mov 38(sp-r3), r1 - mov r1, 19(sp) - mov 39(sp-r3), r1 - mov r1, 18(sp) - mov 40(sp-r3), r1 - mov r1, 17(sp) - mov 41(sp-r3), r1 - mov r1, 16(sp) - mov 42(sp-r3), r1 - mov r1, 15(sp) - mov 43(sp-r3), r1 - mov r1, 14(sp) - mov 44(sp-r3), r1 - mov 45(sp-r3), r4 - mov r4, 34(sp) - div r4, r6, r0, r3 - mov r3, 10(sp) - mov r1, 12(sp) - mov r1, 33(sp) - mov 14(sp), r1 - mov r1, 32(sp) - mov 15(sp), r1 - mov r1, 31(sp) - mov 16(sp), r1 - mov r1, 30(sp) - mov 17(sp), r1 - mov r1, 29(sp) - mov 18(sp), r1 - mov r1, 28(sp) - mov 19(sp), r1 - mov r1, 27(sp) - mov 20(sp), r1 - mov r1, 26(sp) - mov 13(sp), r1 - mov r1, 25(sp) - mov r2, 24(sp) - sfll #2, r3, r3 - sflh #0, r3, r3 - mov r3, 21(sp) - mov 10(sp), r1 - sub r1, r3, r0 - jlt .LBB0_4, .LBB0_1 -.LBB0_1: - sub r0, r5, r3 - sfll #340282366920938463463374607431768211455, r1, r1 - sflh #340282366920938463463374607431768211455, r1, r1 - add r3, r1, r1 - add r4, r1, r3 - div r3, r6, r0, r3 - mov 24(sp-r3), r3 - div r3, r6, r0, r3 - div r5, r6, r0, r4 - mov r3, 24(sp-r4) - mov 23(sp), r3 - mov 22(sp), r4 - div r4, r3, r3, r0 - mov 1(sp-r3), r4 - add r4, r1, r1 - div r2, r6, r0, r2 - div r1, r6, r0, r1 - mov r2, 24(sp-r1) - sfll #1, r1, r1 - sflh #0, r1, r1 - add r5, r1, r5 - mov 1(sp-r3), r4 - div r4, r6, r0, r1 - mov 21(sp), r2 - div r1, r2, r1, r0 - div r5, r6, r0, r2 - sub r2, r1, r0 - jge .LBB0_3, .LBB0_2 -.LBB0_2: - mov 24(sp-r2), r1 - div r1, r6, r0, r2 - j .LBB0_1, .LBB0_1 -.LBB0_3: - mov 23(sp), r1 - mov 1(sp), r2 - div r2, r1, r2, r0 - mov r2, 19(sp) - mov 7(sp), r2 - div r2, r1, r6, r0 - mov 2(sp), r2 - div r2, r1, r2, r0 - mov r2, 22(sp) - mov 3(sp), r2 - div r2, r1, r2, r0 - mov r2, 21(sp) - mov 4(sp), r2 - div r2, r1, r2, r0 - mov r2, 20(sp) - mov 5(sp), r2 - div r2, r1, r2, r0 - mov r2, 18(sp) - mov 6(sp), r2 - div r2, r1, r5, r0 - mov 8(sp), r2 - div r2, r1, r3, r0 - mov 9(sp), r2 - div r2, r1, r1, r0 - mov 24(sp), r2 - mov 1(sp-r6), r6 - mov r6, 12(sp) - mov 19(sp), r6 - mov 1(sp-r6), r6 - mov r6, 14(sp) - mov 1(sp-r1), r1 - mov r1, 15(sp) - mov 1(sp-r3), r1 - mov r1, 16(sp) - mov 1(sp-r5), r1 - mov r1, 17(sp) - mov 18(sp), r1 - mov 1(sp-r1), r1 - mov r1, 18(sp) - mov 20(sp), r1 - mov 1(sp-r1), r1 - mov r1, 19(sp) - mov 21(sp), r1 - mov 1(sp-r1), r1 - mov r1, 20(sp) - mov 22(sp), r1 - mov 1(sp-r1), r1 - mov r1, 13(sp) -.LBB0_4: - mov 11(sp), r1 - mov 23(sp), r3 - div r1, r3, r3, r0 - mov r4, 11(sp-r3) - mov 12(sp), r4 - mov r4, 10(sp-r3) - mov 14(sp), r4 - mov r4, 9(sp-r3) - mov 15(sp), r4 - mov r4, 8(sp-r3) - mov 16(sp), r4 - mov r4, 7(sp-r3) - mov 17(sp), r4 - mov r4, 6(sp-r3) - mov 18(sp), r4 - mov r4, 5(sp-r3) - mov 19(sp), r4 - mov r4, 4(sp-r3) - mov 20(sp), r4 - mov r4, 3(sp-r3) - mov 13(sp), r4 - mov r4, 2(sp-r3) - mov r2, 1(sp-r3) - pop #33, r0 - ret -reverse_test: - push #26, r0 - sfll #9, r1, r1 - sflh #0, r1, r1 - mov r1, 25(sp) - sfll #4, r1, r1 - sflh #0, r1, r1 - mov r1, 24(sp) - sfll #5, r1, r1 - sflh #0, r1, r1 - mov r1, 23(sp) - sfll #10, r1, r1 - sflh #0, r1, r1 - mov r1, 27(sp) - mov r1, 21(sp) - sfll #8, r1, r1 - sflh #0, r1, r1 - mov r1, 20(sp) - sfll #6, r1, r1 - sflh #0, r1, r1 - mov r1, 4(sp) - mov r1, 26(sp) - sfll #3, r1, r1 - sflh #0, r1, r1 - mov r1, 2(sp) - mov r1, 22(sp) - sfll #1, r1, r1 - sflh #0, r1, r1 - mov r1, 1(sp) - mov r1, 19(sp) - sfll #2, r1, r1 - sflh #0, r1, r1 - mov r1, 5(sp) - mov r1, 18(sp) - sfll #7, r1, r1 - sflh #0, r1, r1 - mov r1, 3(sp) - mov r1, 17(sp) - sfll #160, r0, r1 - sfll #512, r0, r2 - call reverse - mov 1(sp), r5 - sfll #0, r2, r2 - sflh #0, r2, r2 - mov 10(sp), r3 - add r5, r0, r1 - mov 2(sp), r4 - sub r3, r4, r0 - je .LBB1_2, .LBB1_1 -.LBB1_1: - add r2, r0, r1 -.LBB1_2: - mov 15(sp), r4 - add r5, r0, r3 - mov 3(sp), r6 - sub r4, r6, r0 - je .LBB1_4, .LBB1_3 -.LBB1_3: - add r2, r0, r3 -.LBB1_4: - mov 6(sp), r4 - mov 4(sp), r6 - sub r4, r6, r0 - je .LBB1_6, .LBB1_5 -.LBB1_5: - add r2, r0, r5 -.LBB1_6: - mul r5, r3, r2, r0 - mul r1, r2, r1, r0 - mov 5(sp), r2 - div r1, r2, r0, r1 - pop #26, r0 - ret -.Lfunc_end1: - .size reverse_test, .Lfunc_end1-reverse_test - - .section ".note.GNU-stack","",@progbits - "#; - - let assembly = zkevm_assembly::Assembly::try_from(asm_text.to_owned()).unwrap(); - let calldata = r#" - 0100 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0100 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 7bfc 6c64 - 0500 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0000 - - "# - .replace(|c: char| c.is_whitespace(), ""); - - let decoded_calldata = decode_hex(&calldata).expect("Decoding failed"); - let storage_key = UserKey( - Rollup(H160::zero()), - Rollup(H160::zero()), - H256::from_low_u64_be(1), - ); - let mut initial_storage: HashMap = HashMap::new(); - initial_storage.insert(storage_key.clone(), H256::from_low_u64_be(43)); - let VmSnapshot { storage, .. } = run_vm( - assembly, - decoded_calldata, - initial_storage, - vec![], - None, - VmLaunchOption::Default, - usize::MAX, - ); - assert_eq!(storage.get(&storage_key), Some(&H256::from_low_u64_be(48))) // 43 + 5 = 48 -} - -pub fn decode_hex(s: &str) -> Result, ParseIntError> { - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) - .collect() -} diff --git a/crates/zk_evm/src/tests/mod.rs b/crates/zk_evm/src/tests/mod.rs deleted file mode 100644 index c33b137..0000000 --- a/crates/zk_evm/src/tests/mod.rs +++ /dev/null @@ -1,1750 +0,0 @@ -#![allow(clippy::bool_assert_comparison)] // There are a lot of bool checks, direct comparison is more declarative. - -mod assembly; - -use crate::vm::VmExecutionContext; -use crate::vm_runner::{run_vm_multi_contracts, set_account_type, RawInMemoryStorage}; -use crate::{run_vm, VmExecutionResult, VmInstance, VmLaunchOption, VmSnapshot}; -use std::collections::HashMap; -use zkevm_assembly::*; -use zksync_types::AccountTreeId::Rollup; -use zksync_types::{ - utils::{address_to_h256, h256_to_u256}, - Address, BlockNumber, VmEvent, H160, H256, U256, -}; -use zksync_types::{AccountTreeId, AccountType, StorageKey}; - -const NULL: RegisterOperand = RegisterOperand::Null; -const RG0: RegisterOperand = RegisterOperand::Register(0); -const RG1: RegisterOperand = RegisterOperand::Register(1); -const RG2: RegisterOperand = RegisterOperand::Register(2); -const RG3: RegisterOperand = RegisterOperand::Register(3); -const RG4: RegisterOperand = RegisterOperand::Register(4); -const RG5: RegisterOperand = RegisterOperand::Register(5); - -const RF0: FullOperand = FullOperand::Register(RG0); -const RF1: FullOperand = FullOperand::Register(RG1); -const RF2: FullOperand = FullOperand::Register(RG2); - -const OP_RETURN: Instruction = - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }); - -#[test] -fn test_calldata_allocation() { - let storage = &mut dummy_storage(); - let mut calldata_word_2_bytes = [0u8; 32]; - U256::from(42424242).to_little_endian(&mut calldata_word_2_bytes); - let mut calldata = vec![0x0; 32]; - calldata.extend_from_slice(&calldata_word_2_bytes); - let mut vm = VmInstance::new( - storage, - &calldata, - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - - vm.dispatch_opcode(Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::SharedParent, - offset: 0, - register: NULL, - }, - operation: DataOperation::Read { destination: RG0 }, - })) - .unwrap(); - - vm.dispatch_opcode(Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::SharedParent, - offset: 1, - register: NULL, - }, - operation: DataOperation::Read { destination: RG1 }, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG0), U256::zero()); - assert_eq!(vm.read_reg(RG1), U256::from(42424242)); -} - -#[test] -fn test_add_execution() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::from(1)); - vm.write_reg(RG1, U256::from(2)); - - vm.dispatch_opcode(Instruction::Add(AddInstruction { - source_1: RF0, - source_2: RG1, - destination: RG2, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), U256::from(3)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); -} - -#[test] -fn test_bitwise_xor() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::from(0b01011011)); - vm.write_reg(RG1, U256::from(0b10000001)); - - vm.dispatch_opcode(Instruction::Bitwise(BitwiseInstruction { - source_1: RF0, - source_2: RG1, - destination: RG2, - op_type: BitwiseOpType::Xor, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), U256::from(0b11011010)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); -} - -#[test] -fn test_bitwise_shift() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::from(3 + (17 << 8))); // only first 8 bytes must be considered - vm.write_reg(RG1, U256::from(0b010111011011) + (U256::one() << 255)); - - vm.dispatch_opcode(Instruction::Shift(ShiftInstruction { - source_1: RF0, - source_2: RG1, - destination: RG2, - is_cyclic: false, - is_right: false, - })) - .unwrap(); - - vm.dispatch_opcode(Instruction::Shift(ShiftInstruction { - source_1: RF0, - source_2: RG1, - destination: RG3, - is_cyclic: false, - is_right: true, - })) - .unwrap(); - - vm.dispatch_opcode(Instruction::Shift(ShiftInstruction { - source_1: RF0, - source_2: RG1, - destination: RG4, - is_cyclic: true, - is_right: false, - })) - .unwrap(); - - vm.dispatch_opcode(Instruction::Shift(ShiftInstruction { - source_1: RF0, - source_2: RG1, - destination: RG5, - is_cyclic: true, - is_right: true, - })) - .unwrap(); - assert_eq!(vm.read_reg(RG2), U256::from(0b010111011011000)); - assert_eq!( - vm.read_reg(RG3), - U256::from(0b010111011) + (U256::one() << 252) - ); - assert_eq!(vm.read_reg(RG4), U256::from(0b010111011011100)); - assert_eq!( - vm.read_reg(RG5), - U256::from(0b010111011) + (U256::from(0b111) << 252) - ); -} - -#[test] -fn test_add_execution_overflow() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::max_value()); - vm.write_reg(RG1, U256::from(44)); - - vm.dispatch_opcode(Instruction::Add(AddInstruction { - source_1: RF0, - source_2: RG1, - destination: RG2, - })) - .unwrap(); - assert_eq!(vm.read_reg(RG2), U256::from(43)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, true); -} - -#[test] -fn test_mul_execution() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::from(1000)); - vm.write_reg(RG1, U256::from(43)); - - vm.dispatch_opcode(Instruction::Mul(MulInstruction { - source_1: RF0, - source_2: RG1, - destination_1: RG2, - destination_2: RG3, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), U256::from(43000)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, true); - assert_eq!(vm.flags.greater_than_flag, false); -} - -#[test] -fn test_mul_execution_overflow() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, (U256::max_value() - 1) / 2); - vm.write_reg(RG1, U256::from(3)); - - vm.dispatch_opcode(Instruction::Mul(MulInstruction { - source_1: RF0, - source_2: RG1, - destination_1: RG2, - destination_2: RG3, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), (U256::max_value() - 4) / 2); - assert_eq!(vm.read_reg(RG3), U256::one()); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); - assert_eq!(vm.flags.greater_than_flag, true); -} - -#[test] -fn test_div_execution() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::from(43)); - vm.write_reg(RG1, U256::from(10)); - - vm.dispatch_opcode(Instruction::Div(DivInstruction { - source_1: RF0, - source_2: RG1, - quotient_destination: RG2, - remainder_destination: RG3, - swap_operands: false, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), U256::from(4)); - assert_eq!(vm.read_reg(RG3), U256::from(3)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); - assert_eq!(vm.flags.equality_flag, false); - assert_eq!(vm.flags.greater_than_flag, true); -} - -#[test] -fn test_div_execution_zero() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - - vm.write_reg(RG0, U256::from(43)); - vm.write_reg(RG1, U256::from(0)); - - vm.dispatch_opcode(Instruction::Div(DivInstruction { - source_1: RF0, - source_2: RG1, - quotient_destination: RG2, - remainder_destination: RG3, - swap_operands: false, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), U256::from(0)); - assert_eq!(vm.read_reg(RG3), U256::from(0)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); - assert_eq!(vm.flags.equality_flag, true); - assert_eq!(vm.flags.greater_than_flag, true); -} - -#[test] -fn test_div_execution_both_zero() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG0, U256::from(0)); - vm.write_reg(RG1, U256::from(0)); - - vm.dispatch_opcode(Instruction::Div(DivInstruction { - source_1: RF0, - source_2: RG1, - quotient_destination: RG2, - remainder_destination: RG3, - swap_operands: false, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG2), U256::from(0)); - assert_eq!(vm.read_reg(RG3), U256::from(0)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); - assert_eq!(vm.flags.equality_flag, true); - assert_eq!(vm.flags.greater_than_flag, false); -} - -#[test] -fn test_shuffle_lowest() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.dispatch_opcode(Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(43), - source_2: NULL, - destination: RG3, - load_in_low: true, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG3), U256::from(43)); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); - assert_eq!(vm.flags.equality_flag, false); - assert_eq!(vm.flags.greater_than_flag, false); -} - -#[test] -fn test_immediate_highest() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.dispatch_opcode(Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(43), - source_2: NULL, - destination: RG3, - load_in_low: false, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG3), (U256::from(u128::MAX) + 1) * 43); - assert_eq!(vm.flags.error_overflow_or_less_than_flag, false); - assert_eq!(vm.flags.equality_flag, false); - assert_eq!(vm.flags.greater_than_flag, false); -} - -#[test] -fn test_hash_zero() { - let storage = &mut dummy_storage(); - let mut vm = VmInstance::new( - storage, - &Vec::new(), - dummy_context(), - usize::MAX, - Default::default(), - ) - .unwrap(); - vm.write_reg(RG1, U256::from(123)); - - vm.dispatch_opcode(Instruction::HashOutput(HashOutputInstruction { - destination: RG1, - })) - .unwrap(); - - assert_eq!(vm.read_reg(RG1), U256::zero()); -} - -#[test] -fn test_hash_reset() { - let assembly = Assembly { - instructions: vec![ - Instruction::HashAbsorb(HashAbsorbInstruction { - source: RF1, - reset: false, - }), - Instruction::HashAbsorb(HashAbsorbInstruction { - source: RF2, - reset: true, - }), - Instruction::HashOutput(HashOutputInstruction { destination: RG3 }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::zero(), U256::from(123), U256::zero()], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!( - registers[3], - U256::from_dec_str( - "5862139083026206628832301127335791535962889136620943667582563443715053309945" - ) - .unwrap() - ); -} - -#[test] -fn test_hash() { - let assembly = Assembly { - instructions: vec![ - Instruction::HashAbsorb(HashAbsorbInstruction { - source: RF1, - reset: false, - }), - Instruction::HashAbsorb(HashAbsorbInstruction { - source: RF2, - reset: false, - }), - Instruction::HashOutput(HashOutputInstruction { destination: RG3 }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::zero(), U256::from(123), U256::from(125)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!( - registers[3], - U256::from_dec_str( - "9683768255054160865462551840877242526106853161404912121878200381733926296023" - ) - .unwrap() - ); -} - -#[test] -fn test_jump() { - // the second instruction will be skipped - let assembly = Assembly { - instructions: vec![ - Instruction::Jump(JumpInstruction { - source: FullOperand::Register(NULL), - flags: vec![JumpFlag::Unconditional], - destination_true: 2, - destination_false: 1, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(17), - source_2: NULL, - destination: RG0, - load_in_low: true, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(42), - source_2: NULL, - destination: RG2, - load_in_low: true, - }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::zero(), U256::from(123), U256::from(125)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], U256::from(42)); - assert_eq!(registers[0], U256::from(0)); -} - -#[test] -fn test_jump_source_zero() { - let assembly = Assembly { - instructions: vec![ - Instruction::Jump(JumpInstruction { - source: RF0, - flags: vec![], - // should jump here - destination_true: 2, - destination_false: 1, - }), - // this instruction should be skipped - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(17), - source_2: NULL, - destination: RG2, - load_in_low: true, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(42), - source_2: NULL, - destination: RG3, - load_in_low: true, - }), - Instruction::Jump(JumpInstruction { - source: RF1, - flags: vec![], - destination_true: 5, - // should jump here - no skipped instructions - destination_false: 4, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(17), - source_2: NULL, - destination: RG4, - load_in_low: true, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(42), - source_2: NULL, - destination: RG5, - load_in_low: true, - }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - // first register: first two bytes are not zero, flag evaluates to true - // second register: first two bytes are zero, flag evaluates to false - Vec::new(), - HashMap::new(), - vec![U256::from(1 << 15), U256::from(1 << 17)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], U256::from(0)); - assert_eq!(registers[3], U256::from(42)); - assert_eq!(registers[4], U256::from(17)); - assert_eq!(registers[5], U256::from(42)); -} - -#[test] -fn test_mul_memory_stack() { - let assembly = Assembly { - instructions: vec![ - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Write { source: RG0 }, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(20), - source_2: NULL, - destination: RG1, - load_in_low: true, - }), - Instruction::Mul(MulInstruction { - source_1: FullOperand::Memory(MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, // pop one element - the one added in the preceding memory op (= 7) - register: RegisterOperand::Null, - }), - source_2: RG1, // = 20 - destination_1: RG2, - destination_2: RG3, - }), - OP_RETURN, - ], - labels: HashMap::new(), - - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { - registers, - first_contract_stack, - .. - } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(7)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], U256::from(140)); - // make sure stack pop clears the poped element - assert_eq!(first_contract_stack.read(0).unwrap(), U256::zero()); -} - -#[test] -fn test_mul_memory_local() { - let assembly = Assembly { - instructions: vec![ - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Local, - offset: 5, - register: RG0, // = 3 - }, - operation: DataOperation::Write { source: RG1 }, // = 5 - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(20), - source_2: NULL, - destination: RG1, - load_in_low: true, - }), - Instruction::Mul(MulInstruction { - source_1: FullOperand::Memory(MemoryOperand { - r#type: MemoryType::Local, - offset: 8, - register: RegisterOperand::Null, - }), // = 5 - source_2: RG1, // = 20 - destination_1: RG2, - destination_2: RG3, - }), - OP_RETURN, - ], - labels: HashMap::new(), - - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { - registers, - first_contract_local_memory, - .. - } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(3), U256::from(5)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], U256::from(100)); - assert_eq!(first_contract_local_memory.read(8).unwrap(), U256::from(5)); -} - -#[test] -fn test_jump_lesser_than() { - let assembly = Assembly { - instructions: vec![ - Instruction::Sub(SubInstruction { - source_1: RF0, - source_2: RG1, - destination: NULL, // we only need it to set flags - swap_operands: false, - }), - Instruction::Jump(JumpInstruction { - source: FullOperand::Register(NULL), - flags: vec![JumpFlag::LesserThan], - // should jump here - destination_true: 3, - destination_false: 2, - }), - // this instruction should be skipped - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(17), - source_2: NULL, - destination: RG2, - load_in_low: true, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(42), - source_2: NULL, - destination: RG3, - load_in_low: true, - }), - OP_RETURN, - ], - labels: HashMap::new(), - - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(5), U256::from(7)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], U256::zero()); - assert_eq!(registers[3], U256::from(42)); -} - -#[test] -fn test_memory_stack_push_pop() { - let assembly = Assembly { - instructions: vec![ - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Write { source: RG0 }, - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 1, // pushes two elements: zero and RG1 - register: NULL, - }, - operation: DataOperation::Write { source: RG1 }, - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Write { source: RG2 }, - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: RG3, // RG3 == 1: raw_mem_offset = 1. POP two elements. - }, - operation: DataOperation::Read { destination: RG3 }, - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: RG4, // RG4 == 0: raw_mem_offset = 1. - }, - operation: DataOperation::Read { destination: RG4 }, - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: RG4, // RG4 == 0: raw_mem_offset = 1. - }, - operation: DataOperation::Read { destination: RG5 }, - }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![ - U256::from(201), - U256::from(202), - U256::from(203), - U256::from(1), - ], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[3], U256::from(202)); - assert_eq!(registers[4], U256::from(0)); - assert_eq!(registers[5], U256::from(201)); -} - -#[test] -fn test_memory_push_pop() { - let assembly = Assembly { - instructions: vec![ - Instruction::Memory(MemoryInstruction { - // bump sp to 1 - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Write { source: RG0 }, - }), - Instruction::Memory(MemoryInstruction { - // bump sp to 3 - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 1, - register: NULL, - }, - operation: DataOperation::Write { source: RG1 }, - }), - Instruction::Memory(MemoryInstruction { - // bump sp to 5 - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 1, - register: NULL, - }, - operation: DataOperation::Write { source: RG2 }, - }), - Instruction::Memory(MemoryInstruction { - // sp is down to 2 - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 2, - register: NULL, - }, - operation: DataOperation::Read { destination: RG3 }, // value of RG1 goes here - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - // sp is down to 1 - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Read { destination: RG4 }, // NULL goes here - }), - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - // sp is down to 0 - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Read { destination: RG5 }, //value of RG0 goes here - }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { - registers, - first_contract_stack, - .. - } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(10), U256::from(20)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(first_contract_stack.read(0).unwrap(), U256::default()); - assert_eq!(first_contract_stack.read(1).unwrap(), U256::default()); - assert_eq!(first_contract_stack.read(2).unwrap(), U256::default()); - assert_eq!(first_contract_stack.read(3).unwrap(), U256::default()); - assert_eq!(first_contract_stack.read(4).unwrap(), U256::default()); - assert_eq!(registers[3], U256::from(20)); - assert_eq!(registers[4], U256::from(0)); - assert_eq!(registers[5], U256::from(10)); -} - -#[test] -fn test_storage() { - let assembly = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::Storage { - storage_key: RF0, - operation: DataOperation::Write { source: RG1 }, - is_external_storage_access: false, - }), - Instruction::Storage(StorageInstruction::Storage { - storage_key: RF0, - operation: DataOperation::Read { destination: RG2 }, - is_external_storage_access: false, - }), - OP_RETURN, - ], - labels: HashMap::new(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let address1 = Address::random(); - let address2 = Address::random(); - - let address_to_u256 = |addr| h256_to_u256(address_to_h256(&addr)); - - let mut storage = HashMap::new(); - set_account_type(&mut storage, &Address::zero(), &AccountType::ZkRollup); - - let VmSnapshot { - registers, storage, .. - } = run_vm( - assembly, - Vec::new(), - storage, - vec![address_to_u256(address1), address_to_u256(address2)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], address_to_u256(address2)); - assert_eq!( - storage.get(&StorageKey::UserKey( - AccountTreeId::Rollup(Address::zero()), - AccountTreeId::Rollup(Address::zero()), - address_to_h256(&address1) - )), - Some(&address_to_h256(&address2)) - ); -} - -#[test] -fn test_limit_cycles() { - let assembly = Assembly { - instructions: vec![ - Instruction::Context(ContextInstruction { - destination: RG0, - field: ContextField::RemainingCycles, - }), - Instruction::Sub(SubInstruction { - source_1: FullOperand::Immediate(100), - source_2: RG0, - destination: NULL, - swap_operands: false, - }), // use this to compare current cycles to 100. It's more than 100 *before* the jump, and it's less *after*. - Instruction::Jump(JumpInstruction { - source: FullOperand::Register(NULL), - flags: vec![JumpFlag::LesserThan], - destination_true: 3, - destination_false: 5, - }), - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - operand: FullOperand::Immediate(10 << 16), // bytes 2..4 represent `num_passed_cycles` - is_delegate: false, - }, - }), - Instruction::Context(ContextInstruction { - destination: RG1, - field: ContextField::RemainingCycles, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let res_reg: [U256; 6] = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![], - None, - VmLaunchOption::Default, - usize::MAX, - ) - .registers; - - assert_eq!(res_reg[0], U256::from(9)); - assert_eq!(res_reg[1], U256::from(usize::MAX - 11)); -} - -#[test] -fn test_rollback_multiple_contracts_explicit_error() { - let callee_1_contract_address = H160::from_low_u64_le(366); - let mut contract_1_address_bytes = callee_1_contract_address.as_bytes().to_vec(); - contract_1_address_bytes.reverse(); - let callee_2_contract_address = H160::from_low_u64_le(239); - let mut contract_2_address_bytes = callee_2_contract_address.as_bytes().to_vec(); - contract_2_address_bytes.reverse(); - let mut function_jump_calldata_bytes = vec![0u8; 32]; - function_jump_calldata_bytes.splice(4..24, contract_1_address_bytes); - let callee_1_calldata = U256::from_little_endian(&function_jump_calldata_bytes); - function_jump_calldata_bytes.splice(4..24, contract_2_address_bytes); - let callee_2_calldata = U256::from_little_endian(&function_jump_calldata_bytes); - - let main_contract = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::Storage { - storage_key: FullOperand::Immediate(42), - operation: DataOperation::Write { source: RG0 }, - is_external_storage_access: false, - }), - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - operand: FullOperand::Register(RG2), - is_delegate: false, - }, - }), - Instruction::Storage(StorageInstruction::Storage { - storage_key: FullOperand::Immediate(34), - operation: DataOperation::Write { source: RG0 }, - is_external_storage_access: false, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let callee_contract_1 = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::Storage { - storage_key: FullOperand::Immediate(44), - operation: DataOperation::Write { source: RG0 }, - is_external_storage_access: false, - }), - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - operand: FullOperand::Register(RG3), - is_delegate: false, - }, - }), - // we return with an explicit error. This contract and the callee contract will be reverted. - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: true }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let callee_contract_2 = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::Storage { - storage_key: FullOperand::Immediate(50), - operation: DataOperation::Write { source: RG0 }, - is_external_storage_access: false, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let mut loaded_contracts: HashMap = HashMap::new(); - loaded_contracts.insert(Address::default(), main_contract); - loaded_contracts.insert(callee_1_contract_address, callee_contract_1); - loaded_contracts.insert(callee_2_contract_address, callee_contract_2); - - let mut storage = HashMap::new(); - set_account_type(&mut storage, &Address::default(), &AccountType::ZkRollup); - set_account_type( - &mut storage, - &callee_1_contract_address, - &AccountType::ZkRollup, - ); - set_account_type( - &mut storage, - &callee_2_contract_address, - &AccountType::ZkRollup, - ); - - let VmSnapshot { storage, .. } = run_vm_multi_contracts( - loaded_contracts, - Vec::new(), - storage, - vec![ - U256::from(10), - U256::from(12), - callee_1_calldata, - callee_2_calldata, - ], - Address::default(), - None, - VmLaunchOption::Default, - usize::MAX, - ); - assert_eq!( - storage.get(&StorageKey::new_user( - Rollup(Address::default()), - Rollup(Address::default()), - H256::from_low_u64_be(42) - )), - Some(&H256::from_low_u64_be(10)) - ); // the storage write from the entry contract shouldn't be rollback - assert_eq!( - storage.get(&StorageKey::new_user( - Rollup(Address::default()), - Rollup(Address::default()), - H256::from_low_u64_be(34) - )), - Some(&H256::from_low_u64_be(10)) - ); // the storage write from the entry contract shouldn't be rollback - assert_eq!( - storage.get(&StorageKey::new_user( - Rollup(callee_1_contract_address), - Rollup(callee_1_contract_address), - H256::from_low_u64_be(44) - )), - Some(&H256::zero()) - ); // callee storages must be reverted - assert_eq!( - storage.get(&StorageKey::new_user( - Rollup(callee_2_contract_address), - Rollup(callee_2_contract_address), - H256::from_low_u64_be(50) - )), - Some(&H256::zero()) - ); // callee storages must be reverted -} - -#[test] -fn test_local_call_memory_sharing() { - let assembly = Assembly { - instructions: vec![ - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Local, - offset: 239, - register: NULL, - }, - operation: DataOperation::Write { source: RG1 }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::Local { - address: 4, // pc - operand: FullOperand::Immediate(0), - }, - }), - // read from stack - callee contract wrote there - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Read { destination: RG2 }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - // local jump goes here - // read value saved by calling contract - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Local, - offset: 239, - register: NULL, - }, - operation: DataOperation::Read { destination: RG3 }, - }), - // write to stack - calling contract should be able to pop it - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Write { source: RG0 }, - }), - // return back to the entry frame - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: true }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { registers, .. } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![U256::from(366), U256::from(30)], - None, - VmLaunchOption::Default, - usize::MAX, - ); - assert_eq!(registers[2], U256::from(366)); - assert_eq!(registers[3], U256::from(30)); -} - -#[test] -fn test_delegate_call_memory_sharing() { - let callee_contract_address = H160::from_low_u64_le(366); - let mut function_jump_calldata_bytes = vec![0u8; 32]; - let mut contract_address_bytes = callee_contract_address.as_bytes().to_vec(); - contract_address_bytes.reverse(); - function_jump_calldata_bytes.splice(4..24, contract_address_bytes); - let callee_calldata = U256::from_little_endian(&function_jump_calldata_bytes); - - let assembly_main = Assembly { - instructions: vec![ - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Local, - offset: 239, - register: NULL, - }, - operation: DataOperation::Write { source: RG1 }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - is_delegate: true, - operand: FullOperand::Register(RG2), - }, - }), - // read from stack - callee contract wrote there - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Read { destination: RG2 }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let assembly_callee = Assembly { - instructions: vec![ - // read value saved by calling contract - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Local, - offset: 239, - register: NULL, - }, - operation: DataOperation::Read { destination: RG3 }, - }), - // write to stack - calling contract should be able to pop it - Instruction::Memory(MemoryInstruction { - address: MemoryOperand { - r#type: MemoryType::Stack { force: true }, - offset: 0, - register: NULL, - }, - operation: DataOperation::Write { source: RG0 }, - }), - // return back to the entry frame - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: true }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let mut loaded_contracts: HashMap = HashMap::new(); - loaded_contracts.insert(Address::default(), assembly_main); - loaded_contracts.insert(callee_contract_address, assembly_callee); - - let VmSnapshot { registers, .. } = run_vm_multi_contracts( - loaded_contracts, - Vec::new(), - HashMap::new(), - vec![U256::from(366), U256::from(30), callee_calldata], - Address::default(), - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!(registers[2], U256::from(366)); - assert_eq!(registers[3], U256::from(30)); -} - -#[test] -fn test_limit_cycles_exception() { - let assembly = Assembly { - instructions: vec![ - Instruction::Context(ContextInstruction { - destination: RG0, - field: ContextField::RemainingCycles, - }), - Instruction::Context(ContextInstruction { - destination: RG1, - field: ContextField::RemainingCycles, - }), - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - operand: FullOperand::Immediate(1 << 16), // bytes 2..4 represent `num_passed_cycles`. Only pass one cycle. - is_delegate: false, - }, - }), - Instruction::Jump(JumpInstruction { - source: FullOperand::Register(NULL), - //make sure the Lesser flag is set (== exception flag) - flags: vec![JumpFlag::LesserThan], - destination_true: 5, - destination_false: 6, - }), - Instruction::Shuffle(ShuffleInstruction { - source_1: FullOperand::Immediate(42), - source_2: NULL, - destination: RG2, - load_in_low: true, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let VmSnapshot { - registers, - execution_result, - .. - } = run_vm( - assembly, - Vec::new(), - HashMap::new(), - vec![], - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!( - execution_result.internal_errors, - vec![(Address::default(), "Ran out of cycles.".to_owned())] - ); - assert_eq!(registers[0], U256::from(0)); - assert_eq!(registers[1], U256::from(usize::MAX - 2)); // make sure the second operation (Instruction::Cycles(CyclesInstruction { destination: RG1 })) only ran once - assert_eq!(registers[2], U256::from(42)); -} - -#[test] -fn test_events_multi_contract() { - let callee_contract_address = H160::from_low_u64_le(366); - let mut function_jump_calldata_bytes = vec![0u8; 32]; - let mut contract_address_bytes = callee_contract_address.as_bytes().to_vec(); - contract_address_bytes.reverse(); - function_jump_calldata_bytes.splice(4..24, contract_address_bytes); - let callee_calldata = U256::from_little_endian(&function_jump_calldata_bytes); - - let assembly_main = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::LogInit { - packed_lengths: packed_log_lengths(2, 32), - first_topic_or_chunk: RG2, // 533 - }), - Instruction::Storage(StorageInstruction::Log(FullOperand::Immediate(777), RG3)), //366366366 - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - // call `assembly_callee` - operand: FullOperand::Register(RG1), - is_delegate: false, - }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let assembly_callee = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::LogInit { - packed_lengths: packed_log_lengths(1, 32), - first_topic_or_chunk: RG2, // 533 - }), - Instruction::Storage(StorageInstruction::Log( - FullOperand::Immediate(239239239), - NULL, - )), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let mut loaded_contracts: HashMap = HashMap::new(); - loaded_contracts.insert(Address::default(), assembly_main); - loaded_contracts.insert(callee_contract_address, assembly_callee); - - let VmSnapshot { - execution_result: VmExecutionResult { events, .. }, - .. - } = run_vm_multi_contracts( - loaded_contracts, - Vec::new(), - HashMap::new(), - vec![ - U256::from(32), - callee_calldata, - U256::from(533), - U256::from(366366366), - ], - Address::default(), - None, - VmLaunchOption::Default, - usize::MAX, - ); - - let mut value1_bytes = [0u8; 32]; - let mut value2_bytes = [0u8; 32]; - - U256::from(366366366).to_big_endian(&mut value1_bytes); - U256::from(239239239).to_big_endian(&mut value2_bytes); - - // only one event should be emitted - - // the event in the callee contract is rolled back due to exception - assert_eq!(events.len(), 2); - assert_eq!( - events[0], - VmEvent { - address: Address::default(), - location: (BlockNumber(0), 0), - indexed_topics: vec!(H256::from_low_u64_be(533), H256::from_low_u64_be(777)), - value: value1_bytes.to_vec() - } - ); - assert_eq!( - events[1], - VmEvent { - address: callee_contract_address, - location: (BlockNumber(0), 0), - indexed_topics: vec!(H256::from_low_u64_be(533)), - value: value2_bytes.to_vec() - } - ); -} - -#[test] -fn test_events_multi_contract_exception() { - let callee_contract_address = H160::from_low_u64_le(366); - let mut function_jump_calldata_bytes = vec![0u8; 32]; - let mut contract_address_bytes = callee_contract_address.as_bytes().to_vec(); - contract_address_bytes.reverse(); - function_jump_calldata_bytes.splice(4..24, contract_address_bytes); - let callee_calldata = U256::from_little_endian(&function_jump_calldata_bytes); - - let assembly_main = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::LogInit { - packed_lengths: packed_log_lengths(1, 59), - first_topic_or_chunk: RG2, //533 - }), - Instruction::Storage(StorageInstruction::Log( - FullOperand::Immediate(366366366), - RG3, //544544544 - )), - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - // call `assembly_callee` - operand: FullOperand::Register(RG1), - is_delegate: false, - }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let assembly_callee = Assembly { - instructions: vec![ - Instruction::Storage(StorageInstruction::LogInit { - packed_lengths: packed_log_lengths(1, 59), - first_topic_or_chunk: RG2, - }), - Instruction::Storage(StorageInstruction::Log( - FullOperand::Immediate(544544544), - NULL, - )), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: true }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let mut loaded_contracts: HashMap = HashMap::new(); - loaded_contracts.insert(Address::default(), assembly_main); - loaded_contracts.insert(callee_contract_address, assembly_callee); - - let VmSnapshot { - execution_result: - VmExecutionResult { - events, - internal_errors, - .. - }, - .. - } = run_vm_multi_contracts( - loaded_contracts, - Vec::new(), - HashMap::new(), - vec![ - U256::from(59), - callee_calldata, - U256::from(533), - U256::from(544544544), - ], // 32 + 27 bytes - Address::default(), - None, - VmLaunchOption::Default, - usize::MAX, - ); - - let mut topic_bytes = [0u8; 32]; - let mut value_bytes = [0u8; 64]; - - U256::from(533).to_little_endian(&mut topic_bytes); - U256::from(366366366).to_big_endian(&mut value_bytes[0..32]); - U256::from(544544544).to_big_endian(&mut value_bytes[32..64]); - assert_eq!( - internal_errors, - vec![( - callee_contract_address, - "Contract execution resulted in a revert".to_owned() - )] - ); - // only one event should be emitted - - // the event in the callee contract is rolled back due to exception - assert_eq!(events.len(), 1); - assert_eq!( - events[0], - VmEvent { - address: Address::default(), - location: (BlockNumber(0), 0), - indexed_topics: vec!(H256::from_low_u64_be(533)), - value: value_bytes[0..59].to_vec() - } - ); -} - -#[test] -fn test_context_contract_addresses() { - let contract_address = H160::from_low_u64_le(366); - let assembly = Assembly { - instructions: vec![ - Instruction::Context(ContextInstruction { - destination: RG0, - field: ContextField::CurrentAddress, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let mut loaded_contracts: HashMap = HashMap::new(); - loaded_contracts.insert(contract_address, assembly); - - let VmSnapshot { registers, .. } = run_vm_multi_contracts( - loaded_contracts, - Vec::new(), - HashMap::new(), - vec![], - contract_address, - None, - VmLaunchOption::Default, - usize::MAX, - ); - - assert_eq!( - registers[0], - h256_to_u256(address_to_h256(&contract_address)) - ); -} - -#[test] -fn test_context_msg_sender() { - let callee_contract_address = H160::from_low_u64_le(366); - let mut function_jump_calldata_bytes = vec![0u8; 32]; - let mut contract_address_bytes = callee_contract_address.as_bytes().to_vec(); - contract_address_bytes.reverse(); - function_jump_calldata_bytes.splice(4..24, contract_address_bytes); - let callee_calldata = U256::from_little_endian(&function_jump_calldata_bytes); - - let assembly_main = Assembly { - instructions: vec![ - Instruction::Context(ContextInstruction { - destination: RG1, - field: ContextField::MsgSender, - }), - Instruction::SwitchContext, - Instruction::FunctionJump(FunctionJumpInstruction::Call { - location: FunctionJumpLocation::External { - // call `assembly_callee` - operand: FullOperand::Register(RG0), - is_delegate: false, - }, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: false }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let assembly_callee = Assembly { - instructions: vec![ - Instruction::Context(ContextInstruction { - destination: RG2, - field: ContextField::MsgSender, - }), - Instruction::FunctionJump(FunctionJumpInstruction::Return { error: true }), - ], - labels: Default::default(), - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }; - - let mut loaded_contracts: HashMap = HashMap::new(); - loaded_contracts.insert(H160::from_low_u64_be(777), assembly_main); - loaded_contracts.insert(callee_contract_address, assembly_callee); - - let VmSnapshot { registers, .. } = run_vm_multi_contracts( - loaded_contracts, - Vec::new(), - HashMap::new(), - vec![callee_calldata], // 32 + 27 bytes - H160::from_low_u64_be(777), - Some(VmExecutionContext { - msg_sender: H160::from_low_u64_be(223), - block_number: BlockNumber(0), - transaction_index: 0, - block_timestamp: 0, - entry_address: H160::from_low_u64_be(777), - }), - VmLaunchOption::Default, - usize::MAX, - ); - assert_eq!(registers[1], U256::from(223)); - assert_eq!(registers[2], U256::from(777)); -} - -fn packed_log_lengths(topics: u128, data_bytes: u128) -> FullOperand { - FullOperand::Immediate(topics + (data_bytes << 32)) -} - -fn dummy_storage() -> RawInMemoryStorage { - let mut contracts = HashMap::new(); - - contracts.insert( - Address::default(), - Assembly { - instructions: vec![OP_RETURN], - labels: HashMap::new(), - - assembly_code: "".to_string(), - pc_line_mapping: Default::default(), - }, - ); - RawInMemoryStorage { - values: Default::default(), - contracts, - } -} - -fn dummy_context() -> VmExecutionContext { - VmExecutionContext { - msg_sender: Default::default(), - block_number: BlockNumber(0), - transaction_index: 0, - block_timestamp: 0, - entry_address: Address::default(), - } -} diff --git a/crates/zk_evm/src/vm_state/cycle.rs b/crates/zk_evm/src/vm_state/cycle.rs index faca3cb..f93339d 100644 --- a/crates/zk_evm/src/vm_state/cycle.rs +++ b/crates/zk_evm/src/vm_state/cycle.rs @@ -236,7 +236,6 @@ pub fn read_and_decode< } impl< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -339,7 +338,7 @@ impl< let src1 = self.select_register_value(after_masking_decoded.src1_reg_idx); - let (src0, src1) = if after_masking_decoded.variant.swap_operands() { + let (mut src0, mut src1) = if after_masking_decoded.variant.swap_operands() { (src1, src0) } else { (src0, src1) @@ -372,6 +371,30 @@ impl< .get_current_stack() .is_kernel_mode(); + // Erase fat pointer metadata if unwanted + if !after_masking_decoded + .inner + .variant + .opcode + .src0_can_be_pointer() + && src0.is_pointer + && !is_kernel_mode + { + erase_fat_pointer_metadata(&mut src0.value); + src0.is_pointer = false; + } + if !after_masking_decoded + .inner + .variant + .opcode + .src1_can_be_pointer() + && src1.is_pointer + && !is_kernel_mode + { + erase_fat_pointer_metadata(&mut src1.value); + src1.is_pointer = false; + } + let prestate = PreState { src0, src1, diff --git a/crates/zk_evm/src/vm_state/helpers.rs b/crates/zk_evm/src/vm_state/helpers.rs index ab7129e..f9f4b6d 100644 --- a/crates/zk_evm/src/vm_state/helpers.rs +++ b/crates/zk_evm/src/vm_state/helpers.rs @@ -40,7 +40,6 @@ pub fn read_code< } impl< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -240,7 +239,9 @@ impl< previous_context, &context_entry, ); + #[allow(dropping_references)] drop(previous_context); + self.local_state.callstack.push_entry(context_entry); } @@ -300,7 +301,10 @@ impl< "trying to create bootloader frame with more ergs than VM has available" ); empty_context.ergs_remaining = remaining_for_this_frame; + + #[allow(dropping_references)] drop(empty_context); + self.start_frame(monotonic_cycle_counter, bootloader_context); let base_page = bootloader_context.base_memory_page; self.memory.start_global_frame( diff --git a/crates/zk_evm/src/vm_state/mem_ops.rs b/crates/zk_evm/src/vm_state/mem_ops.rs index 2d5a104..fdb9fb6 100644 --- a/crates/zk_evm/src/vm_state/mem_ops.rs +++ b/crates/zk_evm/src/vm_state/mem_ops.rs @@ -12,7 +12,6 @@ use zkevm_opcode_defs::Operand; impl> MemOpsProcessor { pub fn compute_addresses_and_select_operands< - 'a, S: zk_evm_abstractions::vm::Storage, M: zk_evm_abstractions::vm::Memory, EV: zk_evm_abstractions::vm::EventSink, @@ -54,6 +53,8 @@ impl> MemOpsProcessor { let current_sp = self.sp; // now we also have to decide on case of push or pop, to have push not to overflow 2^16, and pop not to underflow if is_write { + // a generalized version of 'push' + let old_sp = current_sp; let new_sp = current_sp.wrapping_add(vaddr); self.sp = new_sp; @@ -63,11 +64,12 @@ impl> MemOpsProcessor { let location = MemoryLocation { memory_type: MemoryType::Stack, page: stack_page, - index: MemoryIndex(new_sp.as_u64() as u32), + index: MemoryIndex(old_sp.as_u64() as u32), }; Some(location) } else { + // a generalized version of 'pop' let new_sp = current_sp.wrapping_sub(vaddr); self.sp = new_sp; diff --git a/crates/zk_evm/src/vm_state/mod.rs b/crates/zk_evm/src/vm_state/mod.rs index 536b32b..e087b54 100644 --- a/crates/zk_evm/src/vm_state/mod.rs +++ b/crates/zk_evm/src/vm_state/mod.rs @@ -12,13 +12,11 @@ pub mod cycle; pub mod execution_stack; pub mod helpers; pub mod mem_ops; -pub mod pending_port; pub use self::cycle::*; pub use self::execution_stack::*; pub use self::helpers::*; pub use self::mem_ops::*; -pub use self::pending_port::*; pub const SUPPORTED_ISA_VERSION: ISAVersion = ISAVersion(1); diff --git a/crates/zk_evm/src/vm_state/pending_port.rs b/crates/zk_evm/src/vm_state/pending_port.rs deleted file mode 100644 index 05fa83f..0000000 --- a/crates/zk_evm/src/vm_state/pending_port.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum PendingType { - FarCall, - Ret, - WriteLog, - UMAWrite, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct SpongePendingPort { - pub pending_type: Option, -} - -impl SpongePendingPort { - pub const fn empty() -> Self { - Self { pending_type: None } - } - - pub const fn is_any_pending(&self) -> bool { - self.pending_type.is_some() - } - - pub fn reset(&mut self) { - self.pending_type = None; - } -} diff --git a/crates/zk_evm_abstractions/.gitignore b/crates/zk_evm_abstractions/.gitignore index f2f9e58..ea10157 100644 --- a/crates/zk_evm_abstractions/.gitignore +++ b/crates/zk_evm_abstractions/.gitignore @@ -1,2 +1,4 @@ -target -Cargo.lock \ No newline at end of file +/target +/Cargo.lock + +/.idea/ diff --git a/crates/zk_evm_abstractions/CONTRIBUTING.md b/crates/zk_evm_abstractions/CONTRIBUTING.md index dd3d458..f129e60 100644 --- a/crates/zk_evm_abstractions/CONTRIBUTING.md +++ b/crates/zk_evm_abstractions/CONTRIBUTING.md @@ -1,44 +1,9 @@ # Contribution Guidelines -Hello! Thanks for your interest in joining the mission to accelerate the mass adoption of crypto for personal -sovereignty! We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes! +Thank you for considering helping out with the source code! We are extremely grateful for any consideration of +contributions to this repository. However, at this time, we generally do not accept external contributions. This policy +will change in the future, so please check back regularly for updates. -## Ways to contribute +For security issues, please contact us at [security@matterlabs.dev](mailto:security@matterlabs.dev). -There are many ways to contribute to the ZK Stack: - -1. Open issues: if you find a bug, have something you believe needs to be fixed, or have an idea for a feature, please - open an issue. -2. Add color to existing issues: provide screenshots, code snippets, and whatever you think would be helpful to resolve - issues. -3. Resolve issues: either by showing an issue isn't a problem and the current state is ok as is or by fixing the problem - and opening a PR. -4. Report security issues, see [our security policy](./github/SECURITY.md). -5. [Join the team!](https://matterlabs.notion.site/Shape-the-future-of-Ethereum-at-Matter-Labs-dfb3b5a037044bb3a8006af2eb0575e0) - -## Fixing issues - -To contribute code fixing issues, please fork the repo, fix an issue, commit, add documentation as per the PR template, -and the repo's maintainers will review the PR. -[here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) -for guidance how to work with PRs created from a fork. - -## Licenses - -If you contribute to this project, your contributions will be made to the project under both Apache 2.0 and the MIT -license. - -## Resources - -We aim to make it as easy as possible to contribute to the mission. This is still WIP, and we're happy for contributions -and suggestions here too. Some resources to help: - -1. [In-repo docs aimed at developers](docs) -2. [zkSync Era docs!](https://era.zksync.io/docs/) -3. Company links can be found in the [repo's readme](README.md) - -## Code of Conduct - -Be polite and respectful. - -### Thank you +Thank you for your support in accelerating the mass adoption of crypto for personal sovereignty! diff --git a/crates/zk_evm_abstractions/Cargo.toml b/crates/zk_evm_abstractions/Cargo.toml index faa67a0..e49b2da 100644 --- a/crates/zk_evm_abstractions/Cargo.toml +++ b/crates/zk_evm_abstractions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zk_evm_abstractions" -version = "0.140.0" +version = "0.141.0" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -18,7 +18,8 @@ serde = { version = "1", features = ["derive"] } static_assertions = "1" num_enum = "0.6" -zkevm_opcode_defs = { version = "=0.132.0", path = "../zkevm_opcode_defs" } +zkevm_opcode_defs = { version = "=0.141.0", path = "../zkevm_opcode_defs" } + [dev-dependencies] hex = "0.4" diff --git a/crates/zk_evm_abstractions/src/precompiles/ecrecover.rs b/crates/zk_evm_abstractions/src/precompiles/ecrecover.rs index e7cb779..6088397 100644 --- a/crates/zk_evm_abstractions/src/precompiles/ecrecover.rs +++ b/crates/zk_evm_abstractions/src/precompiles/ecrecover.rs @@ -147,12 +147,7 @@ impl Precompile for ECRecoverPrecompile { let v = buffer[31]; assert!(v == 0 || v == 1); - let mut serialized = Vec::with_capacity(65); - serialized.extend(r_bytes); - serialized.extend(s_bytes); - serialized.push(v); - - let pk = ecrecover_inner(hash, serialized); + let pk = ecrecover_inner(&hash, &r_bytes, &s_bytes, v); // here it may be possible to have non-recoverable k*G point, so can fail if let Ok(recovered_pubkey) = pk { @@ -251,22 +246,22 @@ impl Precompile for ECRecoverPrecompile { } pub fn ecrecover_inner( - digest: [u8; 32], - serialized_signature: Vec, + digest: &[u8; 32], + r: &[u8; 32], + s: &[u8; 32], + rec_id: u8, ) -> Result { - if digest.iter().all(|el| *el == 0) { - // zero hash is not supported by our convension at the current version, will be activated later separately - return Err(()); - } + use k256::ecdsa::{RecoveryId, Signature}; + // r, s + let mut signature = [0u8; 64]; + signature[..32].copy_from_slice(r); + signature[32..].copy_from_slice(s); // we expect pre-validation, so this check always works - let sig = - k256::ecdsa::recoverable::Signature::try_from(&serialized_signature[..]).map_err(|_| ())?; - let mut hash_array = k256::FieldBytes::default(); - let hash_array_mut_ref: &mut [u8] = hash_array.as_mut(); - hash_array_mut_ref.copy_from_slice(&digest); - - sig.recover_verifying_key_from_digest_bytes(&hash_array) - .map_err(|_| ()) + let signature = Signature::try_from(&signature[..]).map_err(|_| ())?; + + let recid = RecoveryId::try_from(rec_id).unwrap(); + + VerifyingKey::recover_from_prehash(digest, &signature, recid).map_err(|_| ()) } pub fn ecrecover_function( diff --git a/crates/zk_evm_abstractions/src/precompiles/keccak256.rs b/crates/zk_evm_abstractions/src/precompiles/keccak256.rs index aeb4b10..22471e4 100644 --- a/crates/zk_evm_abstractions/src/precompiles/keccak256.rs +++ b/crates/zk_evm_abstractions/src/precompiles/keccak256.rs @@ -8,29 +8,59 @@ use crate::vm::*; use super::precompile_abi_in_log; -pub const KECCAK_RATE_IN_U64_WORDS: usize = 17; -pub const MEMORY_READS_PER_CYCLE: usize = 5; +pub const KECCAK_RATE_BYTES: usize = 136; +pub const MEMORY_READS_PER_CYCLE: usize = 6; +pub const KECCAK_PRECOMPILE_BUFFER_SIZE: usize = MEMORY_READS_PER_CYCLE * 32; pub const MEMORY_WRITES_PER_CYCLE: usize = 1; pub const NUM_WORDS_PER_QUERY: usize = 4; -pub const NEW_WORDS_PER_CYCLE: usize = NUM_WORDS_PER_QUERY * MEMORY_READS_PER_CYCLE; - -// we need a buffer such that if we can not fill it in this block eventually it should -// also contain enough data to run another round function this time -pub const BUFFER_SIZE: usize = NEW_WORDS_PER_CYCLE + KECCAK_RATE_IN_U64_WORDS - 1; - -// since NEW_WORDS_PER_CYCLE and KECCAK_RATE_IN_U64_WORDS are co-prime we will have remainders in a buffer like -// 0 - 3 - 6 - 9 - .... - 18 (here we can actually absorb), so there is no good trick to other than check -// if we skip or not memory reads at this cycle - -// static_assertions::const_assert!(BUFFER_SIZE - NEW_WORDS_PER_CYCLE >= KECCAK_RATE_IN_U64_WORDS); +pub const KECCAK_RATE_IN_U64_WORDS: usize = KECCAK_RATE_BYTES / 8; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Keccak256RoundWitness { pub new_request: Option, - pub reads: Option<[MemoryQuery; MEMORY_READS_PER_CYCLE]>, + pub reads: [Option; MEMORY_READS_PER_CYCLE], pub writes: Option<[MemoryQuery; MEMORY_WRITES_PER_CYCLE]>, } +pub struct ByteBuffer { + pub bytes: [u8; BUFFER_SIZE], + pub filled: usize, +} + +impl ByteBuffer { + pub fn can_fill_bytes(&self, num_bytes: usize) -> bool { + self.filled + num_bytes <= BUFFER_SIZE + } + + pub fn fill_with_bytes( + &mut self, + input: &[u8; N], + offset: usize, + meaningful_bytes: usize, + ) { + assert!(self.filled + meaningful_bytes <= BUFFER_SIZE); + self.bytes[self.filled..(self.filled + meaningful_bytes)] + .copy_from_slice(&input[offset..(offset + meaningful_bytes)]); + self.filled += meaningful_bytes; + } + + pub fn consume(&mut self) -> [u8; N] { + assert!(N <= BUFFER_SIZE); + let mut result = [0u8; N]; + result.copy_from_slice(&self.bytes[..N]); + if self.filled < N { + self.filled = 0; + } else { + self.filled -= N; + } + let mut new_bytes = [0u8; BUFFER_SIZE]; + new_bytes[..(BUFFER_SIZE - N)].copy_from_slice(&self.bytes[N..]); + self.bytes = new_bytes; + + result + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Keccak256Precompile; @@ -46,16 +76,28 @@ impl Precompile for Keccak256Precompile { usize, Option<(Vec, Vec, Vec)>, ) { + let mut full_round_padding = [0u8; KECCAK_RATE_BYTES]; + full_round_padding[0] = 0x01; + full_round_padding[KECCAK_RATE_BYTES - 1] = 0x80; + let precompile_call_params = query; // read the parameters let params = precompile_abi_in_log(precompile_call_params); let timestamp_to_read = precompile_call_params.timestamp; let timestamp_to_write = Timestamp(timestamp_to_read.0 + 1); // our default timestamping agreement - let num_rounds = params.precompile_interpreted_data as usize; + let mut input_byte_offset = params.input_memory_offset as usize; + let mut bytes_left = params.input_memory_length as usize; + + let mut num_rounds = (bytes_left + (KECCAK_RATE_BYTES - 1)) / KECCAK_RATE_BYTES; + let padding_space = bytes_left % KECCAK_RATE_BYTES; + let needs_extra_padding_round = padding_space == 0; + if needs_extra_padding_round { + num_rounds += 1; + } + let source_memory_page = params.memory_page_to_read; let destination_memory_page = params.memory_page_to_write; - let mut current_read_offset = params.input_memory_offset; let write_offset = params.output_memory_offset; let mut read_queries = if B { @@ -76,15 +118,17 @@ impl Precompile for Keccak256Precompile { vec![] }; - let mut input_buffer = Buffer::new(); - let mut words_buffer = [0u64; NEW_WORDS_PER_CYCLE]; + let mut input_buffer = ByteBuffer:: { + bytes: [0u8; KECCAK_PRECOMPILE_BUFFER_SIZE], + filled: 0, + }; let mut internal_state = Keccak256::default(); for round in 0..num_rounds { let mut round_witness = Keccak256RoundWitness { new_request: None, - reads: None, + reads: [None; MEMORY_READS_PER_CYCLE], writes: None, }; @@ -92,17 +136,40 @@ impl Precompile for Keccak256Precompile { round_witness.new_request = Some(precompile_call_params); } - // fill the buffer if we can - if input_buffer.can_read_into() { - let mut reads = [MemoryQuery::empty(); MEMORY_READS_PER_CYCLE]; + let is_last = round == num_rounds - 1; + let paddings_round = needs_extra_padding_round && is_last; + + let mut bytes32_buffer = [0u8; 32]; + for idx in 0..MEMORY_READS_PER_CYCLE { + let (memory_index, unalignment) = (input_byte_offset / 32, input_byte_offset % 32); + let at_most_meaningful_bytes_in_query = 32 - unalignment; + let meaningful_bytes_in_query = if bytes_left >= at_most_meaningful_bytes_in_query { + at_most_meaningful_bytes_in_query + } else { + bytes_left + }; + + let enough_buffer_space = input_buffer.can_fill_bytes(meaningful_bytes_in_query); + let nothing_to_read = meaningful_bytes_in_query == 0; + let should_read = + nothing_to_read == false && paddings_round == false && enough_buffer_space; + + let bytes_to_fill = if should_read { + meaningful_bytes_in_query + } else { + 0 + }; + + if should_read { + input_byte_offset += meaningful_bytes_in_query; + bytes_left -= meaningful_bytes_in_query; - for query_index in 0..MEMORY_READS_PER_CYCLE { let data_query = MemoryQuery { timestamp: timestamp_to_read, location: MemoryLocation { - memory_type: MemoryType::Heap, + memory_type: MemoryType::FatPointer, page: MemoryPage(source_memory_page), - index: MemoryIndex(current_read_offset), + index: MemoryIndex(memory_index as u32), }, value: U256::zero(), value_is_pointer: false, @@ -112,38 +179,32 @@ impl Precompile for Keccak256Precompile { memory.execute_partial_query(monotonic_cycle_counter, data_query); let data = data_query.value; if B { - reads[query_index] = data_query; + round_witness.reads[idx] = Some(data_query); read_queries.push(data_query); } - let mut bytes32_buffer = [0u8; 32]; data.to_big_endian(&mut bytes32_buffer[..]); - // revert endianess and push - for (i, chunk) in bytes32_buffer.chunks(8).enumerate() { - let as_u64 = u64::from_le_bytes(chunk.try_into().unwrap()); - words_buffer[query_index * NUM_WORDS_PER_QUERY + i] = as_u64; - } - - current_read_offset += 1; - } - - if B { - round_witness.reads = Some(reads); } - input_buffer.append(&words_buffer); + input_buffer.fill_with_bytes(&bytes32_buffer, unalignment, bytes_to_fill) } - // always consume rate and run keccak round function - let words = input_buffer.consume_rate(); - let mut block = [0u8; KECCAK_RATE_IN_U64_WORDS * 8]; - - for (i, word) in words.into_iter().enumerate() { - block[(i * 8)..(i * 8 + 8)].copy_from_slice(&word.to_le_bytes()); + // buffer is always large enough for us to have data + + let mut block = input_buffer.consume::(); + // apply padding + if paddings_round { + block = full_round_padding; + } else if is_last { + if padding_space == KECCAK_RATE_BYTES - 1 { + block[KECCAK_RATE_BYTES - 1] = 0x81; + } else { + block[padding_space] = 0x01; + block[KECCAK_RATE_BYTES - 1] = 0x80; + } } + // update the keccak internal state internal_state.update(&block); - let is_last = round == num_rounds - 1; - if is_last { let state_inner = transmute_state(internal_state.clone()); @@ -192,52 +253,6 @@ impl Precompile for Keccak256Precompile { } } -pub struct Buffer { - pub words: [u64; BUFFER_SIZE], - pub filled: usize, -} - -impl Buffer { - pub fn new() -> Self { - Self { - words: [0u64; BUFFER_SIZE], - filled: 0, - } - } - - pub fn reset(&mut self) { - self.words = [0u64; BUFFER_SIZE]; - self.filled = 0; - } - - pub fn can_read_into(&self) -> bool { - self.filled <= BUFFER_SIZE - NEW_WORDS_PER_CYCLE - } - - pub fn append(&mut self, data: &[u64; NEW_WORDS_PER_CYCLE]) { - debug_assert!( - self.filled <= BUFFER_SIZE - NEW_WORDS_PER_CYCLE, - "have {} words filled, but the limit is {}", - self.filled, - BUFFER_SIZE - NEW_WORDS_PER_CYCLE - ); - self.words[self.filled..(self.filled + NEW_WORDS_PER_CYCLE)].copy_from_slice(&data[..]); - self.filled += NEW_WORDS_PER_CYCLE; - } - - pub fn consume_rate(&mut self) -> [u64; KECCAK_RATE_IN_U64_WORDS] { - debug_assert!(self.filled >= KECCAK_RATE_IN_U64_WORDS); - let taken = self.words[..KECCAK_RATE_IN_U64_WORDS].try_into().unwrap(); - self.filled -= KECCAK_RATE_IN_U64_WORDS; - let mut tmp = [0u64; BUFFER_SIZE]; - tmp[..(BUFFER_SIZE - KECCAK_RATE_IN_U64_WORDS)] - .copy_from_slice(&self.words[KECCAK_RATE_IN_U64_WORDS..]); - self.words = tmp; - - taken - } -} - pub fn keccak256_rounds_function( monotonic_cycle_counter: u32, precompile_call_params: LogQuery, @@ -256,20 +271,21 @@ pub fn keccak256_rounds_function( pub type Keccak256InnerState = [u64; 25]; +struct Sha3State { + state: [u64; 25], + _round_count: usize, +} + struct BlockBuffer { _buffer: [u8; 136], _pos: u8, } struct CoreWrapper { - core: Keccak256VarCore, + core: Sha3State, _buffer: BlockBuffer, } -struct Keccak256VarCore { - state: Keccak256InnerState, -} - static_assertions::assert_eq_size!(Keccak256, CoreWrapper); pub fn transmute_state(reference_state: Keccak256) -> Keccak256InnerState { diff --git a/crates/zk_evm_abstractions/src/precompiles/mod.rs b/crates/zk_evm_abstractions/src/precompiles/mod.rs index 7167f09..08a4754 100644 --- a/crates/zk_evm_abstractions/src/precompiles/mod.rs +++ b/crates/zk_evm_abstractions/src/precompiles/mod.rs @@ -13,6 +13,8 @@ use zkevm_opcode_defs::system_params::{ SHA256_ROUND_FUNCTION_PRECOMPILE_ADDRESS, }; +use zkevm_opcode_defs::PrecompileCallABI; + #[repr(u16)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] pub enum PrecompileAddress { @@ -21,18 +23,8 @@ pub enum PrecompileAddress { Keccak256 = KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS, } -#[derive(Clone, Copy, Debug)] -pub struct PrecompileCallParams { - pub input_location: MemoryLocation, - pub timestamp_for_input_read: Timestamp, - pub output_location: MemoryLocation, - pub timestamp_for_output_write: Timestamp, -} - -use zkevm_opcode_defs::PrecompileCallInnerABI; - -pub const fn precompile_abi_in_log(query: LogQuery) -> PrecompileCallInnerABI { - PrecompileCallInnerABI::from_u256(query.key) +pub const fn precompile_abi_in_log(query: LogQuery) -> PrecompileCallABI { + PrecompileCallABI::from_u256(query.key) } #[derive(Clone, Copy, Debug)] diff --git a/crates/zkevm_circuits/.github/workflows/ci.yaml b/crates/zkevm_circuits/.github/workflows/ci.yaml index 691210c..401d548 100644 --- a/crates/zkevm_circuits/.github/workflows/ci.yaml +++ b/crates/zkevm_circuits/.github/workflows/ci.yaml @@ -13,7 +13,7 @@ jobs: toolchain: nightly-2023-09-20 rustflags: "" - run: cargo build --verbose --release - - run: cargo test --verbose --release --all + - run: cargo test --verbose --release --all -- --test-threads=1 formatting: name: cargo fmt diff --git a/crates/zkevm_circuits/Cargo.toml b/crates/zkevm_circuits/Cargo.toml index faa9256..c19d0db 100644 --- a/crates/zkevm_circuits/Cargo.toml +++ b/crates/zkevm_circuits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zkevm_circuits" -version = "0.140.2" +version = "0.141.1" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -29,9 +29,9 @@ rand_new = { package = "rand", version = "0.8" } hex = "0.4" seq-macro = "0.3" -zkevm_opcode_defs = { version = "=0.132.0", path = "../zkevm_opcode_defs" } -cs_derive = "0" # Version is not pinned, since it's an old dependency for MultiVM only. -boojum = "0" # Version is not pinned, since it's an old dependency for MultiVM only. +zkevm_opcode_defs = { version = "=0.141.0", path = "../zkevm_opcode_defs" } +boojum = "0" +cs_derive = "0" [features] default = [] diff --git a/crates/zkevm_circuits/cs_derive/.gitignore b/crates/zkevm_circuits/cs_derive/.gitignore deleted file mode 100644 index 77147e2..0000000 --- a/crates/zkevm_circuits/cs_derive/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -Cargo.lock -.idea/ diff --git a/crates/zkevm_circuits/cs_derive/Cargo.toml b/crates/zkevm_circuits/cs_derive/Cargo.toml deleted file mode 100644 index 4a102f4..0000000 --- a/crates/zkevm_circuits/cs_derive/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "cs_derive" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -syn = { version = "1.0.*", features = ["extra-traits", "printing"]} -quote = {version = "1.0.*"} -proc-macro2 = "1" -proc-macro-error = "1" - -[lib] -proc-macro = true \ No newline at end of file diff --git a/crates/zkevm_circuits/cs_derive/src/.DS_Store b/crates/zkevm_circuits/cs_derive/src/.DS_Store deleted file mode 100644 index 01117831263f46ee11bed5f9109806c7b9e43482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}&BV5S~R21WY8xM2~whF8hflcCSzn8{RWN)h-*;QO)K%&^^R7sdHp2ZtHgg=3DAPo`hR=6SNLv_lV2_Le$^ z>zD>$JH%5&V=zys?^eng?)A5Ia{YQPj0G;ur8{?*GZKXR}pm7Nr*jL;+DCQ-Ie8jm8)_Of8zF z1D!bn0LyS2!@b%2fSMh^z+q|;9+ proc_macro::TokenStream { - let derived_input = parse_macro_input!(input as DeriveInput); - - let DeriveInput { ident, data, generics, attrs, .. } = derived_input.clone(); - - let serde_remove_bounds = if let Some(serde_remove_bounds) = fetch_attr_nopanic(SERDE_REMOVE_BOUNDS, &attrs) { - let serde_remove_bounds = syn::parse_str::(&serde_remove_bounds).expect("has attr as Expr"); - - serde_remove_bounds == syn::parse_str::("true").unwrap() - } else { - false - }; - - let mut allocations = TokenStream::new(); - let mut allocations_without_value = TokenStream::new(); - let mut initializations = TokenStream::new(); - let mut placeholder_initializations = TokenStream::new(); - - match data { - syn::Data::Struct(ref struct_data) => { - match struct_data.fields { - syn::Fields::Named(ref named_fields) => { - for field in named_fields.named.iter() { - - let field_ident = field.ident.clone().expect("a field ident"); - - let allocation_line = match field.ty { - Type::Path(ref _path_ty) => derive_allocate_by_type_path(&field_ident, _path_ty), - Type::Array(ref _arr_ty) => { - derive_allocate_by_array_type(&field_ident, _arr_ty) - }, - _ => abort_call_site!("only array and path types are allowed"), - }; - allocations.extend(allocation_line); - - let allocation_without_value_line = match field.ty { - Type::Path(ref _path_ty) => derive_allocate_without_value_by_type_path(&field_ident, _path_ty), - Type::Array(ref _arr_ty) => { - derive_allocate_without_value_by_array_type(&field_ident, _arr_ty) - }, - _ => abort_call_site!("only array and path types are allowed"), - }; - allocations_without_value.extend(allocation_without_value_line); - - let placeholder_init_line = match field.ty { - Type::Path(ref _path_ty) => derive_placeholder_witness_by_type(&field_ident, _path_ty), - Type::Array(ref _arr_ty) => { - derive_placeholder_witness_by_array_type(&field_ident, _arr_ty) - }, - _ => abort_call_site!("only array and path types are allowed"), - }; - - placeholder_initializations.extend(placeholder_init_line); - - initializations.extend(quote!{ - #field_ident, - }); - } - } - _ => abort_call_site!("only named fields are allowed"), - } - } - _ => abort_call_site!("only data structs are allowed"), - } - - let comma = Comma(Span::call_site()); - - let type_params_of_allocated_struct = get_type_params_from_generics(&generics, &comma); - - let where_clause = if let Some(clause) = generics.where_clause.as_ref() { - quote! { - #clause - } - } else { - quote! {} - }; - - let field_generic_param = syn::parse_str::(&"F: SmallField").unwrap(); - let has_engine_param = has_proper_small_field_parameter(&generics.params, &field_generic_param); - if has_engine_param == false { - panic!("Expected to have `F: SmallField` somewhere in bounds"); - } - - let witness_ident = get_witness_ident(&ident); - let witness_struct = derive_witness_struct_recursive(derived_input.clone()); - - let derive_line = if serde_remove_bounds { - quote! { - #[derive(Derivative, ::serde::Serialize, ::serde::Deserialize)] - #[derivative(Clone, Debug, Hash(bound = ""), PartialEq(bound = ""), Eq(bound = ""))] - } - } else { - quote! { - #[derive(Derivative, ::serde::Serialize, ::serde::Deserialize)] - #[serde(bound = "")] - #[derivative(Clone, Debug, Hash(bound = ""), PartialEq(bound = ""), Eq(bound = ""))] - } - }; - - let expanded = quote! { - #derive_line - #witness_struct - - impl #generics CSAllocatable for #ident<#type_params_of_allocated_struct> #where_clause { - - type Witness = #witness_ident <#type_params_of_allocated_struct>; - - fn placeholder_witness() -> Self::Witness { - #witness_ident :: <#type_params_of_allocated_struct> { - #placeholder_initializations - } - } - - fn allocate>(cs: &mut CS, witness: Self::Witness) -> Self { - #allocations - - Self { - #initializations - } - } - - fn allocate_without_value>(cs: &mut CS) -> Self { - #allocations_without_value - - Self { - #initializations - } - } - } - }; - - proc_macro::TokenStream::from(expanded) -} - -fn derive_allocate_by_type_path(ident: &Ident, ty: &TypePath) -> TokenStream{ - // create a witness element - quote! { - let wit = witness.#ident.clone(); - let #ident = <#ty as CSAllocatable>::allocate(cs, wit); - } -} - -fn derive_allocate_without_value_by_type_path(ident: &Ident, ty: &TypePath) -> TokenStream{ - // create a witness element - quote! { - let #ident = <#ty as CSAllocatable>::allocate_without_value(cs); - } -} - -fn derive_allocate_by_array_type(ident: &Ident, ty: &TypeArray) -> TokenStream{ - quote! { - let wit = witness.#ident.clone(); - let #ident = <#ty as CSAllocatable>::allocate(cs, wit); - } -} - -fn derive_allocate_without_value_by_array_type(ident: &Ident, ty: &TypeArray) -> TokenStream{ - quote! { - let #ident = <#ty as CSAllocatable>::allocate_without_value(cs); - } -} - -// fn derive_get_witness_by_type(ident: &Ident, ty: &TypePath) -> TokenStream{ -// quote! { -// #ident: <#ty as CSAllocatable>::create_witness(&self.#ident)?, -// } -// } - -// fn derive_get_witness_by_array_type(ident: &Ident, ty: &TypeArray) -> TokenStream{ -// quote! { -// #ident: <#ty as CSAllocatable>::create_witness(&self.#ident)?, -// } -// } - -fn derive_placeholder_witness_by_type(ident: &Ident, ty: &TypePath) -> TokenStream{ - quote! { - #ident: <#ty as CSAllocatable>::placeholder_witness(), - } -} - -fn derive_placeholder_witness_by_array_type(ident: &Ident, ty: &TypeArray) -> TokenStream{ - quote! { - #ident: <#ty as CSAllocatable>::placeholder_witness(), - } -} - -pub(crate) fn derive_witness_struct_recursive(derived_input: DeriveInput) -> DeriveInput{ - let DeriveInput { - attrs: _attrs, - vis, - ident, - generics, - mut data, - .. - } = derived_input; - - let comma = Comma(Span::call_site()); - - match data { - Data::Struct(ref mut struct_data) => { - match struct_data.fields { - // we only use named fields for now - Fields::Named(ref mut fields) => { - for field in fields.named.iter_mut() { - let (new_ty, derive_hint) = get_equivalent_type_recursive(&field.ty); - field.ty = new_ty; - match derive_hint { - SerdeDeriveToUse::Default => { - // let att: Attribute = syn::parse_quote! { - // #[serde(bound = "")] - // }; - // field.attrs.push(att); - }, - SerdeDeriveToUse::BigArray => { - let att: Attribute = syn::parse_quote! { - #[serde(with = "crate::serde_utils::BigArraySerde")] - }; - field.attrs.push(att); - } - } - } - }, - _ => abort_call_site!("only named fields are allowed"), - } - }, - _ => abort_call_site!("only structs are allowed"), - }; - - let punc_generic_params = get_type_params_from_generics_output_params(&generics, &comma); - - let new_generics = Generics { - lt_token: generics.lt_token, - params: punc_generic_params, - gt_token: generics.gt_token, - where_clause: generics.where_clause, - }; - - let witness_ident = get_witness_ident(&ident); - - DeriveInput { - attrs: vec![], - vis: vis, - ident: witness_ident, - generics: new_generics, - data: data, - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum SerdeDeriveToUse { - Default, - BigArray -} - -// we assume that every type implements a trait -pub(crate) fn get_equivalent_type_recursive(original_ty: &Type) -> (Type, SerdeDeriveToUse) { - match original_ty { - Type::Array(ty) => { - let ts = quote! { - <#ty as CSAllocatable>::Witness - }; - let ts = proc_macro::TokenStream::from(ts); - (Type::Path(syn::parse::(ts).unwrap()), SerdeDeriveToUse::BigArray) - }, - Type::Path(ty) => { - let ts = quote! { - <#ty as CSAllocatable>::Witness - }; - let ts = proc_macro::TokenStream::from(ts); - (Type::Path(syn::parse::(ts).unwrap()), SerdeDeriveToUse::Default) - } - _ => abort_call_site!("only array and path types are allowed"), - } -} \ No newline at end of file diff --git a/crates/zkevm_circuits/cs_derive/src/lib.rs b/crates/zkevm_circuits/cs_derive/src/lib.rs deleted file mode 100644 index b136eec..0000000 --- a/crates/zkevm_circuits/cs_derive/src/lib.rs +++ /dev/null @@ -1,100 +0,0 @@ -use proc_macro::TokenStream; - -pub(crate) mod utils; -mod selectable; -mod allocatable; -mod witness_hook; -mod var_length_encodable; - -#[proc_macro_derive(CSSelectable, attributes(CSSelectableBound))] -#[proc_macro_error::proc_macro_error] -pub fn derive_selectable(input: TokenStream) -> TokenStream{ - self::selectable::derive_select(input) -} - -#[proc_macro_derive(CSAllocatable)] -#[proc_macro_error::proc_macro_error] -pub fn derive_allocatable(input: TokenStream) -> TokenStream{ - self::allocatable::derive_allocatable(input) -} - -#[proc_macro_derive(WitnessHookable, attributes(WitnessHookBound))] -#[proc_macro_error::proc_macro_error] -pub fn derive_witness_hook(input: TokenStream) -> TokenStream{ - self::witness_hook::derive_witness_hook(input) -} - -#[proc_macro_derive(CSVarLengthEncodable)] -#[proc_macro_error::proc_macro_error] -pub fn derive_var_length_encodable(input: TokenStream) -> TokenStream{ - self::var_length_encodable::derive_var_length_encodable(input) -} - -// #[proc_macro_derive(CSOrdering)] -// pub fn derive_ord(input: TokenStream) -> TokenStream{ -// self::ord::derive_ord(input) -// } - -// #[proc_macro_derive(CSOrthogonalSelectable)] -// pub fn derive_orthogonal_select(input: TokenStream) -> TokenStream{ -// self::orth_select::derive_orthogonal_select(input) -// } - -// #[proc_macro_derive(FixedLengthEncodableExt, attributes(EncodingLength, PackWithCS))] -// pub fn derive_encodable(input: TokenStream) -> TokenStream{ -// self::fixed_encodable::derive_encodable(input) -// } -// #[proc_macro_derive(FixedLengthDecodableExt, attributes(EncodingLength))] -// pub fn derive_decodable(input: TokenStream) -> TokenStream{ -// self::fixed_decodable::derive_decodable(input) -// } - -// mod witnessable; -// #[proc_macro_derive(CSWitnessable)] -// pub fn derive_witnessable(input: TokenStream) -> TokenStream{ -// self::witnessable::derive_witnessable(input) -// } - - - -// mod packable; -// #[proc_macro_derive(CSPackable)] -// pub fn derive_packable(input: TokenStream) -> TokenStream{ -// let mut _ts = proc_macro2::TokenStream::new(); -// let derived_input = self::packable::derive_packable(input, &mut _ts); - -// derived_input -// } - -// mod encodable; -// #[proc_macro_derive(CSEncodable)] -// pub fn derive_cs_encodable(input: TokenStream) -> TokenStream{ -// let mut len_expr = proc_macro2::TokenStream::new(); -// let _ = self::packable::derive_packable(input.clone(), &mut len_expr); - -// self::encodable::derive_encodable(input, len_expr).into() -// } - -// mod decodable; -// #[proc_macro_derive(CSDecodable)] -// pub fn derive_cs_decodable(input: TokenStream) -> TokenStream{ -// let mut len_expr = proc_macro2::TokenStream::new(); -// let _ = self::packable::derive_packable(input.clone(), &mut len_expr); - -// self::decodable::derive_decodable(input, len_expr).into() -// } - -// mod var_encodable; -// #[proc_macro_derive(CSVariableLengthEncodable)] -// pub fn derive_cs_var_encodable(input: TokenStream) -> TokenStream{ -// let inner_impl: proc_macro2::TokenStream = self::packable::derive_var_packable(input.clone()).into(); -// let outer_impl: proc_macro2::TokenStream = self::var_encodable::derive_var_encodable(input).into(); - -// let expanded = quote::quote!{ -// #inner_impl - -// #outer_impl -// }; - -// proc_macro::TokenStream::from(expanded).into() -// } diff --git a/crates/zkevm_circuits/cs_derive/src/selectable/mod.rs b/crates/zkevm_circuits/cs_derive/src/selectable/mod.rs deleted file mode 100644 index 1ebdf95..0000000 --- a/crates/zkevm_circuits/cs_derive/src/selectable/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort_call_site; -use quote::quote; -use syn::{ - Expr, - Ident, parse_macro_input, punctuated::Punctuated, token::Comma, DeriveInput, GenericParam, Generics, - Type, WhereClause, parse_quote, -}; - -use crate::utils::*; - -const BOUND_ATTR_NAME: &'static str = "CSSelectableBound"; - -pub(crate) fn derive_select(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let derived_input = parse_macro_input!(input as DeriveInput); - let DeriveInput { - ident, - generics, - data, - attrs, - .. - } = derived_input.clone(); - - let mut struct_initializations = TokenStream::new(); - let mut field_selections = TokenStream::new(); - - let bound = if let Some(bound) = fetch_attr_from_list(BOUND_ATTR_NAME, &attrs) { - let bound = syn::parse_str::(&bound).expect("must parse bound as WhereClause"); - - quote! { #bound } - } else { - quote! { } - }; - - match data { - syn::Data::Struct(ref struct_data) => match struct_data.fields { - syn::Fields::Named(ref named_fields) => { - for field in named_fields.named.iter() { - let field_ident = field.ident.clone().expect("should have a field elem ident"); - match field.ty { - Type::Array(ref _array_ty) => { - let field_select = quote! { - let #field_ident = Selectable::::conditionally_select(cs, flag, &a.#field_ident, &b.#field_ident); - }; - field_selections.extend(field_select); - } - Type::Path(_) => { - let field_select = quote! { - let #field_ident = Selectable::::conditionally_select(cs, flag, &a.#field_ident, &b.#field_ident); - }; - field_selections.extend(field_select); - } - _ => abort_call_site!("only array and path types are allowed"), - }; - - let init_field = quote! { - #field_ident, - }; - - struct_initializations.extend(init_field); - } - } - _ => abort_call_site!("only named fields are allowed!"), - }, - _ => abort_call_site!("only struct types are allowed!"), - } - - let comma = Comma(Span::call_site()); - - let field_generic_param = syn::parse_str::(&"F: SmallField").unwrap(); - let has_engine_param = has_proper_small_field_parameter(&generics.params, &field_generic_param); - if has_engine_param == false { - panic!("Expected to have `F: SmallField` somewhere in bounds"); - } - - // add CS to func generic params - let mut function_generic_params = Punctuated::new(); - let cs_generic_param = syn::parse_str::(&"CS: ConstraintSystem").unwrap(); - function_generic_params.push(cs_generic_param.clone()); - function_generic_params.push_punct(comma.clone()); - - let type_params_of_allocated_struct = get_type_params_from_generics(&generics, &comma); - - let function_generics = Generics { - lt_token: Some(syn::token::Lt(Span::call_site())), - params: function_generic_params, - gt_token: Some(syn::token::Gt(Span::call_site())), - where_clause: None, - }; - - - let expanded = quote! { - impl #generics Selectable for #ident<#type_params_of_allocated_struct> #bound { - fn conditionally_select #function_generics(cs: &mut CS, flag: Boolean, a: &Self, b: &Self) -> Self { - #field_selections - - Self { - #struct_initializations - } - } - } - }; - - proc_macro::TokenStream::from(expanded) -} diff --git a/crates/zkevm_circuits/cs_derive/src/utils.rs b/crates/zkevm_circuits/cs_derive/src/utils.rs deleted file mode 100644 index 7c076c5..0000000 --- a/crates/zkevm_circuits/cs_derive/src/utils.rs +++ /dev/null @@ -1,167 +0,0 @@ -use quote::__private::ext::RepToTokensExt; -use syn::punctuated::Punctuated; -use syn::{GenericParam, TypeParamBound, Generics, Ident}; - -/// Fetch an attribute string from the derived struct. -pub(crate) fn fetch_attr(name: &str, attrs: &[syn::Attribute]) -> Option { - for attr in attrs { - if let Ok(meta) = attr.parse_meta() { - match meta { - syn::Meta::NameValue(nv) => { - if nv.path.is_ident(name) { - match nv.lit { - syn::Lit::Str(ref s) =>{ - return Some(s.value()) - }, - _ => panic!("attribute {} not found", name) - } - } - } - _ => panic!("attribute {} not found", name) - } - } - } - - None -} - - -pub(crate) fn fetch_attr_from_list(name: &str, attrs: &[syn::Attribute]) -> Option { - for attr in attrs { - if attr.path.is_ident(name) { - if let Ok(meta) = attr.parse_meta() { - match meta { - syn::Meta::List(ml) => { - if let Some(nv) = ml.nested.first() { - match nv { - syn::NestedMeta::Lit( - nl - ) => { - match nl { - syn::Lit::Str(ref s) =>{ - return Some(s.value()) - }, - _ => {} - } - } - _ => {} - } - } - }, - _ => {} - } - - } - } - } - - None -} - -pub(crate) fn fetch_attr_nopanic(name: &str, attrs: &[syn::Attribute]) -> Option { - for attr in attrs { - if let Ok(meta) = attr.parse_meta() { - match meta { - syn::Meta::NameValue(nv) => { - if nv.path.is_ident(name) { - match nv.lit { - syn::Lit::Str(ref s) =>{ - return Some(s.value()) - }, - _ => {} - } - } - } - _ => {} - } - } - } - - None -} - -pub(crate) fn has_proper_small_field_parameter

( - generic_params: &Punctuated, - expected: &GenericParam -) -> bool { - for p in generic_params.iter() { - if p == expected { - return true; - } - } - return false; -} - -pub(crate) fn get_type_params_from_generics( - generics: &Generics, - punc: &P, -) -> Punctuated { - let type_params = generics.type_params(); - let const_params = generics.const_params(); - - let mut idents = Punctuated::new(); - - for param in type_params.into_iter() { - let ident = param.ident.clone(); - idents.push(ident); - idents.push_punct(punc.clone()); - } - - for param in const_params.into_iter() { - let ident = param.ident.clone(); - idents.push(ident.clone()); - idents.push_punct(punc.clone()); - } - - idents -} - -pub(crate) fn get_witness_ident(original_ident: &Ident) -> Ident{ - let mut witness_ident_str = original_ident.to_string(); - witness_ident_str.push_str(&"Witness"); - syn::parse_str(&witness_ident_str).unwrap() -} - - -pub(crate) fn get_type_params_from_generics_output_params( - generics: &Generics, - punc: &P, -) -> Punctuated { - let type_params = generics.type_params(); - let const_params = generics.const_params(); - - let mut idents = Punctuated::new(); - - for param in type_params.into_iter() { - idents.push(GenericParam::Type(param.clone())); - idents.push_punct(punc.clone()); - } - - for param in const_params.into_iter() { - idents.push(GenericParam::Const(param.clone())); - idents.push_punct(punc.clone()); - } - - idents -} - -// pub(crate) fn has_small_field_parameter

( -// generic_params: &Punctuated, -// ) -> bool { -// for p in generic_params.iter() { -// match p { -// GenericParam::Type(ty) => { -// for bound in ty.bounds.iter() { -// match bound { -// TypeParamBound::Trait(t) => { - -// }, -// _ => {} -// } -// } -// } -// _ => {} -// } -// } -// return false; -// } \ No newline at end of file diff --git a/crates/zkevm_circuits/cs_derive/src/var_length_encodable/mod.rs b/crates/zkevm_circuits/cs_derive/src/var_length_encodable/mod.rs deleted file mode 100644 index 6a753ab..0000000 --- a/crates/zkevm_circuits/cs_derive/src/var_length_encodable/mod.rs +++ /dev/null @@ -1,110 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort_call_site; -use quote::quote; -use syn::{ - Expr, - Ident, parse_macro_input, punctuated::Punctuated, token::Comma, DeriveInput, GenericParam, Generics, - Type, WhereClause, parse_quote, -}; - -use crate::utils::*; - -// const BOUND_ATTR_NAME: &'static str = "CSSelectableBound"; - -pub(crate) fn derive_var_length_encodable(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let derived_input = parse_macro_input!(input as DeriveInput); - let DeriveInput { - ident, - generics, - data, - .. - } = derived_input.clone(); - - let mut length_impls = TokenStream::new(); - let mut field_impls = TokenStream::new(); - - // let bound = if let Some(bound) = fetch_attr_from_list(BOUND_ATTR_NAME, &attrs) { - // let bound = syn::parse_str::(&bound).expect("must parse bound as WhereClause"); - - // quote! { #bound } - // } else { - // quote! { } - // }; - - match data { - syn::Data::Struct(ref struct_data) => match struct_data.fields { - syn::Fields::Named(ref named_fields) => { - for field in named_fields.named.iter() { - let field_ident = field.ident.clone().expect("should have a field elem ident"); - match field.ty { - Type::Array(ref _array_ty) => { - let field_impl = quote! { - total_len += CircuitVarLengthEncodable::::encoding_length(&self.#field_ident); - }; - length_impls.extend(field_impl); - - let field_impl = quote! { - CircuitVarLengthEncodable::::encode_to_buffer(&self.#field_ident, cs, dst); - }; - field_impls.extend(field_impl); - } - Type::Path(_) => { - let field_impl = quote! { - total_len += CircuitVarLengthEncodable::::encoding_length(&self.#field_ident); - }; - length_impls.extend(field_impl); - let field_impl = quote! { - CircuitVarLengthEncodable::::encode_to_buffer(&self.#field_ident, cs, dst); - }; - field_impls.extend(field_impl); - } - _ => abort_call_site!("only array and path types are allowed"), - }; - } - } - _ => abort_call_site!("only named fields are allowed!"), - }, - _ => abort_call_site!("only struct types are allowed!"), - } - - let comma = Comma(Span::call_site()); - - let field_generic_param = syn::parse_str::(&"F: SmallField").unwrap(); - let has_engine_param = has_proper_small_field_parameter(&generics.params, &field_generic_param); - if has_engine_param == false { - panic!("Expected to have `F: SmallField` somewhere in bounds"); - } - - // add CS to func generic params - let mut function_generic_params = Punctuated::new(); - let cs_generic_param = syn::parse_str::(&"CS: ConstraintSystem").unwrap(); - function_generic_params.push(cs_generic_param.clone()); - function_generic_params.push_punct(comma.clone()); - - let type_params_of_allocated_struct = get_type_params_from_generics(&generics, &comma); - - let function_generics = Generics { - lt_token: Some(syn::token::Lt(Span::call_site())), - params: function_generic_params, - gt_token: Some(syn::token::Gt(Span::call_site())), - where_clause: None, - }; - - - let expanded = quote! { - impl #generics CircuitVarLengthEncodable for #ident<#type_params_of_allocated_struct> { - #[inline(always)] - fn encoding_length(&self) -> usize { - let mut total_len = 0; - #length_impls; - - total_len - } - fn encode_to_buffer>(&self, cs: &mut CS, dst: &mut Vec) { - #field_impls - } - } - }; - - proc_macro::TokenStream::from(expanded) -} diff --git a/crates/zkevm_circuits/cs_derive/src/witness_hook/mod.rs b/crates/zkevm_circuits/cs_derive/src/witness_hook/mod.rs deleted file mode 100644 index e6fa1a6..0000000 --- a/crates/zkevm_circuits/cs_derive/src/witness_hook/mod.rs +++ /dev/null @@ -1,124 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort_call_site; -use quote::quote; -use syn::{ - Expr, - Ident, parse_macro_input, punctuated::Punctuated, token::Comma, DeriveInput, GenericParam, Generics, - Type, WhereClause, parse_quote, -}; - -use crate::utils::*; - -const BOUND_ATTR_NAME: &'static str = "WitnessHookBound"; - -pub(crate) fn derive_witness_hook(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let derived_input = parse_macro_input!(input as DeriveInput); - let DeriveInput { - ident, - generics, - data, - attrs, - .. - } = derived_input.clone(); - - let mut struct_initializations = TokenStream::new(); - let mut field_selections = TokenStream::new(); - let mut field_callings = TokenStream::new(); - - let bound = if let Some(bound) = fetch_attr_from_list(BOUND_ATTR_NAME, &attrs) { - let bound = syn::parse_str::(&bound).expect("must parse bound as WhereClause"); - - quote! { #bound } - } else { - quote! { } - }; - - match data { - syn::Data::Struct(ref struct_data) => match struct_data.fields { - syn::Fields::Named(ref named_fields) => { - for field in named_fields.named.iter() { - let field_ident = field.ident.clone().expect("should have a field elem ident"); - match field.ty { - Type::Array(ref _array_ty) => { - let field_select = quote! { - let #field_ident = WitnessHookable::::witness_hook(&self.#field_ident, cs); - }; - field_selections.extend(field_select); - let field_calling = quote! { - let #field_ident = (#field_ident)()?; - }; - field_callings.extend(field_calling); - } - Type::Path(_) => { - let field_select = quote! { - let #field_ident = WitnessHookable::::witness_hook(&self.#field_ident, cs); - }; - field_selections.extend(field_select); - let field_calling = quote! { - let #field_ident = (#field_ident)()?; - }; - field_callings.extend(field_calling); - } - _ => abort_call_site!("only array and path types are allowed"), - }; - - let init_field = quote! { - #field_ident, - }; - - struct_initializations.extend(init_field); - } - } - _ => abort_call_site!("only named fields are allowed!"), - }, - _ => abort_call_site!("only struct types are allowed!"), - } - - let comma = Comma(Span::call_site()); - - let field_generic_param = syn::parse_str::(&"F: SmallField").unwrap(); - let has_engine_param = has_proper_small_field_parameter(&generics.params, &field_generic_param); - if has_engine_param == false { - panic!("Expected to have `F: SmallField` somewhere in bounds"); - } - - // add CS to func generic params - let mut function_generic_params = Punctuated::new(); - let cs_generic_param = syn::parse_str::(&"CS: ConstraintSystem").unwrap(); - function_generic_params.push(cs_generic_param.clone()); - function_generic_params.push_punct(comma.clone()); - - let type_params_of_allocated_struct = get_type_params_from_generics(&generics, &comma); - - let function_generics = Generics { - lt_token: Some(syn::token::Lt(Span::call_site())), - params: function_generic_params, - gt_token: Some(syn::token::Gt(Span::call_site())), - where_clause: None, - }; - - let expanded = quote! { - impl #generics WitnessHookable for #ident<#type_params_of_allocated_struct> #bound { - fn witness_hook #function_generics( - &self, - cs: &CS, - ) -> Box Option + 'static> { - #field_selections; - - Box::new( - move || { - #field_callings; - - Some( - Self::Witness { - #struct_initializations - } - ) - } - ) - } - } - }; - - proc_macro::TokenStream::from(expanded) -} diff --git a/crates/zkevm_circuits/src/base_structures/register/mod.rs b/crates/zkevm_circuits/src/base_structures/register/mod.rs index 12a33db..780e1c1 100644 --- a/crates/zkevm_circuits/src/base_structures/register/mod.rs +++ b/crates/zkevm_circuits/src/base_structures/register/mod.rs @@ -54,6 +54,26 @@ impl VMRegister { }, } } + + pub fn conditionally_erase>( + &mut self, + cs: &mut CS, + condition: Boolean, + ) { + self.is_pointer = self.is_pointer.mask_negated(cs, condition); + self.value = self.value.mask_negated(cs, condition); + } + + pub fn conditionally_erase_fat_pointer_data>( + &mut self, + cs: &mut CS, + condition: Boolean, + ) { + self.is_pointer = self.is_pointer.mask_negated(cs, condition); + // we need to erase bits 32-64 and 64-96 + self.value.inner[1] = self.value.inner[1].mask_negated(cs, condition); + self.value.inner[2] = self.value.inner[2].mask_negated(cs, condition); + } } impl CSAllocatableExt for VMRegister { diff --git a/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs b/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs index 5c555a0..2be44fd 100644 --- a/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs +++ b/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs @@ -443,6 +443,8 @@ fn decompose_uint32_to_uint16s>( #[cfg(test)] mod tests { + use std::alloc::Global; + use crate::base_structures::decommit_query; use super::*; @@ -542,12 +544,12 @@ mod tests { use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; let builder_impl = - CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 1 << 20); + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); use boojum::cs::cs_builder::new_builder; let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); let table = create_maj4_table(); owned_cs.add_lookup_table::(table); @@ -611,7 +613,7 @@ mod tests { cs.pad_and_shrink(); let worker = Worker::new(); - let mut owned_cs = owned_cs.into_assembly(); + let mut owned_cs = owned_cs.into_assembly::(); owned_cs.print_gate_stats(); assert!(owned_cs.check_if_satisfied(&worker)); } diff --git a/crates/zkevm_circuits/src/demux_log_queue/input.rs b/crates/zkevm_circuits/src/demux_log_queue/input.rs index 119a5fc..f2f324c 100644 --- a/crates/zkevm_circuits/src/demux_log_queue/input.rs +++ b/crates/zkevm_circuits/src/demux_log_queue/input.rs @@ -16,6 +16,8 @@ use boojum::gadgets::{ use cs_derive::*; use derivative::*; +pub const NUM_OUTPUT_QUEUES: usize = 6; + #[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] #[derivative(Clone, Copy, Debug)] #[DerivePrettyComparison("true")] @@ -83,6 +85,19 @@ impl CSPlaceholder for LogDemuxerOutputData { } } +impl LogDemuxerOutputData { + pub fn all_output_queues_refs(&self) -> [&QueueState; NUM_OUTPUT_QUEUES] { + [ + &self.storage_access_queue_state, + &self.keccak256_access_queue_state, + &self.sha256_access_queue_state, + &self.ecrecover_access_queue_state, + &self.events_access_queue_state, + &self.l1messages_access_queue_state, + ] + } +} + pub type LogDemuxerInputOutput = crate::fsm_input_output::ClosedFormInput< F, LogDemuxerFSMInputOutput, diff --git a/crates/zkevm_circuits/src/demux_log_queue/mod.rs b/crates/zkevm_circuits/src/demux_log_queue/mod.rs index 438bf11..81cc1b2 100644 --- a/crates/zkevm_circuits/src/demux_log_queue/mod.rs +++ b/crates/zkevm_circuits/src/demux_log_queue/mod.rs @@ -459,6 +459,8 @@ pub fn check_if_bitmask_and_if_empty, con #[cfg(test)] mod tests { + use std::alloc::Global; + use super::*; use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; use boojum::cs::traits::gate::GatePlacementStrategy; @@ -551,12 +553,12 @@ mod tests { use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; let builder_impl = - CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 1 << 20); + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); use boojum::cs::cs_builder::new_builder; let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); // add tables let table = create_xor8_table(); @@ -592,7 +594,7 @@ mod tests { cs.pad_and_shrink(); let worker = Worker::new(); - let mut owned_cs = owned_cs.into_assembly(); + let mut owned_cs = owned_cs.into_assembly::(); owned_cs.print_gate_stats(); assert!(owned_cs.check_if_satisfied(&worker)); } diff --git a/crates/zkevm_circuits/src/ecrecover/baseline.rs b/crates/zkevm_circuits/src/ecrecover/baseline.rs index 1e01607..f688efb 100644 --- a/crates/zkevm_circuits/src/ecrecover/baseline.rs +++ b/crates/zkevm_circuits/src/ecrecover/baseline.rs @@ -675,6 +675,8 @@ where #[cfg(test)] mod test { + use std::alloc::Global; + use boojum::field::goldilocks::GoldilocksField; use boojum::gadgets::traits::allocatable::CSAllocatable; use boojum::pairing::ff::{Field, PrimeField, SqrtField}; @@ -776,7 +778,6 @@ mod test { num_constant_columns: 8, max_allowed_constraint_degree: 4, }; - let max_variables = 1 << 26; let max_trace_len = 1 << 20; fn configure< @@ -841,15 +842,12 @@ mod test { builder } - let builder_impl = CsReferenceImplementationBuilder::::new( - geometry, - max_variables, - max_trace_len, - ); + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, max_trace_len); let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); // add tables let table = create_xor8_table(); @@ -929,7 +927,7 @@ mod test { cs.pad_and_shrink(); - let mut cs = owned_cs.into_assembly(); + let mut cs = owned_cs.into_assembly::(); cs.print_gate_stats(); let worker = Worker::new(); assert!(cs.check_if_satisfied(&worker)); diff --git a/crates/zkevm_circuits/src/ecrecover/decomp_table.rs b/crates/zkevm_circuits/src/ecrecover/decomp_table.rs index 636b64e..7c7ea6e 100644 --- a/crates/zkevm_circuits/src/ecrecover/decomp_table.rs +++ b/crates/zkevm_circuits/src/ecrecover/decomp_table.rs @@ -11,10 +11,10 @@ const TABLE_NAME: &'static str = "WNAFDECOMP table"; #[derivative(Clone, Copy, Debug, PartialEq, Eq)] pub struct WnafDecompTable; -const GLV_WINDOW_SIZE: usize = 2; -const TABLE_SIZE: i8 = 1 << (GLV_WINDOW_SIZE + 1); -const HALF_TABLE_SIZE: i8 = 1 << GLV_WINDOW_SIZE; -const MASK_FOR_MOD_TABLE_SIZE: u8 = (TABLE_SIZE as u8) - 1; +pub const WNAF_WINDOW_SIZE: usize = 2; +pub const WNAF_TABLE_SIZE: i8 = 1 << (WNAF_WINDOW_SIZE + 1); +const HALF_TABLE_SIZE: i8 = 1 << WNAF_WINDOW_SIZE; +const MASK_FOR_MOD_TABLE_SIZE: u8 = (WNAF_TABLE_SIZE as u8) - 1; // Lookups for wNAF decomposition of scalars. pub fn create_wnaf_decomp_table() -> LookupTable { @@ -35,7 +35,7 @@ pub fn create_wnaf_decomp_table() -> LookupTable { if a % 2 == 1 { let mut naf = (a & MASK_FOR_MOD_TABLE_SIZE) as i8; if naf >= HALF_TABLE_SIZE { - naf -= TABLE_SIZE + naf -= WNAF_TABLE_SIZE }; let naf_abs = naf.abs() as u8; diff --git a/crates/zkevm_circuits/src/ecrecover/mod.rs b/crates/zkevm_circuits/src/ecrecover/mod.rs index 7316c0b..9ed10e6 100644 --- a/crates/zkevm_circuits/src/ecrecover/mod.rs +++ b/crates/zkevm_circuits/src/ecrecover/mod.rs @@ -70,4 +70,4 @@ fn secp256k1_scalar_field_params() -> Secp256ScalarNNFieldParams { } // re-exports for integration -pub use self::baseline::{ecrecover_function_entry_point, EcrecoverPrecompileCallParams}; +pub use self::new_optimized::{ecrecover_function_entry_point, EcrecoverPrecompileCallParams}; diff --git a/crates/zkevm_circuits/src/ecrecover/naf_abs_div2_table.rs b/crates/zkevm_circuits/src/ecrecover/naf_abs_div2_table.rs index e83dd9a..3cdd006 100644 --- a/crates/zkevm_circuits/src/ecrecover/naf_abs_div2_table.rs +++ b/crates/zkevm_circuits/src/ecrecover/naf_abs_div2_table.rs @@ -27,7 +27,7 @@ pub fn create_naf_abs_div2_table() -> LookupTable { // we need unsigned abs, to handle i8::MIN let v = a.unsigned_abs() >> 1; - smallvec::smallvec![F::from_u64_unchecked(v as u64), F::from_u64_unchecked(0u64)] + smallvec::smallvec![F::from_u64_unchecked(v as u64), F::ZERO] }, ) } diff --git a/crates/zkevm_circuits/src/ecrecover/new_optimized.rs b/crates/zkevm_circuits/src/ecrecover/new_optimized.rs index 6d8d548..646d17f 100644 --- a/crates/zkevm_circuits/src/ecrecover/new_optimized.rs +++ b/crates/zkevm_circuits/src/ecrecover/new_optimized.rs @@ -50,7 +50,7 @@ pub use self::input::*; use super::input::*; pub const MEMORY_QUERIES_PER_CALL: usize = 4; -pub const ALLOW_ZERO_MESSAGE: bool = false; +pub const ALLOW_ZERO_MESSAGE: bool = true; #[derive(Derivative, CSSelectable)] #[derivative(Clone, Debug)] @@ -81,7 +81,7 @@ impl EcrecoverPrecompileCallParams { const NUM_WORDS: usize = 17; const SECP_B_COEF: u64 = 7; -const EXCEPTION_FLAGS_ARR_LEN: usize = 9; +const EXCEPTION_FLAGS_ARR_LEN: usize = 10; const NUM_MEMORY_READS_PER_CYCLE: usize = 4; const X_POWERS_ARR_LEN: usize = 256; const VALID_Y_IN_EXTERNAL_FIELD: u64 = 4; @@ -89,8 +89,8 @@ const VALID_X_CUBED_IN_EXTERNAL_FIELD: u64 = 9; // GLV consts -// 2**128 -const TWO_POW_128: &'static str = "340282366920938463463374607431768211456"; +// Decomposition scalars can be a little more than 2^128 in practice, so we use 33 chunks of width 4 bits +const MAX_DECOMPOSITION_VALUE: U256 = U256([u64::MAX, u64::MAX, 0x0f, 0]); // BETA s.t. for any curve point Q = (x,y): // lambda * Q = (beta*x mod p, y) const BETA: &'static str = @@ -106,6 +106,13 @@ const A1: &'static str = "0x3086d221a7d46bcde86c90e49284eb15"; const B1: &'static str = "0xe4437ed6010e88286f547fa90abfe4c3"; const A2: &'static str = "0x114ca50f7a8e2f3f657c1108d9d44cfd8"; +const HALF_SUBGROUP_SIZE: &'static str = + "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"; + +const WINDOW_WIDTH: usize = 4; +const NUM_MULTIPLICATION_STEPS_FOR_WIDTH_4: usize = 33; +const PRECOMPUTATION_TABLE_SIZE: usize = (1 << WINDOW_WIDTH) - 1; + // assume that constructed field element is not zero // if this is not satisfied - set the result to be F::one fn convert_uint256_to_field_element_masked< @@ -141,6 +148,7 @@ where let (overflows, rem) = max_value.div_rem(¶ms.modulus_u1024); + assert!(overflows.lt(&U1024::from_word(1u64 << 32))); let mut max_moduluses = overflows.as_words()[0] as u32; if rem.is_zero().unwrap_u8() != 1 { max_moduluses += 1; @@ -187,6 +195,7 @@ fn convert_uint256_to_field_element< max_value = max_value.saturating_sub(&U1024::from_word(1u64)); let (overflows, rem) = max_value.div_rem(¶ms.modulus_u1024); + assert!(overflows.lt(&U1024::from_word(1u64 << 32))); let mut max_moduluses = overflows.as_words()[0] as u32; if rem.is_zero().unwrap_u8() != 1 { max_moduluses += 1; @@ -316,6 +325,263 @@ fn to_wnaf>( naf } +fn width_4_windowed_multiplication>( + cs: &mut CS, + mut point: SWProjectivePoint>, + mut scalar: Secp256ScalarNNField, + base_field_params: &Arc, + scalar_field_params: &Arc, +) -> SWProjectivePoint> { + scalar.enforce_reduced(cs); + + let beta = Secp256Fq::from_str(BETA).unwrap(); + let mut beta = Secp256BaseNNField::allocated_constant(cs, beta, &base_field_params); + + let bigint_from_hex_str = |cs: &mut CS, s: &str| -> UInt512 { + let v = U256::from_str_radix(s, 16).unwrap(); + UInt512::allocated_constant(cs, (v, U256::zero())) + }; + + let modulus_minus_one_div_two = bigint_from_hex_str(cs, MODULUS_MINUS_ONE_DIV_TWO); + + let u256_from_hex_str = |cs: &mut CS, s: &str| -> UInt256 { + let v = U256::from_str_radix(s, 16).unwrap(); + UInt256::allocated_constant(cs, v) + }; + + let a1 = u256_from_hex_str(cs, A1); + let b1 = u256_from_hex_str(cs, B1); + let a2 = u256_from_hex_str(cs, A2); + let b2 = a1.clone(); + + let boolean_false = Boolean::allocated_constant(cs, false); + + // Scalar decomposition + let (k1_was_negated, k1, k2_was_negated, k2) = { + let k = convert_field_element_to_uint256(cs, scalar.clone()); + + // We take 8 non-zero limbs for the scalar (since it could be of any size), and 4 for B2 + // (since it fits in 128 bits). + let b2_times_k = k.widening_mul(cs, &b2, 8, 4); + // can not overflow u512 + let (b2_times_k, of) = b2_times_k.overflowing_add(cs, &modulus_minus_one_div_two); + Boolean::enforce_equal(cs, &of, &boolean_false); + let c1 = b2_times_k.to_high(); + + // We take 8 non-zero limbs for the scalar (since it could be of any size), and 4 for B1 + // (since it fits in 128 bits). + let b1_times_k = k.widening_mul(cs, &b1, 8, 4); + // can not overflow u512 + let (b1_times_k, of) = b1_times_k.overflowing_add(cs, &modulus_minus_one_div_two); + Boolean::enforce_equal(cs, &of, &boolean_false); + let c2 = b1_times_k.to_high(); + + let mut a1 = convert_uint256_to_field_element(cs, &a1, &scalar_field_params); + let mut b1 = convert_uint256_to_field_element(cs, &b1, &scalar_field_params); + let mut a2 = convert_uint256_to_field_element(cs, &a2, &scalar_field_params); + let mut b2 = a1.clone(); + let mut c1 = convert_uint256_to_field_element(cs, &c1, &scalar_field_params); + let mut c2 = convert_uint256_to_field_element(cs, &c2, &scalar_field_params); + + let mut c1_times_a1 = c1.mul(cs, &mut a1); + let mut c2_times_a2 = c2.mul(cs, &mut a2); + let mut k1 = scalar.sub(cs, &mut c1_times_a1).sub(cs, &mut c2_times_a2); + k1.normalize(cs); + let mut c2_times_b2 = c2.mul(cs, &mut b2); + let mut k2 = c1.mul(cs, &mut b1).sub(cs, &mut c2_times_b2); + k2.normalize(cs); + + let k1_u256 = convert_field_element_to_uint256(cs, k1.clone()); + let k2_u256 = convert_field_element_to_uint256(cs, k2.clone()); + let max_k1_or_k2 = UInt256::allocated_constant(cs, MAX_DECOMPOSITION_VALUE); + // we will need k1 and k2 to be < 2^128, so we can compare via subtraction + let (_res, k1_out_of_range) = max_k1_or_k2.overflowing_sub(cs, &k1_u256); + let k1_negated = k1.negated(cs); + // dbg!(k1.witness_hook(cs)()); + // dbg!(k1_negated.witness_hook(cs)()); + let k1 = as NonNativeField>::conditionally_select( + cs, + k1_out_of_range, + &k1_negated, + &k1, + ); + let (_res, k2_out_of_range) = max_k1_or_k2.overflowing_sub(cs, &k2_u256); + let k2_negated = k2.negated(cs); + // dbg!(k2.witness_hook(cs)()); + // dbg!(k2_negated.witness_hook(cs)()); + let k2 = as NonNativeField>::conditionally_select( + cs, + k2_out_of_range, + &k2_negated, + &k2, + ); + + (k1_out_of_range, k1, k2_out_of_range, k2) + }; + + // create precomputed table of size 1<<4 - 1 + // there is no 0 * P in the table, we will handle it below + let mut table = Vec::with_capacity(PRECOMPUTATION_TABLE_SIZE); + let mut tmp = point.clone(); + let (mut p_affine, _) = point.convert_to_affine_or_default(cs, Secp256Affine::one()); + table.push(p_affine.clone()); + for _ in 1..PRECOMPUTATION_TABLE_SIZE { + // 2P, 3P, ... + tmp = tmp.add_mixed(cs, &mut p_affine); + let (affine, _) = tmp.convert_to_affine_or_default(cs, Secp256Affine::one()); + table.push(affine); + } + assert_eq!(table.len(), PRECOMPUTATION_TABLE_SIZE); + + let mut endomorphisms_table = table.clone(); + for (x, _) in endomorphisms_table.iter_mut() { + *x = x.mul(cs, &mut beta); + } + + // we also know that we will multiply k1 by points, and k2 by their endomorphisms, and if they were + // negated above to fit into range, we negate bases here + for (_, y) in table.iter_mut() { + let negated = y.negated(cs); + *y = Selectable::conditionally_select(cs, k1_was_negated, &negated, &*y); + } + + for (_, y) in endomorphisms_table.iter_mut() { + let negated = y.negated(cs); + *y = Selectable::conditionally_select(cs, k2_was_negated, &negated, &*y); + } + + // now decompose every scalar we are interested in + let k1_msb_decomposition = to_width_4_window_form(cs, k1); + let k2_msb_decomposition = to_width_4_window_form(cs, k2); + + let mut comparison_constants = Vec::with_capacity(PRECOMPUTATION_TABLE_SIZE); + for i in 1..=PRECOMPUTATION_TABLE_SIZE { + let constant = Num::allocated_constant(cs, F::from_u64_unchecked(i as u64)); + comparison_constants.push(constant); + } + + // now we do amortized double and add + let mut acc = SWProjectivePoint::zero(cs, base_field_params); + assert_eq!( + k1_msb_decomposition.len(), + NUM_MULTIPLICATION_STEPS_FOR_WIDTH_4 + ); + assert_eq!( + k2_msb_decomposition.len(), + NUM_MULTIPLICATION_STEPS_FOR_WIDTH_4 + ); + + for (idx, (k1_window_idx, k2_window_idx)) in k1_msb_decomposition + .into_iter() + .zip(k2_msb_decomposition.into_iter()) + .enumerate() + { + let ignore_k1_part = k1_window_idx.is_zero(cs); + let ignore_k2_part = k2_window_idx.is_zero(cs); + + // dbg!(k1_window_idx.witness_hook(cs)()); + // dbg!(k2_window_idx.witness_hook(cs)()); + // dbg!(ignore_k1_part.witness_hook(cs)()); + // dbg!(ignore_k2_part.witness_hook(cs)()); + + let (mut selected_k1_part_x, mut selected_k1_part_y) = table[0].clone(); + let (mut selected_k2_part_x, mut selected_k2_part_y) = endomorphisms_table[0].clone(); + for i in 1..PRECOMPUTATION_TABLE_SIZE { + let should_select_k1 = Num::equals(cs, &comparison_constants[i], &k1_window_idx); + let should_select_k2 = Num::equals(cs, &comparison_constants[i], &k2_window_idx); + selected_k1_part_x = Selectable::conditionally_select( + cs, + should_select_k1, + &table[i].0, + &selected_k1_part_x, + ); + selected_k1_part_y = Selectable::conditionally_select( + cs, + should_select_k1, + &table[i].1, + &selected_k1_part_y, + ); + selected_k2_part_x = Selectable::conditionally_select( + cs, + should_select_k2, + &endomorphisms_table[i].0, + &selected_k2_part_x, + ); + selected_k2_part_y = Selectable::conditionally_select( + cs, + should_select_k2, + &endomorphisms_table[i].1, + &selected_k2_part_y, + ); + } + + // dbg!(selected_k1_part_x.witness_hook(cs)()); + // dbg!(selected_k1_part_y.witness_hook(cs)()); + + let tmp_acc = acc.add_mixed(cs, &mut (selected_k1_part_x, selected_k1_part_y)); + acc = Selectable::conditionally_select(cs, ignore_k1_part, &acc, &tmp_acc); + let tmp_acc = acc.add_mixed(cs, &mut (selected_k2_part_x, selected_k2_part_y)); + acc = Selectable::conditionally_select(cs, ignore_k2_part, &acc, &tmp_acc); + + // let ((x, y), _) = acc.convert_to_affine_or_default(cs, Secp256Affine::zero()); + // dbg!(x.witness_hook(cs)()); + // dbg!(y.witness_hook(cs)()); + + if idx != NUM_MULTIPLICATION_STEPS_FOR_WIDTH_4 - 1 { + for _ in 0..WINDOW_WIDTH { + acc = acc.double(cs); + } + } + } + + acc +} + +fn to_width_4_window_form>( + cs: &mut CS, + mut limited_width_scalar: Secp256ScalarNNField, +) -> Vec> { + limited_width_scalar.enforce_reduced(cs); + // we know that width is 128 bits, so just do BE decomposition and put into resulting array + let zero_num = Num::zero(cs); + for word in limited_width_scalar.limbs[9..].iter() { + let word = Num::from_variable(*word); + Num::enforce_equal(cs, &word, &zero_num); + } + + let byte_split_id = cs + .get_table_id_for_marker::>() + .expect("table should exist"); + let mut result = Vec::with_capacity(32); + // special case + { + let highest_word = limited_width_scalar.limbs[8]; + let word = unsafe { UInt16::from_variable_unchecked(highest_word) }; + let [high, low] = word.to_be_bytes(cs); + Num::enforce_equal(cs, &high.into_num(), &zero_num); + let [l, h] = cs.perform_lookup::<1, 2>(byte_split_id, &[low.get_variable()]); + Num::enforce_equal(cs, &Num::from_variable(h), &zero_num); + let l = Num::from_variable(l); + result.push(l); + } + + for word in limited_width_scalar.limbs[..8].iter().rev() { + let word = unsafe { UInt16::from_variable_unchecked(*word) }; + let [high, low] = word.to_be_bytes(cs); + for t in [high, low].into_iter() { + let [l, h] = cs.perform_lookup::<1, 2>(byte_split_id, &[t.get_variable()]); + let h = Num::from_variable(h); + let l = Num::from_variable(l); + result.push(h); + result.push(l); + } + } + assert_eq!(result.len(), NUM_MULTIPLICATION_STEPS_FOR_WIDTH_4); + + result +} + +#[deprecated] fn wnaf_scalar_mul>( cs: &mut CS, mut point: SWProjectivePoint>, @@ -325,8 +591,7 @@ fn wnaf_scalar_mul>( ) -> SWProjectivePoint> { scalar.enforce_reduced(cs); - let pow_2_128 = U256::from_dec_str(TWO_POW_128).unwrap(); - let pow_2_128 = UInt512::allocated_constant(cs, (pow_2_128, U256::zero())); + let pow_2_128 = UInt512::allocated_constant(cs, (U256([0, 0, 1, 0]), U256::zero())); let beta = Secp256Fq::from_str(BETA).unwrap(); let mut beta = Secp256BaseNNField::allocated_constant(cs, beta, &base_field_params); @@ -338,31 +603,37 @@ fn wnaf_scalar_mul>( let modulus_minus_one_div_two = bigint_from_hex_str(cs, MODULUS_MINUS_ONE_DIV_TWO); - // Scalar decomposition - let (k1_neg, k1, k2_neg, k2) = { - let u256_from_hex_str = |cs: &mut CS, s: &str| -> UInt256 { - let v = U256::from_str_radix(s, 16).unwrap(); - UInt256::allocated_constant(cs, v) - }; + let u256_from_hex_str = |cs: &mut CS, s: &str| -> UInt256 { + let v = U256::from_str_radix(s, 16).unwrap(); + UInt256::allocated_constant(cs, v) + }; - let a1 = u256_from_hex_str(cs, A1); - let b1 = u256_from_hex_str(cs, B1); - let a2 = u256_from_hex_str(cs, A2); - let b2 = a1.clone(); + let a1 = u256_from_hex_str(cs, A1); + let b1 = u256_from_hex_str(cs, B1); + let a2 = u256_from_hex_str(cs, A2); + let b2 = a1.clone(); + + let boolean_false = Boolean::allocated_constant(cs, false); + // Scalar decomposition + let (k1_neg, k1, k2_neg, k2) = { let k = convert_field_element_to_uint256(cs, scalar.clone()); // We take 8 non-zero limbs for the scalar (since it could be of any size), and 4 for B2 // (since it fits in 128 bits). let b2_times_k = k.widening_mul(cs, &b2, 8, 4); - let b2_times_k = b2_times_k.overflowing_add(cs, &modulus_minus_one_div_two); - let c1 = b2_times_k.0.to_high(); + // can not overflow u512 + let (b2_times_k, of) = b2_times_k.overflowing_add(cs, &modulus_minus_one_div_two); + Boolean::enforce_equal(cs, &of, &boolean_false); + let c1 = b2_times_k.to_high(); // We take 8 non-zero limbs for the scalar (since it could be of any size), and 4 for B1 // (since it fits in 128 bits). let b1_times_k = k.widening_mul(cs, &b1, 8, 4); - let b1_times_k = b1_times_k.overflowing_add(cs, &modulus_minus_one_div_two); - let c2 = b1_times_k.0.to_high(); + // can not overflow u512 + let (b1_times_k, of) = b1_times_k.overflowing_add(cs, &modulus_minus_one_div_two); + Boolean::enforce_equal(cs, &of, &boolean_false); + let c2 = b1_times_k.to_high(); let mut a1 = convert_uint256_to_field_element(cs, &a1, &scalar_field_params); let mut b1 = convert_uint256_to_field_element(cs, &b1, &scalar_field_params); @@ -404,10 +675,10 @@ fn wnaf_scalar_mul>( // WNAF // The scalar multiplication window size. - const GLV_WINDOW_SIZE: usize = 2; + use super::decomp_table::WNAF_WINDOW_SIZE; - // The GLV table length. - const L: usize = 1 << (GLV_WINDOW_SIZE - 1); + // The WNAF precomputation table length + const L: usize = 1 << (WNAF_WINDOW_SIZE - 1); let mut t1 = Vec::with_capacity(L); // We use `convert_to_affine_or_default`, but we don't need to worry about returning 1, since @@ -416,23 +687,26 @@ fn wnaf_scalar_mul>( .double(cs) .convert_to_affine_or_default(cs, Secp256Affine::one()); t1.push(point.clone()); + // we need 1P, 3P, 5P, ... for i in 1..L { let next = t1[i - 1].add_mixed(cs, &mut double); t1.push(next); } + // (x, y) let t1 = t1 .iter_mut() .map(|el| el.convert_to_affine_or_default(cs, Secp256Affine::one()).0) .collect::>(); + // (x*beta, y) let t2 = t1 .clone() .into_iter() .map(|mut el| (el.0.mul(cs, &mut beta), el.1)) .collect::>(); - let overflow_checker = UInt8::allocated_constant(cs, 2u8.pow(7)); + let overflow_checker = UInt8::allocated_constant(cs, 1 << 7); let decomp_id = cs .get_table_id_for_marker::() .expect("table should exist"); @@ -454,7 +728,22 @@ fn wnaf_scalar_mul>( cs.perform_lookup::<1, 2>(naf_abs_div2_table_id, &[naf.get_variable()])[0], ) }; - let coords = &table[index.witness_hook(cs)().unwrap() as usize]; + // We assume that only one of the values in the table will be selected (since the index + // <= table.len()) and so we can iteratively conditionally select over all elements. + let mut sanity_checks = vec![index.is_zero(cs)]; + let mut coords = table[0].clone(); + for (i, v) in table.iter().enumerate().skip(1) { + assert!((i as u8) < u8::MAX); + let const_idx: UInt8 = UInt8::allocated_constant(cs, i as u8); + let correct_idx = UInt8::equals(cs, &index, &const_idx); + sanity_checks.push(correct_idx); + coords.0 = Selectable::conditionally_select(cs, correct_idx, &v.0, &coords.0); + coords.1 = Selectable::conditionally_select(cs, correct_idx, &v.1, &coords.1); + } + let matched = Boolean::multi_or(cs, &sanity_checks); + let boolean_true = Boolean::allocated_constant(cs, true); + Boolean::enforce_equal(cs, &matched, &boolean_true); + let mut p_1 = SWProjectivePoint::>::from_xy_unchecked( cs, @@ -503,7 +792,7 @@ fn fixed_base_mul, F: SmallField>( SWProjectivePoint::>::zero(cs, base_field_params); let mut full_table_ids = vec![]; seq_macro::seq!(C in 0..32 { - let ids = vec![ + let ids = [ cs.get_table_id_for_marker::>() .expect("table must exist"), cs.get_table_id_for_marker::>() @@ -529,11 +818,6 @@ fn fixed_base_mul, F: SmallField>( .zip(bytes) .rev() .for_each(|(ids, byte)| { - // let chunks = ids - // .iter() - // .map(|id| cs.perform_lookup::<1, 2>(*id, &[byte.get_variable()])) - // .collect::>(); - let (x, y): (Vec, Vec) = ids .iter() .flat_map(|id| { @@ -575,7 +859,9 @@ fn fixed_base_mul, F: SmallField>( params: base_field_params.clone(), _marker: std::marker::PhantomData, }; - acc = acc.add_mixed(cs, &mut (x, y)); + let new_acc = acc.add_mixed(cs, &mut (x, y)); + let should_not_update = byte.is_zero(cs); + acc = Selectable::conditionally_select(cs, should_not_update, &acc, &new_acc); }); acc = Selectable::conditionally_select(cs, is_zero, &zero_point, &acc); acc @@ -638,6 +924,13 @@ fn ecrecover_precompile_inner_routine< let [y_is_odd, x_overflow, ..] = Num::::from_variable(recid.get_variable()).spread_into_bits::<_, 8>(cs); + // check convention s < N/2 + let s_upper_bound = + UInt256::allocated_constant(cs, U256::from_str_radix(HALF_SUBGROUP_SIZE, 16).unwrap()); + let (_, uf) = s.overflowing_sub(cs, &s_upper_bound); + let s_too_large = uf.negated(cs); + exception_flags.push(s_too_large); + let (r_plus_n, of) = r.overflowing_add(cs, &secp_n_u256); let mut x_as_u256 = UInt256::conditionally_select(cs, x_overflow, &r_plus_n, &r); let error = Boolean::multi_and(cs, &[x_overflow, of]); @@ -720,6 +1013,11 @@ fn ecrecover_precompile_inner_routine< may_be_recovered_y.normalize(cs); let may_be_recovered_y_negated = may_be_recovered_y.negated(cs); + if crate::config::CIRCUIT_VERSOBE { + dbg!(may_be_recovered_y.witness_hook(cs)()); + dbg!(may_be_recovered_y_negated.witness_hook(cs)()); + } + let [lowest_bit, ..] = Num::::from_variable(may_be_recovered_y.limbs[0]).spread_into_bits::<_, 16>(cs); @@ -753,15 +1051,24 @@ fn ecrecover_precompile_inner_routine< let mut message_hash_by_r_inv = message_hash_fe.mul(cs, &mut r_fe_inversed); s_by_r_inv.normalize(cs); - message_hash_by_r_inv.normalize(cs); + let mut message_hash_by_r_inv_negated = message_hash_by_r_inv.negated(cs); + message_hash_by_r_inv_negated.normalize(cs); // now we are going to compute the public key Q = (x, y) determined by the formula: // Q = (s * X - hash * G) / r which is equivalent to r * Q = s * X - hash * G + if crate::config::CIRCUIT_VERSOBE { + dbg!(x.witness_hook(cs)()); + dbg!(y.witness_hook(cs)()); + dbg!(s_by_r_inv.witness_hook(cs)()); + dbg!(message_hash_by_r_inv_negated.witness_hook(cs)()); + } + let recovered_point = SWProjectivePoint::>::from_xy_unchecked(cs, x, y); + // now we do multiplication - let mut s_times_x = wnaf_scalar_mul( + let mut s_times_x = width_4_windowed_multiplication( cs, recovered_point.clone(), s_by_r_inv.clone(), @@ -769,7 +1076,16 @@ fn ecrecover_precompile_inner_routine< &scalar_field_params, ); - let mut hash_times_g = fixed_base_mul(cs, message_hash_by_r_inv, &base_field_params); + // let mut s_times_x = wnaf_scalar_mul( + // cs, + // recovered_point.clone(), + // s_by_r_inv.clone(), + // &base_field_params, + // &scalar_field_params, + // ); + + let mut hash_times_g = fixed_base_mul(cs, message_hash_by_r_inv_negated, &base_field_params); + // let mut hash_times_g = fixed_base_mul(cs, message_hash_by_r_inv, &base_field_params); let (mut q_acc, is_infinity) = hash_times_g.convert_to_affine_or_default(cs, Secp256Affine::one()); @@ -782,6 +1098,11 @@ fn ecrecover_precompile_inner_routine< let zero_u8 = UInt8::zero(cs); + if crate::config::CIRCUIT_VERSOBE { + dbg!(q_x.witness_hook(cs)()); + dbg!(q_y.witness_hook(cs)()); + } + let mut bytes_to_hash = [zero_u8; 64]; let it = q_x.limbs[..16] .iter() @@ -966,6 +1287,15 @@ where let [message_hash_as_u256, v_as_u256, r_as_u256, s_as_u256] = read_values; let rec_id = v_as_u256.inner[0].to_le_bytes(cs)[0]; + if crate::config::CIRCUIT_VERSOBE { + if should_process.witness_hook(cs)().unwrap() == true { + dbg!(rec_id.witness_hook(cs)()); + dbg!(r_as_u256.witness_hook(cs)()); + dbg!(s_as_u256.witness_hook(cs)()); + dbg!(message_hash_as_u256.witness_hook(cs)()); + } + } + let (success, written_value) = ecrecover_precompile_inner_routine::<_, _, ALLOW_ZERO_MESSAGE>( cs, &rec_id, @@ -983,6 +1313,13 @@ where let mut success_as_u256 = zero_u256; success_as_u256.inner[0] = success_as_u32; + if crate::config::CIRCUIT_VERSOBE { + if should_process.witness_hook(cs)().unwrap() == true { + dbg!(success_as_u256.witness_hook(cs)()); + dbg!(written_value.witness_hook(cs)()); + } + } + let success_query = MemoryQuery { timestamp: timestamp_to_use_for_write, memory_page: precompile_call_params.output_page, @@ -1048,7 +1385,10 @@ where #[cfg(test)] mod test { + use std::alloc::Global; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::non_native_field::implementations::implementation_u16::FFProxyValue; use boojum::gadgets::traits::allocatable::CSAllocatable; use boojum::pairing::ff::{Field, PrimeField, SqrtField}; use boojum::worker::Worker; @@ -1232,15 +1572,12 @@ mod test { builder } - let builder_impl = CsReferenceImplementationBuilder::::new( - geometry, - max_variables, - max_trace_len, - ); + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, max_trace_len); let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(max_variables); // add tables let table = create_xor8_table(); @@ -1249,11 +1586,11 @@ mod test { let table = create_and8_table(); owned_cs.add_lookup_table::(table); - let table = create_naf_abs_div2_table(); - owned_cs.add_lookup_table::(table); + // let table = create_naf_abs_div2_table(); + // owned_cs.add_lookup_table::(table); - let table = create_wnaf_decomp_table(); - owned_cs.add_lookup_table::(table); + // let table = create_wnaf_decomp_table(); + // owned_cs.add_lookup_table::(table); seq_macro::seq!(C in 0..32 { let table = create_fixed_base_mul_table::(); @@ -1287,6 +1624,88 @@ mod test { } #[test] + fn test_fixed_base_mul() { + let mut owned_cs = create_cs(1 << 21); + let cs = &mut owned_cs; + let scalar_params = Arc::new(secp256k1_scalar_field_params()); + let base_params = Arc::new(secp256k1_base_field_params()); + + let mut seed = Secp256Fr::multiplicative_generator(); + seed = seed.pow([1234]); + + for _i in 0..16 { + let scalar = Secp256ScalarNNField::allocate_checked(cs, seed, &scalar_params); + let mut result = fixed_base_mul(cs, scalar, &base_params); + let ((result_x, result_y), _) = + result.convert_to_affine_or_default(cs, Secp256Affine::one()); + + let expected = Secp256Affine::one().mul(seed).into_affine(); + dbg!(_i); + dbg!(seed); + assert_eq!( + result_x.witness_hook(cs)().unwrap().get(), + *expected.as_xy().0 + ); + assert_eq!( + result_y.witness_hook(cs)().unwrap().get(), + *expected.as_xy().1 + ); + + seed.square(); + } + } + + #[test] + fn test_variable_base_mul() { + let mut owned_cs = create_cs(1 << 21); + let cs = &mut owned_cs; + let scalar_params = Arc::new(secp256k1_scalar_field_params()); + let base_params = Arc::new(secp256k1_base_field_params()); + + let mut seed = Secp256Fr::multiplicative_generator(); + seed = seed.pow([1234]); + + let mut seed_2 = Secp256Fr::multiplicative_generator(); + seed_2 = seed_2.pow([987654]); + + for _i in 0..16 { + dbg!(_i); + dbg!(seed); + + let base = Secp256Affine::one().mul(seed_2).into_affine(); + + // let mut seed = Secp256Fr::from_str("1234567890").unwrap(); + // dbg!(base); + // dbg!(base.mul(seed).into_affine()); + + let scalar = Secp256ScalarNNField::allocate_checked(cs, seed, &scalar_params); + let x = Secp256BaseNNField::allocate_checked(cs, *base.as_xy().0, &base_params); + let y = Secp256BaseNNField::allocate_checked(cs, *base.as_xy().1, &base_params); + let point = SWProjectivePoint::from_xy_unchecked(cs, x, y); + + let mut result = + width_4_windowed_multiplication(cs, point, scalar, &base_params, &scalar_params); + // let mut result = wnaf_scalar_mul(cs, point, scalar, &base_params, &scalar_params); + let ((result_x, result_y), _) = + result.convert_to_affine_or_default(cs, Secp256Affine::one()); + + let expected = base.mul(seed).into_affine(); + assert_eq!( + result_x.witness_hook(cs)().unwrap().get(), + *expected.as_xy().0 + ); + assert_eq!( + result_y.witness_hook(cs)().unwrap().get(), + *expected.as_xy().1 + ); + + seed.square(); + seed_2.square(); + } + } + + #[test] + #[ignore = "test vectors hits malleable S"] fn test_signature_for_address_verification() { let mut owned_cs = create_cs(1 << 20); let cs = &mut owned_cs; @@ -1297,6 +1716,7 @@ mod test { .unwrap(); let eth_address = hex::decode("12890d2cce102216644c59dae5baed380d84830c").unwrap(); let (r, s, _pk, digest) = simulate_signature_for_sk(sk); + dbg!(_pk); let scalar_params = secp256k1_scalar_field_params(); let base_params = secp256k1_base_field_params(); @@ -1330,7 +1750,159 @@ mod test { ); for _ in 0..5 { - let (no_error, digest) = ecrecover_precompile_inner_routine::<_, _, false>( + let (no_error, digest) = ecrecover_precompile_inner_routine::<_, _, true>( + cs, + &rec_id, + &r, + &s, + &digest, + valid_x_in_external_field.clone(), + valid_y_in_external_field.clone(), + valid_t_in_external_field.clone(), + &base_params, + &scalar_params, + ); + + assert!(no_error.witness_hook(&*cs)().unwrap() == true); + let recovered_address = digest.to_be_bytes(cs); + let recovered_address = recovered_address.witness_hook(cs)().unwrap(); + assert_eq!(&recovered_address[12..], ð_address[..]); + } + + dbg!(cs.next_available_row()); + + cs.pad_and_shrink(); + + let mut cs = owned_cs.into_assembly::(); + cs.print_gate_stats(); + let worker = Worker::new(); + assert!(cs.check_if_satisfied(&worker)); + } + + #[test] + fn test_signature_from_reference_vector() { + let mut owned_cs = create_cs(1 << 20); + let cs = &mut owned_cs; + + let digest = + hex::decode("38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e") + .unwrap(); + let v = 0; + let r = hex::decode("38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e") + .unwrap(); + let s = hex::decode("789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02") + .unwrap(); + let eth_address = hex::decode("ceaccac640adf55b2028469bd36ba501f28b699d").unwrap(); + + let scalar_params = secp256k1_scalar_field_params(); + let base_params = secp256k1_base_field_params(); + + let digest_u256 = U256::from_big_endian(&digest); + let r_u256 = U256::from_big_endian(&r); + let s_u256 = U256::from_big_endian(&s); + + let rec_id = UInt8::allocate_checked(cs, v); + let r = UInt256::allocate(cs, r_u256); + let s = UInt256::allocate(cs, s_u256); + let digest = UInt256::allocate(cs, digest_u256); + + let scalar_params = Arc::new(scalar_params); + let base_params = Arc::new(base_params); + + let valid_x_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str("9").unwrap(), + &base_params, + ); + let valid_t_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str("16").unwrap(), + &base_params, + ); + let valid_y_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str("4").unwrap(), + &base_params, + ); + + for _ in 0..1 { + let (no_error, digest) = ecrecover_precompile_inner_routine::<_, _, true>( + cs, + &rec_id, + &r, + &s, + &digest, + valid_x_in_external_field.clone(), + valid_y_in_external_field.clone(), + valid_t_in_external_field.clone(), + &base_params, + &scalar_params, + ); + + assert!(no_error.witness_hook(&*cs)().unwrap() == true); + let recovered_address = digest.to_be_bytes(cs); + let recovered_address = recovered_address.witness_hook(cs)().unwrap(); + assert_eq!(&recovered_address[12..], ð_address[..]); + } + + dbg!(cs.next_available_row()); + + cs.pad_and_shrink(); + + let mut cs = owned_cs.into_assembly::(); + cs.print_gate_stats(); + let worker = Worker::new(); + assert!(cs.check_if_satisfied(&worker)); + } + + #[test] + fn test_signature_from_reference_vector_2() { + let mut owned_cs = create_cs(1 << 20); + let cs = &mut owned_cs; + + let digest = + hex::decode("14431339128bd25f2c7f93baa611e367472048757f4ad67f6d71a5ca0da550f5") + .unwrap(); + let v = 1; + let r = hex::decode("51e4dbbbcebade695a3f0fdf10beb8b5f83fda161e1a3105a14c41168bf3dce0") + .unwrap(); + let s = hex::decode("46eabf35680328e26ef4579caf8aeb2cf9ece05dbf67a4f3d1f28c7b1d0e3546") + .unwrap(); + let eth_address = hex::decode("7f8b3b04bf34618f4a1723fba96b5db211279a2b").unwrap(); + + let scalar_params = secp256k1_scalar_field_params(); + let base_params = secp256k1_base_field_params(); + + let digest_u256 = U256::from_big_endian(&digest); + let r_u256 = U256::from_big_endian(&r); + let s_u256 = U256::from_big_endian(&s); + + let rec_id = UInt8::allocate_checked(cs, v); + let r = UInt256::allocate(cs, r_u256); + let s = UInt256::allocate(cs, s_u256); + let digest = UInt256::allocate(cs, digest_u256); + + let scalar_params = Arc::new(scalar_params); + let base_params = Arc::new(base_params); + + let valid_x_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str("9").unwrap(), + &base_params, + ); + let valid_t_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str("16").unwrap(), + &base_params, + ); + let valid_y_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str("4").unwrap(), + &base_params, + ); + + for _ in 0..1 { + let (no_error, digest) = ecrecover_precompile_inner_routine::<_, _, true>( cs, &rec_id, &r, @@ -1353,7 +1925,7 @@ mod test { cs.pad_and_shrink(); - let mut cs = owned_cs.into_assembly(); + let mut cs = owned_cs.into_assembly::(); cs.print_gate_stats(); let worker = Worker::new(); assert!(cs.check_if_satisfied(&worker)); @@ -1543,7 +2115,7 @@ mod test { cs.pad_and_shrink(); - let mut cs = owned_cs.into_assembly(); + let mut cs = owned_cs.into_assembly::(); cs.print_gate_stats(); let worker = Worker::new(); assert!(cs.check_if_satisfied(&worker)); diff --git a/crates/zkevm_circuits/src/ecrecover/secp256k1/fixed_base_mul_table.rs b/crates/zkevm_circuits/src/ecrecover/secp256k1/fixed_base_mul_table.rs index 8aec309..87aa76e 100644 --- a/crates/zkevm_circuits/src/ecrecover/secp256k1/fixed_base_mul_table.rs +++ b/crates/zkevm_circuits/src/ecrecover/secp256k1/fixed_base_mul_table.rs @@ -9,47 +9,50 @@ const TABLE_NAME: &'static str = "FIXEDBASEMUL table"; #[derive(Derivative)] #[derivative(Clone, Copy, Debug, PartialEq, Eq)] -pub struct FixedBaseMulTable; +pub struct FixedBaseMulTable; // Allows for a radix scalar mul by storing all potential exponentiations // of the generator with 0..255 -pub fn create_fixed_base_mul_table( -) -> LookupTable { - assert!(INDEX < 8); - assert!(B < 32); - let mut all_keys = Vec::with_capacity(1 << 8); - for a in 0..=u8::MAX { - let key = smallvec::smallvec![F::from_u64_unchecked(a as u64)]; - all_keys.push(key); +pub fn create_fixed_base_mul_table< + F: SmallField, + const U32_WORD_INDEX: usize, + const BYTE_OFFSET: usize, +>() -> LookupTable { + assert!(U32_WORD_INDEX < 8); + assert!(BYTE_OFFSET < 32); + let mut content = Vec::with_capacity(1 << 8); + // point of infinity is encoded as (0,0), and we handle it via select in the multiplication routine + content.push([F::ZERO, F::ZERO, F::ZERO]); + let mut base_power = Fr::one(); + for _ in 0..(BYTE_OFFSET * 8) { + base_power.double(); } - let mut generator = Secp256Affine::one(); - generator.negate(); - let r = Fr::from_str("256").unwrap(); - LookupTable::new_from_keys_and_generation_function( - &all_keys, - TABLE_NAME.to_string(), - 1, - |keys| { - let a = keys[0].as_u64_reduced(); - let b = r.pow([B]); - let mut exp = Fr::from_str(&a.to_string()).unwrap(); - exp.mul_assign(&b); - let result = generator.mul(exp); - let result = result.into_affine(); - let is_even = INDEX % 2 == 0; - let res = [result.x, result.y] - .iter() - .map(|c| { - let index = INDEX / 2; - let segment = c.into_repr().0[index]; - if is_even { - F::from_u64_unchecked(segment & (u32::MAX as u64)) - } else { - F::from_u64_unchecked(segment >> 32) - } - }) - .collect::>(); - smallvec::smallvec![res[0], res[1]] - }, - ) + let base = Secp256Affine::one(); + let base = base.mul(base_power); + let mut current = base; + let base = base.into_affine(); + let repr_word_index = U32_WORD_INDEX / 2; + let take_low = U32_WORD_INDEX % 2 == 0; + for a in 1..=u8::MAX { + let current_affine = current.into_affine(); + let (x, y) = current_affine.as_xy(); + let x_repr_word = x.into_repr().as_ref()[repr_word_index]; + let y_repr_word = y.into_repr().as_ref()[repr_word_index]; + if take_low { + content.push([ + F::from_u64_unchecked(a as u64), + F::from_u64_unchecked((x_repr_word as u32) as u64), + F::from_u64_unchecked((y_repr_word as u32) as u64), + ]); + } else { + content.push([ + F::from_u64_unchecked(a as u64), + F::from_u64_unchecked(x_repr_word >> 32), + F::from_u64_unchecked(y_repr_word >> 32), + ]); + } + current.add_assign_mixed(&base); + } + assert_eq!(content.len(), 256); + LookupTable::new_from_content(content, TABLE_NAME.to_string(), 1) } diff --git a/crates/zkevm_circuits/src/eip_4844/input.rs b/crates/zkevm_circuits/src/eip_4844/input.rs new file mode 100644 index 0000000..9b202fc --- /dev/null +++ b/crates/zkevm_circuits/src/eip_4844/input.rs @@ -0,0 +1,66 @@ +use std::collections::VecDeque; + +use super::*; +use crate::base_structures::vm_state::*; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::keccak256; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::u256::recompose_u256_as_u32x8; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{ + allocatable::*, + encodable::{CircuitEncodable, CircuitEncodableExt, CircuitVarLengthEncodable}, + selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; + +pub const BLOB_CHUNK_SIZE: usize = 31; +pub const ELEMENTS_PER_4844_BLOCK: usize = 4096; + +#[derive(Derivative, CSAllocatable, CSSelectable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct BlobChunk { + pub inner: [UInt8; BLOB_CHUNK_SIZE], +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct EIP4844OutputData { + pub linear_hash: [UInt8; keccak256::KECCAK256_DIGEST_SIZE], + pub output_hash: [UInt8; keccak256::KECCAK256_DIGEST_SIZE], +} + +impl CSPlaceholder for EIP4844OutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + linear_hash: [UInt8::::allocate_constant(cs, 0); keccak256::KECCAK256_DIGEST_SIZE], + output_hash: [UInt8::::allocate_constant(cs, 0); keccak256::KECCAK256_DIGEST_SIZE], + } + } +} + +pub type EIP4844InputOutput = + crate::fsm_input_output::ClosedFormInput>; + +pub type EIP4844InputOutputWitness = + crate::fsm_input_output::ClosedFormInputWitness>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct EIP4844CircuitInstanceWitness { + pub closed_form_input: EIP4844InputOutputWitness, + pub versioned_hash: [u8; 32], + pub linear_hash_output: [u8; 32], + pub data_chunks: VecDeque>, +} diff --git a/crates/zkevm_circuits/src/eip_4844/mod.rs b/crates/zkevm_circuits/src/eip_4844/mod.rs new file mode 100644 index 0000000..929f5cc --- /dev/null +++ b/crates/zkevm_circuits/src/eip_4844/mod.rs @@ -0,0 +1,772 @@ +use crate::base_structures::state_diff_record::StateDiffRecord; +use crate::demux_log_queue::StorageLogQueue; +use crate::ethereum_types::U256; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::keccak256_round_function::keccak256_absorb_and_run_permutation; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::config::*; +use boojum::crypto_bigint::{Zero, U1024}; +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::cs::traits::cs::{ConstraintSystem, DstBuffer}; +use boojum::cs::{Place, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::keccak256; +use boojum::gadgets::non_native_field::implementations::*; +use boojum::gadgets::num::Num; +use boojum::gadgets::queue::CircuitQueue; +use boojum::gadgets::queue::CircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt, CSPlaceholder}; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u16::UInt16; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::pairing::ff::{Field, PrimeField}; +use std::mem::MaybeUninit; +use std::sync::{Arc, RwLock}; + +use super::*; + +pub mod input; +use self::input::*; + +use boojum::pairing::bls12_381::fr::Fr as Bls12_381Fr; + +const NUM_WORDS_FR: usize = 17; +type Bls12_381ScalarNNFieldParams = NonNativeFieldOverU16Params; +type Bls12_381ScalarNNField = NonNativeFieldOverU16; + +// turns 128 bits into a Bls12 field element. +fn convert_truncated_keccak_digest_to_field_element>( + cs: &mut CS, + input: [UInt8; 16], + params: &Arc, +) -> Bls12_381ScalarNNField { + // compose the bytes into u16 words for the nonnative wrapper + let zero_var = cs.allocate_constant(F::ZERO); + let mut limbs = [zero_var; NUM_WORDS_FR]; + // since the value would be interpreted as big endian in the L1 we need to reverse our bytes to + // get the correct value + for (dst, src) in limbs.iter_mut().zip(input.array_chunks::<2>().rev()) { + let [c0, c1] = *src; + // for some reason there is no "from_be_bytes" + *dst = UInt16::from_le_bytes(cs, [c1, c0]).get_variable(); + } + + // Note: we do not need to check for overflows because the max value is 2^128 which is less + // than the field modulus. + NonNativeFieldOverU16 { + limbs: limbs, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses: 1 }, + form: RepresentationForm::Normalized, + params: params.clone(), + _marker: std::marker::PhantomData, + } +} + +// here we just interpret it as LE +fn convert_blob_chunk_to_field_element>( + cs: &mut CS, + input: [UInt8; BLOB_CHUNK_SIZE], + params: &Arc, +) -> Bls12_381ScalarNNField { + // compose the bytes into u16 words for the nonnative wrapper + let zero_var = cs.allocate_constant(F::ZERO); + let mut limbs = [zero_var; NUM_WORDS_FR]; + let input_chunks = input.array_chunks::<2>(); + let remainder = input_chunks.remainder(); + for (dst, src) in limbs.iter_mut().zip(input_chunks) { + *dst = UInt16::from_le_bytes(cs, *src).get_variable(); + } + + // since array_chunks drops any remaining elements that don't fit in the size requirement, + // we need to manually set the last byte in limbs + limbs[15] = remainder[0].get_variable(); + + // Note: we do not need to check for overflows because the max value is 2^248 which is less + // than the field modulus. + NonNativeFieldOverU16 { + limbs: limbs, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses: 1 }, + form: RepresentationForm::Normalized, + params: params.clone(), + _marker: std::marker::PhantomData, + } +} + +/// We interpret out pubdata as chunks of 31 bytes, that are coefficients of +/// some polynomial, starting from the highest one. It's different from 4844 blob data format, +/// and we provide additional functions to compute the corresponding 4844 blob data, and restore back +pub fn eip_4844_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: EIP4844CircuitInstanceWitness, + round_function: &R, + params: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + let limit = params; + + assert_eq!(limit, ELEMENTS_PER_4844_BLOCK); // max blob length eip4844 + + let EIP4844CircuitInstanceWitness { + closed_form_input, + versioned_hash, + linear_hash_output, + data_chunks, + } = witness; + + if ::WitnessConfig::EVALUATE_WITNESS { + assert_eq!(data_chunks.len(), ELEMENTS_PER_4844_BLOCK) + } + + let mut data_chunks = data_chunks; + let zero_u8 = UInt8::zero(cs); + + let versioned_hash = <[UInt8; 32]>::allocate(cs, versioned_hash); + let linear_hash_output = <[UInt8; 32]>::allocate(cs, linear_hash_output); + + let boolean_true = Boolean::allocated_constant(cs, true); + let mut structured_input = EIP4844InputOutput:: { + start_flag: boolean_true, + completion_flag: boolean_true, + observable_input: (), + observable_output: EIP4844OutputData { + linear_hash: [UInt8::::zero(cs); keccak256::KECCAK256_DIGEST_SIZE], + output_hash: [UInt8::::zero(cs); keccak256::KECCAK256_DIGEST_SIZE], + }, + hidden_fsm_input: (), + hidden_fsm_output: (), + }; + + // create a field element out of the hash of the input hash and the kzg commitment + let challenge_hash = boojum::gadgets::keccak256::keccak256( + cs, + linear_hash_output + .into_iter() + .chain(versioned_hash.into_iter()) + .collect::>>() + .as_slice(), + ); + + // truncate hash to 128 bits + // NOTE: it is safe to draw a random scalar at max 128 bits because of the schwartz zippel + // lemma + let mut truncated_hash = [zero_u8; 16]; + // take last 16 bytes to get max 2^128 + // in big endian scenario + truncated_hash.copy_from_slice(&challenge_hash[16..]); + let params = Arc::new(Bls12_381ScalarNNFieldParams::create()); + let mut evaluation_point = + convert_truncated_keccak_digest_to_field_element(cs, truncated_hash, ¶ms); + + let mut buffer = Vec::with_capacity(31 * 4096); + + let mut opening_value = + Bls12_381ScalarNNField::::allocated_constant(cs, Bls12_381Fr::zero(), ¶ms); + + // We always pad the pubdata to be 31*(2^12) bytes, no matter how many elements we fill. + // Therefore, we can run the circuit straightforwardly without needing to account for potential + // changes in the cycle number in which the padding happens. Hence, we run the loop + // to perform the horner's rule evaluation of the blob polynomial and then finalize the hash + // out of the loop with a single keccak256 call. + for cycle in 0..limit { + let el = data_chunks + .pop_front() + .unwrap_or(BlobChunk::placeholder_witness()); + let el = BlobChunk::::allocate(cs, el); + // polynomial evaluations via horner's rule + let mut fe = convert_blob_chunk_to_field_element(cs, el.inner, ¶ms); + // horner's rule is defined as evaluating a polynomial a_0 + a_1x + a_2x^2 + ... + a_nx^n + // in the form of a_0 + x(a_1 + x(a_2 + x(a_3 + ... + x(a_{n-1} + xa_n)))) + // since the blob is considered to be a polynomial in monomial form, we essentially + // 'work backwards' and start with the highest degree coefficients first. so we can + // add and multiply and at the last step we only add the coefficient. + opening_value = opening_value.add(cs, &mut fe); + if cycle != limit - 1 { + opening_value = opening_value.mul(cs, &mut evaluation_point); + } + + buffer.extend(el.inner); + } + + use boojum::gadgets::keccak256::keccak256; + let keccak256_hash = keccak256(cs, &buffer); + + // hash equality check + for (input_byte, hash_byte) in linear_hash_output.iter().zip(keccak256_hash) { + Num::enforce_equal(cs, &input_byte.into_num(), &hash_byte.into_num()); + } + + // now commit to versioned hash || opening point || openinig value + + // normalize and serialize opening value as BE + let mut opening_value_be_bytes = [zero_u8; 32]; + opening_value.normalize(cs); + + for (dst, src) in opening_value_be_bytes + .array_chunks_mut::<2>() + .zip(opening_value.limbs[..16].iter().rev()) + { + // field element is normalized, so all limbs are 16 bits + let be_bytes = unsafe { UInt16::from_variable_unchecked(*src).to_be_bytes(cs) }; + *dst = be_bytes; + } + + let output_hash = keccak256( + cs, + versioned_hash + .into_iter() + .chain(truncated_hash.into_iter()) + .chain(opening_value_be_bytes.into_iter()) + .collect::>>() + .as_slice(), + ); + + let mut observable_output = EIP4844OutputData::placeholder(cs); + observable_output.linear_hash = keccak256_hash; + observable_output.output_hash = output_hash; + structured_input.observable_output = observable_output; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + use crate::fsm_input_output::commit_variable_length_encodable_item; + use crate::fsm_input_output::ClosedFormInputCompactForm; + use boojum::cs::gates::PublicInputGate; + + let compact_form = + ClosedFormInputCompactForm::from_full_form(cs, &structured_input, round_function); + let input_commitment = commit_variable_length_encodable_item(cs, &compact_form, round_function); + for el in input_commitment.iter() { + let gate = PublicInputGate::new(el.get_variable()); + gate.add_to_cs(cs); + } + + input_commitment +} + +fn omega() -> Bls12_381Fr { + let mut omega = Bls12_381Fr::root_of_unity(); + let exp = ELEMENTS_PER_4844_BLOCK.trailing_zeros(); + + for _ in exp..Bls12_381Fr::S { + omega.square(); + } + + let mut t = omega; + for _ in 0..12 { + assert!(t != Bls12_381Fr::one()); + t.square(); + } + assert!(t == Bls12_381Fr::one()); + + omega +} + +fn omega_inv() -> Bls12_381Fr { + omega().inverse().unwrap() +} + +fn m_inv() -> Bls12_381Fr { + Bls12_381Fr::from_str(&format!("{}", ELEMENTS_PER_4844_BLOCK)) + .unwrap() + .inverse() + .unwrap() +} + +fn bitreverse_idx(mut n: u32, l: u32) -> u32 { + let mut r = 0; + for _ in 0..l { + r = (r << 1) | (n & 1); + n >>= 1; + } + + r +} + +pub fn bitreverse(a: &mut [T]) { + let n = a.len() as u32; + assert!(n.is_power_of_two()); + let log_n = n.trailing_zeros(); + + for k in 0..n { + let rk = bitreverse_idx(k, log_n); + if k < rk { + a.swap(rk as usize, k as usize); + } + } +} + +fn serial_fft(a: &mut [Bls12_381Fr], omega: &Bls12_381Fr, log_n: u32) { + let n = a.len() as u32; + assert_eq!(n, 1 << log_n); + + bitreverse(a); + + let mut m = 1; + for _ in 0..log_n { + let w_m = omega.pow(&[(n / (2 * m)) as u64]); + + let mut k = 0; + while k < n { + let mut w = Bls12_381Fr::one(); + for j in 0..m { + let mut t = a[(k + j + m) as usize]; + t.mul_assign(&w); + let mut tmp = a[(k + j) as usize]; + tmp.sub_assign(&t); + a[(k + j + m) as usize] = tmp; + a[(k + j) as usize].add_assign(&t); + w.mul_assign(&w_m); + } + + k += 2 * m; + } + + m *= 2; + } +} + +pub fn fft(a: &mut [Bls12_381Fr]) { + assert_eq!(a.len(), ELEMENTS_PER_4844_BLOCK); + serial_fft(a, &omega(), ELEMENTS_PER_4844_BLOCK.trailing_zeros()); +} + +pub fn ifft(a: &mut [Bls12_381Fr]) { + assert_eq!(a.len(), ELEMENTS_PER_4844_BLOCK); + serial_fft(a, &omega_inv(), ELEMENTS_PER_4844_BLOCK.trailing_zeros()); + let m_inv = m_inv(); + for a in a.iter_mut() { + a.mul_assign(&m_inv); + } +} + +pub fn zksync_pubdata_into_ethereum_4844_data(input: &[u8]) -> Vec { + let mut poly = zksync_pubdata_into_monomial_form_poly(input); + fft(&mut poly); + // and we need to bitreverse + bitreverse(&mut poly); + // and now serialize in BE form as Ethereum expects + let mut result = Vec::with_capacity(32 * ELEMENTS_PER_4844_BLOCK); + use boojum::pairing::ff::PrimeFieldRepr; + for el in poly.into_iter() { + let mut buffer = [0u8; 32]; + el.into_repr().write_be(&mut buffer[..]).unwrap(); + result.extend(buffer); + } + assert_eq!(result.len(), 32 * ELEMENTS_PER_4844_BLOCK); + + result +} + +pub fn zksync_pubdata_into_monomial_form_poly(input: &[u8]) -> Vec { + assert_eq!(input.len(), BLOB_CHUNK_SIZE * ELEMENTS_PER_4844_BLOCK); + // we interpret it as coefficients starting from the top one + let mut poly = Vec::with_capacity(ELEMENTS_PER_4844_BLOCK); + use boojum::pairing::ff::PrimeFieldRepr; + for bytes in input.array_chunks::().rev() { + let mut buffer = [0u8; 32]; + buffer[..BLOB_CHUNK_SIZE].copy_from_slice(bytes); + let mut repr = ::Repr::default(); + repr.read_le(&buffer[..]).unwrap(); // note that it's LE + // Since repr only has 31 bytes, repr is guaranteed to be below the modulus + let as_field_element = Bls12_381Fr::from_repr(repr).unwrap(); + poly.push(as_field_element); + } + + poly +} + +pub fn ethereum_4844_pubdata_into_bitreversed_lagrange_form_poly(input: &[u8]) -> Vec { + // Ethereum's blob data requires that all field element representations are canonical, but we will handle + // a generic case. For BLS12-381 one can fit 2*modulus into 32 bytes, but not 3, so we need to subtract at most twice + // to get completely canonical representation sooner or later + assert_eq!(input.len(), 32 * ELEMENTS_PER_4844_BLOCK); + let mut poly = Vec::with_capacity(ELEMENTS_PER_4844_BLOCK); + use boojum::pairing::ff::PrimeFieldRepr; + let modulus = ::char(); + for bytes in input.array_chunks::<32>() { + let mut repr = ::Repr::default(); + repr.read_be(&bytes[..]).unwrap(); + let mut as_field_element = None; + 'inner: for _ in 0..3 { + if let Ok(normalized_field_element) = Bls12_381Fr::from_repr(repr) { + as_field_element = Some(normalized_field_element); + break 'inner; + } else { + repr.sub_noborrow(&modulus) + } + } + let as_field_element = as_field_element.unwrap(); + poly.push(as_field_element); + } + assert_eq!(poly.len(), ELEMENTS_PER_4844_BLOCK); + + poly +} + +pub fn ethereum_4844_data_into_zksync_pubdata(input: &[u8]) -> Vec { + assert_eq!(input.len(), 32 * ELEMENTS_PER_4844_BLOCK); + let mut poly = ethereum_4844_pubdata_into_bitreversed_lagrange_form_poly(input); + // and we need to bitreverse + bitreverse(&mut poly); + // now we need to iFFT it to get monomial form + ifft(&mut poly); + // and now serialize in LE by BLOB_CHUNK_SIZE chunks + let mut result = Vec::with_capacity(BLOB_CHUNK_SIZE * ELEMENTS_PER_4844_BLOCK); + use boojum::pairing::ff::PrimeFieldRepr; + // note that highest monomial goes first in byte array + for el in poly.into_iter().rev() { + let mut buffer = [0u8; 32]; + el.into_repr().write_le(&mut buffer[..]).unwrap(); + assert_eq!( + 0, buffer[31], + "zksync data is representable by 31 byte field elements LE" + ); + result.extend_from_slice(&buffer[..BLOB_CHUNK_SIZE]); + } + assert_eq!(result.len(), BLOB_CHUNK_SIZE * ELEMENTS_PER_4844_BLOCK); + + result +} + +#[cfg(test)] +mod tests { + use std::alloc::Global; + use std::collections::VecDeque; + + use super::*; + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder::*; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + use boojum::cs::gates::*; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::field::traits::field_like::PrimeFieldLike; + use boojum::field::Field; + use boojum::field::SmallField; + use boojum::gadgets::queue::CircuitQueueRawWitness; + use boojum::gadgets::tables::byte_split::ByteSplitTable; + use boojum::gadgets::tables::*; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::pairing::bls12_381::G1; + use boojum::pairing::ff::PrimeFieldRepr; + use boojum::pairing::ff::{Field as PairingField, PrimeField, SqrtField}; + use boojum::worker::Worker; + use rand::SeedableRng; + use rand::{Rand, Rng}; + + type F = GoldilocksField; + type P = GoldilocksField; + + #[test] + fn test_eip4844() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 60, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + let max_variables = 1 << 26; + let max_trace_len = 1 << 20; + + fn configure< + F: SmallField, + T: CsBuilderImpl, + GC: GateConfigurationHolder, + TB: StaticToolboxHolder, + >( + builder: CsBuilder, + ) -> CsBuilder, impl StaticToolboxHolder> { + let builder = builder.allow_lookup( + LookupParameters::UseSpecializedColumnsWithTableIdAsConstant { + width: 3, + num_repetitions: 20, + share_table_id: true, + }, + ); + let builder = ConstantsAllocatorGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = PublicInputGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = FmaGateInBaseFieldWithoutConstant::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = ReductionGate::::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + // let owned_cs = ReductionGate::::configure_for_cs(owned_cs, GatePlacementStrategy::UseSpecializedColumns { num_repetitions: 8, share_constants: true }); + let builder = BooleanConstraintGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = UIntXAddGate::<32>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = UIntXAddGate::<16>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = SelectionGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = DotProductGate::<4>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + // let owned_cs = DotProductGate::<4>::configure_for_cs(owned_cs, GatePlacementStrategy::UseSpecializedColumns { num_repetitions: 1, share_constants: true }); + let builder = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, max_trace_len); + let builder = new_builder::<_, F>(builder_impl); + + let builder = configure(builder); + let mut owned_cs = builder.build(max_variables); + + // add tables + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let table = create_and8_table(); + owned_cs.add_lookup_table::(table); + + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + + let cs = &mut owned_cs; + + let round_function = Poseidon2Goldilocks; + + // make some random chunks + let mut data_chunks = vec![[0u8; BLOB_CHUNK_SIZE]; ELEMENTS_PER_4844_BLOCK]; + let mut rng = rand::XorShiftRng::from_seed([0, 0, 0, 42]); + for dst in data_chunks.iter_mut() { + let el = Bls12_381Fr::rand(&mut rng); + let mut bytes = [0u8; 32]; + el.into_repr().write_le(&mut bytes[..]).unwrap(); + dst.copy_from_slice(&bytes[..BLOB_CHUNK_SIZE]); + } + + let mut blob_data_flattened = vec![]; + for el in data_chunks.iter() { + blob_data_flattened.extend_from_slice(el); + } + + // now we can get it as polynomial, and + use zkevm_opcode_defs::sha3::*; + let mut linear_hash_output = [0u8; 32]; + let digest = Keccak256::digest(&blob_data_flattened); + + linear_hash_output.copy_from_slice(digest.as_slice()); + + // now we can get some quasi-setup for KZG and make a blob + let poly = zksync_pubdata_into_monomial_form_poly(&blob_data_flattened); + + let mut setup = Vec::with_capacity(ELEMENTS_PER_4844_BLOCK); + use boojum::pairing::CurveAffine; + use boojum::pairing::CurveProjective; + let mut point = G1::one(); + let scalar = Bls12_381Fr::from_str("42").unwrap().into_repr(); + for _ in 0..ELEMENTS_PER_4844_BLOCK { + setup.push(point); + point.mul_assign(scalar); + } + + // compute commitment + let mut commitment = G1::zero(); + for (scalar, point) in poly.iter().zip(setup.iter()) { + let mut el = *point; + el.mul_assign(scalar.into_repr()); + commitment.add_assign(&el); + } + + let (kzg_commitment_x, kzg_commitment_y) = commitment.into_affine().into_xy_unchecked(); + let mut buffer = [0u8; 96]; + kzg_commitment_x + .into_repr() + .write_be(&mut buffer[..48]) + .unwrap(); + kzg_commitment_y + .into_repr() + .write_be(&mut buffer[48..]) + .unwrap(); + + let mut versioned_hash = [0u8; 32]; + let digest = Keccak256::digest(&buffer); + versioned_hash.copy_from_slice(digest.as_slice()); + versioned_hash[0] = 0x01; + + let evaluation_point = Keccak256::digest( + linear_hash_output + .into_iter() + .chain(versioned_hash.into_iter()) + .collect::>(), + )[16..] + .to_vec(); + + dbg!(hex::encode(&evaluation_point)); + + let mut buffer = [0u8; 32]; + buffer[16..].copy_from_slice(&evaluation_point); + let mut evaluation_point_repr = + ::Repr::default(); + evaluation_point_repr.read_be(&buffer[..]).unwrap(); + let evaluation_point_fr = Bls12_381Fr::from_repr(evaluation_point_repr).unwrap(); + dbg!(evaluation_point_fr); + + use boojum::pairing::bls12_381::FrRepr; + // evaluate polynomial + let mut evaluation_result = Bls12_381Fr::zero(); + let mut power = Bls12_381Fr::one(); + for coeff in poly.iter() { + let mut tmp = *coeff; + tmp.mul_assign(&power); + evaluation_result.add_assign(&tmp); + power.mul_assign(&evaluation_point_fr); + } + + dbg!(evaluation_result); + let mut opening_value_bytes = [0u8; 32]; + evaluation_result + .into_repr() + .write_be(&mut opening_value_bytes[..]) + .unwrap(); + + let mut observable_output = EIP4844OutputData::::placeholder_witness(); + let output_hash = Keccak256::digest( + versioned_hash + .into_iter() + .chain(evaluation_point.into_iter()) + .chain(opening_value_bytes.into_iter()) + .collect::>(), + ) + .into(); + dbg!(hex::encode(&output_hash)); + observable_output.output_hash = output_hash; + observable_output.linear_hash = linear_hash_output; + + let closed_form_input = EIP4844InputOutputWitness { + start_flag: true, + completion_flag: true, + observable_input: (), + observable_output: observable_output, + hidden_fsm_input: (), + hidden_fsm_output: (), + }; + + let witness = EIP4844CircuitInstanceWitness { + closed_form_input, + versioned_hash, + linear_hash_output, + data_chunks: data_chunks + .into_iter() + .map(|el| BlobChunkWitness { inner: el }) + .collect::>>(), + }; + + eip_4844_entry_point::<_, _, _>(cs, witness, &round_function, 4096); + + dbg!(cs.next_available_row()); + + cs.pad_and_shrink(); + + let mut cs = owned_cs.into_assembly::(); + cs.print_gate_stats(); + let worker = Worker::new(); + assert!(cs.check_if_satisfied(&worker)); + } + + #[test] + fn round_trip_fft() { + let mut rng = rand::XorShiftRng::new_unseeded(); + for _ in 0..128 { + let mut input: Vec<_> = (0..4096).map(|_| Bls12_381Fr::rand(&mut rng)).collect(); + let expected = input.clone(); + ifft(&mut input); + fft(&mut input); + for (i, (a, b)) in input.iter().zip(expected.iter()).enumerate() { + if a != b { + panic!("Diverged at i = {}, a = {:?}, b = {:?}", i, a, b); + } + } + } + } + + #[test] + fn test_data_roundtrip() { + let mut rng = rand::XorShiftRng::new_unseeded(); + let zksync_data: Vec = (0..(31 * 4096)).map(|_| rng.gen()).collect(); + let monomial_form = zksync_pubdata_into_monomial_form_poly(&zksync_data); + let mut expected_lagrange_form = monomial_form.clone(); + fft(&mut expected_lagrange_form); + bitreverse(&mut expected_lagrange_form); + + let ethereum_data = zksync_pubdata_into_ethereum_4844_data(&zksync_data); + let recreated_lagrange_form = + ethereum_4844_pubdata_into_bitreversed_lagrange_form_poly(ðereum_data); + for (i, (a, b)) in expected_lagrange_form + .iter() + .zip(recreated_lagrange_form.iter()) + .enumerate() + { + if a != b { + panic!("Diverged at i = {}, a = {:?}, b = {:?}", i, a, b); + } + } + let mut recreated_monomial_form = recreated_lagrange_form.clone(); + bitreverse(&mut recreated_monomial_form); + ifft(&mut recreated_monomial_form); + for (i, (a, b)) in monomial_form + .iter() + .zip(recreated_monomial_form.iter()) + .enumerate() + { + if a != b { + panic!("Diverged at i = {}, a = {:?}, b = {:?}", i, a, b); + } + } + let data_back = ethereum_4844_data_into_zksync_pubdata(ðereum_data); + for (i, (a, b)) in zksync_data.iter().zip(data_back.iter()).enumerate() { + if a != b { + panic!("Diverged at i = {}, a = {:?}, b = {:?}", i, a, b); + } + } + } +} diff --git a/crates/zkevm_circuits/src/keccak256_round_function/buffer/mod.rs b/crates/zkevm_circuits/src/keccak256_round_function/buffer/mod.rs new file mode 100644 index 0000000..6747331 --- /dev/null +++ b/crates/zkevm_circuits/src/keccak256_round_function/buffer/mod.rs @@ -0,0 +1,164 @@ +use super::*; +use crate::boojum::gadgets::traits::auxiliary::PrettyComparison; +use crate::boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct ByteBuffer { + pub bytes: [UInt8; BUFFER_SIZE], + pub filled: UInt8, // assume that it's enough +} + +impl CSPlaceholder for ByteBuffer { + fn placeholder>(cs: &mut CS) -> Self { + let zero_u8 = UInt8::zero(cs); + Self { + bytes: [zero_u8; BUFFER_SIZE], + filled: zero_u8, + } + } +} + +// we map a set of offset + current fill factor into "start from here" bit for 0-th byte of the buffer of length N +pub type BufferMappingFunction = + fn(&mut CS, UInt8, UInt8, [(); N]) -> [Boolean; M]; + +impl ByteBuffer { + pub fn can_fill_fixed_bytes, const N: usize>( + &self, + cs: &mut CS, + ) -> Boolean { + let max_filled = BUFFER_SIZE - N; + let max_filled = u8::try_from(max_filled).expect("must fit into u8"); + let upper_bound = UInt8::allocate_constant(cs, max_filled); + // we need to check that filled <= max_filled + let (_, uf) = upper_bound.overflowing_sub(cs, &self.filled); + let can_fill = uf.negated(cs); + + can_fill + } + + pub fn can_fill_bytes>( + &self, + cs: &mut CS, + bytes_to_fill: UInt8, + ) -> Boolean { + let next_filled = self.filled.add_no_overflow(cs, bytes_to_fill); + let max_filled = BUFFER_SIZE; + let max_filled = u8::try_from(max_filled).expect("must fit into u8"); + let upper_bound = UInt8::allocate_constant(cs, max_filled); + // we need to check that next_filled <= max_filled + let (_, uf) = upper_bound.overflowing_sub(cs, &next_filled); + let can_fill = uf.negated(cs); + + can_fill + } + + pub fn can_consume_n_bytes, const N: usize>( + &self, + cs: &mut CS, + ) -> Boolean { + let bytes_to_consume = N; + let bytes_to_consume = u8::try_from(bytes_to_consume).expect("must fit into u8"); + let bytes_to_consume = UInt8::allocate_constant(cs, bytes_to_consume); + // we need to check that filled >= bytes_to_consume + let (_, uf) = self.filled.overflowing_sub(cs, &bytes_to_consume); + let can_consume = uf.negated(cs); + + can_consume + } + + // must be called only after caller ensures enough capacity left + pub fn fill_with_bytes, const N: usize>( + &mut self, + cs: &mut CS, + input: &[UInt8; N], + offset: UInt8, + meaningful_bytes: UInt8, + mapping_function: BufferMappingFunction, + ) { + assert!(N < 128); // kind of arbitrary constant here, in practice we would only use 32 + // we do naive implementation of the shift register + let mut offset = offset.into_num(); + let one_num = Num::allocated_constant(cs, F::ONE); + let zero_u8 = UInt8::zero(cs); + // base case + let mut shifted_input = *input; + offset = offset.sub(cs, &one_num); + for i in 1..N { + let use_form_here = offset.is_zero(cs); + offset = offset.sub(cs, &one_num); + let mut candidate = [zero_u8; N]; + candidate[0..(N - i)].copy_from_slice(&input[i..]); + shifted_input = <[UInt8; N]>::conditionally_select( + cs, + use_form_here, + &candidate, + &shifted_input, + ); + } + // now we can use a mapping function to determine based on the number of meaningful bytes and current fill factor + // on which bytes to use from the start and which not. We already shifted all meaningful bytes to the left above, + // so we only need 1 bit to show "start here" + + // dbg!(shifted_input.witness_hook(cs)()); + + let use_byte_for_place_mask = mapping_function(cs, meaningful_bytes, self.filled, [(); N]); + let mut counter = meaningful_bytes.into_num(); + let mut shifted_buffer_exhausted = meaningful_bytes.is_zero(cs); + // TODO: transpose to use linear combination + for (idx, src) in shifted_input.into_iter().enumerate() { + // buffer above is shifted all the way to the left, so if byte number 0 can use any of 0..BUFFER_SIZE markers, + // then for byte number 1 we can only use markers 1..BUFFER_SIZE markers, and so on, and byte number 1 can never go into + // buffer position 0 + + // we also need to determine if we ever "use" this byte or should zero it out for later padding procedure + let src = src.mask_negated(cs, shifted_buffer_exhausted); + let markers = &use_byte_for_place_mask[..(BUFFER_SIZE - idx)]; + let dsts = &mut self.bytes[idx..]; + assert_eq!(markers.len(), dsts.len()); + + for (dst, flag) in dsts.iter_mut().zip(markers.iter()) { + *dst = UInt8::conditionally_select(cs, *flag, &src, &*dst); + } + + counter = counter.sub(cs, &one_num); + // this will underflow and walk around the field range, but not important for our ranges of N + let now_exhausted = counter.is_zero(cs); + shifted_buffer_exhausted = + Boolean::multi_or(cs, &[now_exhausted, shifted_buffer_exhausted]); + } + self.filled = self.filled.add_no_overflow(cs, meaningful_bytes); + // compare no overflow + let capacity = u8::try_from(BUFFER_SIZE).expect("must fit into u8"); + let _ = UInt8::allocated_constant(cs, capacity).sub_no_overflow(cs, self.filled); + } + + pub fn consume, const N: usize>( + &mut self, + cs: &mut CS, + allow_partial: Boolean, + ) -> [UInt8; N] { + let bytes_to_take = u8::try_from(N).expect("must fit"); + let bytes_to_take = UInt8::allocated_constant(cs, bytes_to_take); + let (leftover, uf) = self.filled.overflowing_sub(cs, &bytes_to_take); + let have_enough = uf.negated(cs); + let is_valid = Boolean::multi_or(cs, &[have_enough, allow_partial]); + let boolean_true = Boolean::allocated_constant(cs, true); + Boolean::enforce_equal(cs, &is_valid, &boolean_true); + + self.filled = leftover.mask_negated(cs, uf); + + let zero_u8 = UInt8::zero(cs); + let mut result = [zero_u8; N]; + result.copy_from_slice(&self.bytes[..N]); + + let mut new_bytes = [zero_u8; BUFFER_SIZE]; + new_bytes[..(BUFFER_SIZE - N)].copy_from_slice(&self.bytes[N..]); + + self.bytes = new_bytes; + + result + } +} diff --git a/crates/zkevm_circuits/src/keccak256_round_function/input.rs b/crates/zkevm_circuits/src/keccak256_round_function/input.rs index d521b41..480b518 100644 --- a/crates/zkevm_circuits/src/keccak256_round_function/input.rs +++ b/crates/zkevm_circuits/src/keccak256_round_function/input.rs @@ -4,6 +4,7 @@ use super::*; use crate::base_structures::precompile_input_outputs::*; use crate::base_structures::vm_state::*; +use crate::keccak256_round_function::buffer::ByteBuffer; use boojum::cs::Variable; use boojum::gadgets::queue::*; use boojum::gadgets::traits::allocatable::CSAllocatable; @@ -19,19 +20,22 @@ use boojum::gadgets::traits::selectable::Selectable; use boojum::gadgets::traits::witnessable::WitnessHookable; use boojum::serde_utils::BigArraySerde; +pub const MEMORY_QUERIES_PER_CYCLE: usize = 6; +pub const KECCAK_PRECOMPILE_BUFFER_SIZE: usize = MEMORY_QUERIES_PER_CYCLE * 32; + #[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] #[derivative(Clone, Copy, Debug)] #[DerivePrettyComparison("true")] pub struct Keccak256RoundFunctionFSM { pub read_precompile_call: Boolean, pub read_unaligned_words_for_round: Boolean, + pub padding_round: Boolean, pub completed: Boolean, pub keccak_internal_state: [[[UInt8; BYTES_PER_WORD]; LANE_WIDTH]; LANE_WIDTH], pub timestamp_to_use_for_read: UInt32, pub timestamp_to_use_for_write: UInt32, pub precompile_call_params: Keccak256PrecompileCallParams, - pub u8_words_buffer: [UInt8; BYTES_BUFFER_SIZE], - pub u64_words_buffer_markers: [Boolean; BUFFER_SIZE_IN_U64_WORDS], + pub buffer: ByteBuffer, } impl CSPlaceholder for Keccak256RoundFunctionFSM { @@ -42,13 +46,13 @@ impl CSPlaceholder for Keccak256RoundFunctionFSM { Self { read_precompile_call: boolean_false, read_unaligned_words_for_round: boolean_false, + padding_round: boolean_false, completed: boolean_false, keccak_internal_state: [[[zero_u8; BYTES_PER_WORD]; LANE_WIDTH]; LANE_WIDTH], timestamp_to_use_for_read: zero_u32, timestamp_to_use_for_write: zero_u32, precompile_call_params: Keccak256PrecompileCallParams::::placeholder(cs), - u8_words_buffer: [zero_u8; BYTES_BUFFER_SIZE], - u64_words_buffer_markers: [boolean_false; BUFFER_SIZE_IN_U64_WORDS], + buffer: ByteBuffer::::placeholder(cs), } } } diff --git a/crates/zkevm_circuits/src/keccak256_round_function/mod.rs b/crates/zkevm_circuits/src/keccak256_round_function/mod.rs index b726488..fd524c5 100644 --- a/crates/zkevm_circuits/src/keccak256_round_function/mod.rs +++ b/crates/zkevm_circuits/src/keccak256_round_function/mod.rs @@ -2,6 +2,7 @@ use super::*; use boojum::field::SmallField; +use boojum::config::*; use boojum::cs::traits::cs::ConstraintSystem; use boojum::gadgets::boolean::Boolean; use boojum::gadgets::traits::selectable::Selectable; @@ -13,6 +14,7 @@ use cs_derive::*; use crate::ethereum_types::U256; use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::keccak256_round_function::buffer::ByteBuffer; use boojum::gadgets::num::Num; use zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE; @@ -35,6 +37,8 @@ use boojum::gadgets::u160::UInt160; use boojum::gadgets::u8::UInt8; use std::sync::{Arc, RwLock}; +pub mod buffer; + pub mod input; use self::input::*; @@ -43,55 +47,110 @@ use self::input::*; // #[DerivePrettyComparison("true")] pub struct Keccak256PrecompileCallParams { pub input_page: UInt32, - pub input_offset: UInt32, + pub input_memory_byte_offset: UInt32, + pub input_memory_byte_length: UInt32, pub output_page: UInt32, - pub output_offset: UInt32, - pub num_rounds: UInt32, + pub output_word_offset: UInt32, + pub needs_full_padding_round: Boolean, } impl CSPlaceholder for Keccak256PrecompileCallParams { fn placeholder>(cs: &mut CS) -> Self { let zero_u32 = UInt32::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); Self { input_page: zero_u32, - input_offset: zero_u32, + input_memory_byte_offset: zero_u32, + input_memory_byte_length: zero_u32, output_page: zero_u32, - output_offset: zero_u32, - num_rounds: zero_u32, + output_word_offset: zero_u32, + needs_full_padding_round: boolean_false, } } } impl Keccak256PrecompileCallParams { - pub fn from_encoding>(_cs: &mut CS, encoding: UInt256) -> Self { - let input_offset = encoding.inner[0]; - let output_offset = encoding.inner[2]; + // from PrecompileCallABI + pub fn from_encoding>(cs: &mut CS, encoding: UInt256) -> Self { + let input_memory_byte_offset = encoding.inner[0]; + let input_memory_byte_length = encoding.inner[1]; + + let output_word_offset = encoding.inner[2]; + let input_page = encoding.inner[4]; let output_page = encoding.inner[5]; - let num_rounds = encoding.inner[6]; + let (_, rem) = input_memory_byte_length.div_by_constant(cs, KECCAK_RATE_BYTES as u32); + + let needs_full_padding_round = rem.is_zero(cs); let new = Self { input_page, - input_offset, + input_memory_byte_offset, + input_memory_byte_length, output_page, - output_offset, - num_rounds, + output_word_offset, + needs_full_padding_round, }; new } } +fn trivial_mapping_function< + F: SmallField, + CS: ConstraintSystem, + const N: usize, + const BUFFER_SIZE: usize, +>( + cs: &mut CS, + bytes_to_consume: &UInt8, + current_fill_factor: &UInt8, + _unused: [(); N], +) -> [Boolean; BUFFER_SIZE] { + use boojum::config::*; + if ::DebugConfig::PERFORM_RUNTIME_ASSERTS == true { + let already_filled = current_fill_factor.witness_hook(cs)(); + let new_to_fill = bytes_to_consume.witness_hook(cs)(); + if let Some(already_filled) = already_filled { + if let Some(new_to_fill) = new_to_fill { + assert!(new_to_fill as usize + already_filled as usize <= BUFFER_SIZE); + } + } + } + + let boolean_false = Boolean::allocated_constant(cs, false); + + let mut result = [boolean_false; BUFFER_SIZE]; + let zero_to_fill = bytes_to_consume.is_zero(cs); + let marker = zero_to_fill.negated(cs); + + // we just need to put a marker after the current fill value + let mut tmp = current_fill_factor.into_num(); + let one_num = Num::allocated_constant(cs, F::ONE); + for dst in result.iter_mut() { + let should_fill = tmp.is_zero(cs); + *dst = should_fill.and(cs, marker); + tmp = tmp.sub(cs, &one_num); + } + + // if crate::config::CIRCUIT_VERSOBE { + // dbg!(result.witness_hook(cs)().unwrap()); + // } + + result +} + +use boojum::gadgets::keccak256::KECCAK_RATE_BYTES; + pub const KECCAK256_RATE_IN_U64_WORDS: usize = 17; -pub const MEMORY_EQURIES_PER_CYCLE: usize = 5; // we need to read as much as possible to use a round function every cycle +pub const MEMORY_EQURIES_PER_CYCLE: usize = 6; // we need to read as much as possible to use a round function every cycle pub const NUM_U64_WORDS_PER_CYCLE: usize = 4 * MEMORY_EQURIES_PER_CYCLE; pub const NEW_BYTES_PER_CYCLE: usize = 8 * NUM_U64_WORDS_PER_CYCLE; // we absorb 136 elements per cycle, and add 160 elements per cycle, so we need to skip memory reads // sometimes and do absorbs instead -pub const BUFFER_SIZE_IN_U64_WORDS: usize = - MEMORY_EQURIES_PER_CYCLE * 4 + KECCAK256_RATE_IN_U64_WORDS - 1; -pub const BYTES_BUFFER_SIZE: usize = BUFFER_SIZE_IN_U64_WORDS * 8; +pub const BUFFER_SIZE_IN_U64_WORDS: usize = 192 / 8; +pub const BYTES_BUFFER_SIZE: usize = 192; pub fn keccak256_precompile_inner< F: SmallField, @@ -123,10 +182,13 @@ where let boolean_false = Boolean::allocated_constant(cs, false); let boolean_true = Boolean::allocated_constant(cs, true); let zero_u8 = UInt8::zero(cs); - let buffer_len_bound = UInt16::allocated_constant( - cs, - (BUFFER_SIZE_IN_U64_WORDS - NUM_U64_WORDS_PER_CYCLE + 1) as u16, - ); + let one_num = Num::allocated_constant(cs, F::ONE); + + let empty_buffer = ByteBuffer::::placeholder(cs); + + let mut full_padding_buffer = [zero_u8; KECCAK_RATE_BYTES]; + full_padding_buffer[0] = UInt8::allocated_constant(cs, 0x01); + full_padding_buffer[KECCAK_RATE_BYTES - 1] = UInt8::allocated_constant(cs, 0x80); // we can have a degenerate case when queue is empty, but it's a first circuit in the queue, // so we taken default FSM state that has state.read_precompile_call = true; @@ -137,7 +199,6 @@ where if crate::config::CIRCUIT_VERSOBE { dbg!(can_finish_immediatelly.witness_hook(cs)()); - dbg!(state.witness_hook(cs)()); } state.read_precompile_call = state @@ -148,8 +209,53 @@ where .mask_negated(cs, can_finish_immediatelly); state.completed = Boolean::multi_or(cs, &[state.completed, can_finish_immediatelly]); + #[allow(unused_variables)] + let mut keccak_self_verifier = None; + if ::DebugConfig::PERFORM_RUNTIME_ASSERTS == true { + use zkevm_opcode_defs::sha3::Digest; + if state.read_precompile_call.witness_hook(cs)().unwrap() == true { + keccak_self_verifier = Some((true, zkevm_opcode_defs::sha3::Keccak256::new())); + } else { + keccak_self_verifier = Some((false, zkevm_opcode_defs::sha3::Keccak256::new())); + } + } + + if crate::config::CIRCUIT_VERSOBE { + if let Ok(witness) = precompile_calls_queue.witness.elements.read() { + dbg!(witness.len()); + } + } + // main work cycle for _cycle in 0..limit { + if crate::config::CIRCUIT_VERSOBE { + dbg!(_cycle); + dbg!(state.read_precompile_call.witness_hook(cs)()); + dbg!(state.read_unaligned_words_for_round.witness_hook(cs)()); + dbg!(state.padding_round.witness_hook(cs)()); + dbg!(state.completed.witness_hook(cs)()); + dbg!(state + .precompile_call_params + .input_memory_byte_offset + .witness_hook(cs)()); + dbg!(state + .precompile_call_params + .input_memory_byte_length + .witness_hook(cs)()); + dbg!(precompile_calls_queue.length.witness_hook(cs)()); + if let Ok(witness) = precompile_calls_queue.witness.elements.read() { + dbg!(witness.len()); + } + } + + if ::DebugConfig::PERFORM_RUNTIME_ASSERTS == true { + use zkevm_opcode_defs::sha3::Digest; + if state.read_precompile_call.witness_hook(cs)().unwrap() == true { + *keccak_self_verifier.as_mut().unwrap() = + (true, zkevm_opcode_defs::sha3::Keccak256::new()); + } + } + // if we are in a proper state then get the ABI from the queue let (precompile_call, _) = precompile_calls_queue.pop_front(cs, state.read_precompile_call); @@ -178,6 +284,15 @@ where let params_encoding = precompile_call.key; let call_params = Keccak256PrecompileCallParams::from_encoding(cs, params_encoding); + if crate::config::CIRCUIT_VERSOBE { + if state.read_precompile_call.witness_hook(cs)().unwrap() == true { + println!( + "New request for params {:?}", + call_params.witness_hook(cs)().unwrap() + ); + } + } + state.precompile_call_params = Keccak256PrecompileCallParams::conditionally_select( cs, state.read_precompile_call, @@ -204,64 +319,119 @@ where // and do some work! keccak256 is expensive let reset_buffer = Boolean::multi_or(cs, &[state.read_precompile_call, state.completed]); + // if we just have read a precompile call with zero length input, we want to perform only one padding round + let new_request_is_input_length_zero = call_params.input_memory_byte_length.is_zero(cs); + let new_request_with_non_zero_length = new_request_is_input_length_zero.negated(cs); + let have_read_zero_length_call = Boolean::multi_and( + cs, + &[state.read_precompile_call, new_request_is_input_length_zero], + ); + // otherwise we proceed with reading the input and follow the logic of padding round based on the precomputed + // padding round needed/not needed in the params + let have_read_non_zero_length_call = Boolean::multi_and( + cs, + &[state.read_precompile_call, new_request_with_non_zero_length], + ); + + state.read_precompile_call = boolean_false; state.read_unaligned_words_for_round = Boolean::multi_or( cs, &[ - state.read_precompile_call, state.read_unaligned_words_for_round, + have_read_non_zero_length_call, ], ); - state.read_precompile_call = boolean_false; + state.padding_round = + Boolean::multi_or(cs, &[state.padding_round, have_read_zero_length_call]); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(state.read_precompile_call.witness_hook(cs)()); + dbg!(state.read_unaligned_words_for_round.witness_hook(cs)()); + dbg!(state.padding_round.witness_hook(cs)()); + } // --------------------------------- // Now perform few memory queries to read content - for el in state.u64_words_buffer_markers.iter_mut() { - *el = Boolean::conditionally_select(cs, reset_buffer, &boolean_false, el); - } + state.buffer = ByteBuffer::::conditionally_select( + cs, + reset_buffer, + &empty_buffer, + &state.buffer, + ); + + // conditionally reset state. Keccak256 empty state is just all 0s - // even though it's not important, we cleanup the buffer too - for el in state.u8_words_buffer.iter_mut() { - *el = UInt8::conditionally_select(cs, reset_buffer, &zero_u8, el); + for dst in state.keccak_internal_state.iter_mut() { + for dst in dst.iter_mut() { + for dst in dst.iter_mut() { + *dst = dst.mask_negated(cs, reset_buffer); + } + } } - let initial_buffer_len = { - let lc: Vec<_> = state - .u64_words_buffer_markers - .iter() - .map(|el| (el.get_variable(), F::ONE)) - .collect(); - let lc = Num::linear_combination(cs, &lc); + let no_more_bytes = state + .precompile_call_params + .input_memory_byte_length + .is_zero(cs); + let have_leftover_bytes = no_more_bytes.negated(cs); + let should_read_in_general = Boolean::multi_and( + cs, + &[have_leftover_bytes, state.read_unaligned_words_for_round], + ); - unsafe { UInt16::from_variable_unchecked(lc.get_variable()) } + let mapping_function = |cs: &mut CS, + bytes_to_consume: UInt8, + current_fill_factor: UInt8, + _unused: [(); 32]| { + trivial_mapping_function::( + cs, + &bytes_to_consume, + ¤t_fill_factor, + _unused, + ) }; - // we can fill the buffer as soon as it's length <= MAX - NEW_WORDS_PER_CYCLE - let (_, of) = initial_buffer_len.overflowing_sub(cs, &buffer_len_bound); - let can_fill = of; - let can_not_fill = can_fill.negated(cs); - let zero_rounds_left = state.precompile_call_params.num_rounds.is_zero(cs); - // if we can not fill then we should (sanity check) be in a state of reading new words - // and have >0 rounds left - - state - .read_unaligned_words_for_round - .conditionally_enforce_true(cs, can_not_fill); - zero_rounds_left.conditionally_enforce_false(cs, can_not_fill); - let non_zero_rounds_left = zero_rounds_left.negated(cs); - - let should_read = Boolean::multi_and( - cs, - &[ - non_zero_rounds_left, - state.read_unaligned_words_for_round, - can_fill, - ], - ); + let mut bias_variable = should_read_in_general.get_variable(); + // logic in short - we always try to read from memory into buffer, + // and every time execute 1 keccak256 round function + for _ in 0..MEMORY_QUERIES_PER_CYCLE { + // we have a little more complex logic here, but it's homogenious + let (aligned_memory_index, unalignment) = state + .precompile_call_params + .input_memory_byte_offset + .div_by_constant(cs, 32); + let at_most_meaningful_bytes_in_query = UInt32::allocated_constant(cs, 32) + .into_num() + .sub(cs, &unalignment.into_num()); + let at_most_meaningful_bytes_in_query = unsafe { + UInt32::from_variable_unchecked(at_most_meaningful_bytes_in_query.get_variable()) + }; + let (_, uf) = state + .precompile_call_params + .input_memory_byte_length + .overflowing_sub(cs, at_most_meaningful_bytes_in_query); + let meaningful_bytes_in_query = UInt32::conditionally_select( + cs, + uf, + &state.precompile_call_params.input_memory_byte_length, + &at_most_meaningful_bytes_in_query, + ); + + let nothing_to_read = meaningful_bytes_in_query.is_zero(cs); + let have_something_to_read = nothing_to_read.negated(cs); + let bytes_to_fill = + unsafe { UInt8::from_variable_unchecked(meaningful_bytes_in_query.get_variable()) }; + let enough_buffer_space = state.buffer.can_fill_bytes(cs, bytes_to_fill); + let should_read = Boolean::multi_and( + cs, + &[ + have_something_to_read, + enough_buffer_space, + state.read_unaligned_words_for_round, + ], + ); - let mut new_bytes_to_read = [zero_u8; NEW_BYTES_PER_CYCLE]; - let mut bias_variable = should_read.get_variable(); - for dst in new_bytes_to_read.array_chunks_mut::<32>() { let read_query_value = memory_read_witness.conditionally_allocate_biased(cs, should_read, bias_variable); bias_variable = read_query_value.inner[0].get_variable(); @@ -269,114 +439,188 @@ where let read_query = MemoryQuery { timestamp: state.timestamp_to_use_for_read, memory_page: state.precompile_call_params.input_page, - index: state.precompile_call_params.input_offset, + index: aligned_memory_index, rw_flag: boolean_false, is_ptr: boolean_false, value: read_query_value, }; - let may_be_new_offset = unsafe { - state - .precompile_call_params - .input_offset - .increment_unchecked(cs) - }; - state.precompile_call_params.input_offset = UInt32::conditionally_select( + // perform read + memory_queue.push(cs, read_query, should_read); + + // update state variables + let may_be_new_input_memory_byte_offset = state + .precompile_call_params + .input_memory_byte_offset + .add_no_overflow(cs, meaningful_bytes_in_query); + let may_be_new_input_memory_byte_length = state + .precompile_call_params + .input_memory_byte_length + .sub_no_overflow(cs, meaningful_bytes_in_query); + + state.precompile_call_params.input_memory_byte_offset = UInt32::conditionally_select( cs, should_read, - &may_be_new_offset, - &state.precompile_call_params.input_offset, + &may_be_new_input_memory_byte_offset, + &state.precompile_call_params.input_memory_byte_offset, + ); + state.precompile_call_params.input_memory_byte_length = UInt32::conditionally_select( + cs, + should_read, + &may_be_new_input_memory_byte_length, + &state.precompile_call_params.input_memory_byte_length, ); - // perform read - memory_queue.push(cs, read_query, should_read); + // update if we do not read + let bytes_to_fill = bytes_to_fill.mask(cs, should_read); - // we need to change endianess. Memory is BE, and each of 4 byte chunks should be interpreted as BE u32 for sha256 + // fill the buffer let be_bytes = read_query_value.to_be_bytes(cs); - *dst = be_bytes; - } + // if crate::config::CIRCUIT_VERSOBE { + // dbg!(be_bytes.witness_hook(cs)().map(|el| hex::encode(&el))); + // } + let offset = unsafe { UInt8::from_variable_unchecked(unalignment.get_variable()) }; + + if crate::config::CIRCUIT_VERSOBE { + dbg!(hex::encode(&state.buffer.bytes.witness_hook(cs)().unwrap())); + dbg!(hex::encode(&be_bytes.witness_hook(cs)().unwrap())); + dbg!(bytes_to_fill.witness_hook(cs)().unwrap()); + } - // our buffer len fits at least to push new elements and get enough for round function - // this is quadratic complexity, but we it's easier to handle and cheap compared to round function - let should_push = should_read; + state + .buffer + .fill_with_bytes(cs, &be_bytes, offset, bytes_to_fill, mapping_function); - for src in new_bytes_to_read.array_chunks::<8>() { - let mut should_push = should_push; - for (is_busy, dst) in state - .u64_words_buffer_markers - .iter_mut() - .zip(state.u8_words_buffer.array_chunks_mut::<8>()) - { - let is_free = is_busy.negated(cs); - let update = Boolean::multi_and(cs, &[is_free, should_push]); - let should_not_update = update.negated(cs); - *dst = UInt8::parallel_select(cs, update, src, dst); - *is_busy = Boolean::multi_or(cs, &[update, *is_busy]); - should_push = Boolean::multi_and(cs, &[should_push, should_not_update]); + if crate::config::CIRCUIT_VERSOBE { + dbg!(hex::encode(&state.buffer.bytes.witness_hook(cs)().unwrap())); } - - Boolean::enforce_equal(cs, &should_push, &boolean_false); } - - let may_be_new_num_rounds = unsafe { - state - .precompile_call_params - .num_rounds - .decrement_unchecked(cs) - }; - state.precompile_call_params.num_rounds = UInt32::conditionally_select( + // now actually run keccak permutation + + // we either mask for padding, or mask in full if it's full padding round + let zero_bytes_left = state + .precompile_call_params + .input_memory_byte_length + .is_zero(cs); + + let currently_filled = state.buffer.filled; + let almost_filled = UInt8::allocated_constant(cs, (KECCAK_RATE_BYTES - 1) as u8); + let do_one_byte_of_padding = UInt8::equals(cs, ¤tly_filled, &almost_filled); + // NOTE: we have already precomputed if we will need a full padding round, so we just take something form buffer + // and run keccak premutation + let mut input = state + .buffer + .consume::(cs, boolean_true); + let buffer_now_empty = state.buffer.filled.is_zero(cs); + let no_extra_padding_round_required = state + .precompile_call_params + .needs_full_padding_round + .negated(cs); + let apply_padding = Boolean::multi_and( cs, - state.read_unaligned_words_for_round, - &may_be_new_num_rounds, - &state.precompile_call_params.num_rounds, + &[ + zero_bytes_left, + buffer_now_empty, + state.read_unaligned_words_for_round, + no_extra_padding_round_required, + ], ); - // absorb - - // compute shifted buffer that removes first RATE elements and padds with something - - // take some work - let mut input = [zero_u8; keccak256::KECCAK_RATE_BYTES]; - input.copy_from_slice(&state.u8_words_buffer[..keccak256::KECCAK_RATE_BYTES]); - - // keep the rest - let mut tmp_buffer = [zero_u8; BYTES_BUFFER_SIZE]; - tmp_buffer[..(BYTES_BUFFER_SIZE - keccak256::KECCAK_RATE_BYTES)] - .copy_from_slice(&state.u8_words_buffer[keccak256::KECCAK_RATE_BYTES..]); - - // also reset markers - let mut tmp_buffer_markers = [boolean_false; BUFFER_SIZE_IN_U64_WORDS]; - tmp_buffer_markers[..(BUFFER_SIZE_IN_U64_WORDS - KECCAK256_RATE_IN_U64_WORDS)] - .copy_from_slice(&state.u64_words_buffer_markers[KECCAK256_RATE_IN_U64_WORDS..]); + if ::DebugConfig::PERFORM_RUNTIME_ASSERTS == true { + use zkevm_opcode_defs::sha3::Digest; + if state.padding_round.witness_hook(cs)().unwrap() == false { + if apply_padding.witness_hook(cs)().unwrap() == true { + let bytes_to_feed = currently_filled.witness_hook(cs)().unwrap(); + let buffer_to_feed = input.witness_hook(cs)().unwrap(); + dbg!(hex::encode(&buffer_to_feed[..(bytes_to_feed as usize)])); + keccak_self_verifier + .as_mut() + .unwrap() + .1 + .update(&buffer_to_feed[..(bytes_to_feed as usize)]); + } else { + let buffer_to_feed = input.witness_hook(cs)().unwrap(); + dbg!(hex::encode(&buffer_to_feed[..])); + keccak_self_verifier + .as_mut() + .unwrap() + .1 + .update(&buffer_to_feed[..]); + } + } else { + // we absorb nothing, and "finalize" will take care of the rest + } + } - // update buffers - state.u8_words_buffer = tmp_buffer; - state.u64_words_buffer_markers = tmp_buffer_markers; + let mut tmp = currently_filled.into_num(); + let pad_constant = UInt8::allocated_constant(cs, 0x01); + for dst in input[..(KECCAK_RATE_BYTES - 1)].iter_mut() { + let pad_this_byte = tmp.is_zero(cs); + let apply_padding = Boolean::multi_and(cs, &[apply_padding, pad_this_byte]); + *dst = UInt8::conditionally_select(cs, apply_padding, &pad_constant, &*dst); + tmp = tmp.sub(cs, &one_num); + } - // conditionally reset state. Keccak256 empty state is just all 0s + let normal_last_byte_padding_value = UInt8::allocated_constant(cs, 0x80); + let special_last_byte_paddings_value = UInt8::allocated_constant(cs, 0x81); + let last_byte_padding_value = UInt8::conditionally_select( + cs, + do_one_byte_of_padding, + &special_last_byte_paddings_value, + &normal_last_byte_padding_value, + ); + input[KECCAK_RATE_BYTES - 1] = UInt8::conditionally_select( + cs, + apply_padding, + &last_byte_padding_value, + &input[KECCAK_RATE_BYTES - 1], + ); - for dst in state.keccak_internal_state.iter_mut() { - for dst in dst.iter_mut() { - for dst in dst.iter_mut() { - *dst = dst.mask_negated(cs, reset_buffer); - } - } + let input = + UInt8::::parallel_select(cs, state.padding_round, &full_padding_buffer, &input); + if crate::config::CIRCUIT_VERSOBE { + dbg!(input.witness_hook(cs)().map(|el| hex::encode(&el))); } // manually absorb and run round function let squeezed = keccak256_absorb_and_run_permutation(cs, &mut state.keccak_internal_state, &input); - let no_rounds_left = state.precompile_call_params.num_rounds.is_zero(cs); - let write_result = - Boolean::multi_and(cs, &[state.read_unaligned_words_for_round, no_rounds_left]); + let absorbed_and_padded = apply_padding; + // dbg!(absorbed_and_padded.witness_hook(cs)()); + // dbg!(state.padding_round.witness_hook(cs)()); + let finished_processing_current_request = + Boolean::multi_or(cs, &[absorbed_and_padded, state.padding_round]); + let write_result = finished_processing_current_request; + + if ::DebugConfig::PERFORM_RUNTIME_ASSERTS == true { + use zkevm_opcode_defs::sha3::Digest; + if write_result.witness_hook(cs)().unwrap() == true { + if keccak_self_verifier.as_mut().unwrap().0 == true { + keccak_self_verifier.as_mut().unwrap().0 = false; + let mut output = [0u8; 32]; + let internal_state = std::mem::replace( + &mut keccak_self_verifier.as_mut().unwrap().1, + zkevm_opcode_defs::sha3::Keccak256::new(), + ); + output.copy_from_slice(internal_state.finalize().as_slice()); + let circuit_result = squeezed.witness_hook(cs)().unwrap(); + assert_eq!(output, circuit_result); + } + } + } let result = UInt256::from_be_bytes(cs, squeezed); + if crate::config::CIRCUIT_VERSOBE { + if finished_processing_current_request.witness_hook(cs)().unwrap() { + dbg!(result.witness_hook(cs)()); + } + } let write_query = MemoryQuery { timestamp: state.timestamp_to_use_for_write, memory_page: state.precompile_call_params.output_page, - index: state.precompile_call_params.output_offset, + index: state.precompile_call_params.output_word_offset, rw_flag: boolean_true, is_ptr: boolean_false, value: result, @@ -387,7 +631,7 @@ where // --------------------------------- - // update call props + // update FSM state let input_is_empty = precompile_calls_queue.is_empty(cs); let input_is_not_empty = input_is_empty.negated(cs); let nothing_left = Boolean::multi_and(cs, &[write_result, input_is_empty]); @@ -395,7 +639,28 @@ where state.read_precompile_call = process_next; state.completed = Boolean::multi_or(cs, &[nothing_left, state.completed]); - let t = Boolean::multi_or(cs, &[state.read_precompile_call, state.completed]); + + // now we need to decide on full padding round + let needs_full_padding = Boolean::multi_and( + cs, + &[ + state.read_unaligned_words_for_round, + zero_bytes_left, + buffer_now_empty, + state.precompile_call_params.needs_full_padding_round, + ], + ); + state.padding_round = needs_full_padding; + + // otherwise we just continue + let t = Boolean::multi_or( + cs, + &[ + state.read_precompile_call, + state.padding_round, + state.completed, + ], + ); state.read_unaligned_words_for_round = t.negated(cs); } @@ -571,3 +836,310 @@ pub(crate) fn keccak256_absorb_and_run_permutation CSReferenceImplementation< + GoldilocksField, + GoldilocksField, + DevCSConfig, + impl GateConfigurationHolder, + impl StaticToolboxHolder, + > { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + fn configure< + T: CsBuilderImpl, + GC: GateConfigurationHolder, + TB: StaticToolboxHolder, + >( + builder: CsBuilder, + ) -> CsBuilder, impl StaticToolboxHolder> { + let builder = builder.allow_lookup( + LookupParameters::UseSpecializedColumnsWithTableIdAsConstant { + width: 3, + num_repetitions: 8, + share_table_id: true, + }, + ); + let builder = ConstantsAllocatorGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = FmaGateInBaseFieldWithoutConstant::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = ReductionGate::::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = BooleanConstraintGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = UIntXAddGate::<32>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = UIntXAddGate::<16>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = UIntXAddGate::<8>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = SelectionGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = ZeroCheckGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + false, + ); + let builder = DotProductGate::<4>::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let builder = MatrixMultiplicationGate::::configure_builder(builder,GatePlacementStrategy::UseGeneralPurposeColumns); + let builder = MatrixMultiplicationGate::::configure_builder(builder,GatePlacementStrategy::UseGeneralPurposeColumns); + let builder = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); + use boojum::cs::cs_builder::new_builder; + let builder = new_builder::<_, F>(builder_impl); + + let builder = configure(builder); + let mut owned_cs = builder.build(1 << 26); + + // add tables for keccak + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let table = create_and8_table(); + owned_cs.add_lookup_table::(table); + + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + + owned_cs + } + + fn bytes_to_u256_words(input: Vec, unalignement: usize) -> Vec { + let mut result = vec![]; + let mut it = std::iter::repeat(0xffu8) + .take(unalignement) + .chain(input.into_iter()); + 'outer: loop { + let mut done = false; + let mut buffer = [0u8; 32]; + for (idx, dst) in buffer.iter_mut().enumerate() { + if let Some(src) = it.next() { + *dst = src; + } else { + done = true; + if idx == 0 { + break 'outer; + } + break; + } + } + let el = U256::from_big_endian(&buffer); + result.push(el); + if done { + break 'outer; + } + } + + result + } + + fn test_for_length_and_unalignment(length: usize, unalignement: usize) { + use rand_new::{Rng, SeedableRng}; + let mut rng = rand_new::rngs::StdRng::from_seed([1u8; 32]); + let input: Vec = (0..length).map(|_| rng.gen()).collect(); + dbg!(hex::encode(&input)); + let input_witness = bytes_to_u256_words(input.clone(), unalignement); + + use boojum::sha3::Digest; + let reference: [u8; 32] = boojum::sha3::Keccak256::digest(&input) + .as_slice() + .try_into() + .unwrap(); + + let mut owned_cs = create_test_cs(); + let cs = &mut owned_cs; + let mut memory_queue = MemoryQueue::::empty(cs); + + let precompile_abi = PrecompileCallABI { + input_memory_offset: unalignement as u32, + input_memory_length: length as u32, + output_memory_offset: 0, + output_memory_length: 1, + memory_page_to_read: 123, + memory_page_to_write: 456, + precompile_interpreted_data: 0, + }; + let encoded_precompile_abi = precompile_abi.to_u256(); + let boolean_true = Boolean::allocated_constant(cs, true); + + let mut precompile_calls_queue = StorageLogQueue::::empty(cs); + let el = LogQueryWitness { + address: *zkevm_opcode_defs::system_params::KECCAK256_ROUND_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + key: encoded_precompile_abi, + read_value: U256::zero(), + written_value: U256::zero(), + aux_byte: PRECOMPILE_AUX_BYTE, + rw_flag: true, + rollback: false, + is_service: false, + shard_id: 0, + tx_number_in_block: 0, + timestamp: 0, + }; + let el = LogQuery::allocate(cs, el); + precompile_calls_queue.push(cs, el, boolean_true); + + let mut state = Keccak256RoundFunctionFSM::placeholder_witness(); + state.read_precompile_call = true; + state.timestamp_to_use_for_read = 1; + state.timestamp_to_use_for_write = 2; + + let state = Keccak256RoundFunctionFSM::allocate(cs, state); + let round_function = Poseidon2Goldilocks; + + let memory_read_witness = ConditionalWitnessAllocator::> { + witness_source: std::sync::Arc::new(std::sync::RwLock::new(input_witness.into())), + }; + + let new_state = keccak256_precompile_inner( + cs, + &mut memory_queue, + &mut precompile_calls_queue, + memory_read_witness, + state, + &round_function, + 2, + ); + + dbg!(new_state.witness_hook(cs)().unwrap()); + + drop(cs); + + let output = memory_queue + .witness + .elements + .read() + .unwrap() + .back() + .unwrap() + .clone(); + let mut buffer = [0u8; 32]; + assert!(output.0.rw_flag); + output.0.value.to_big_endian(&mut buffer); + + dbg!(hex::encode(&reference)); + dbg!(hex::encode(&buffer)); + + assert_eq!(buffer, reference); + + let _ = owned_cs.pad_and_shrink(); + let mut assembly = owned_cs.into_assembly::(); + let worker = Worker::new(); + let is_satisfied = assembly.check_if_satisfied(&worker); + assert!(is_satisfied); + } + + #[test] + fn keccak_256_aligned_one_round() { + test_for_length_and_unalignment(50, 0); + } + + #[test] + fn keccak_256_aligned_one_round_to_the_end() { + test_for_length_and_unalignment(135, 0); + } + + #[test] + fn keccak_256_aligned_two_rounds() { + test_for_length_and_unalignment(200, 0); + } + + #[test] + fn keccak_256_aligned_two_rounds_but_one_read_round() { + test_for_length_and_unalignment(180, 0); + } + + #[test] + fn keccak_256_aligned_one_round_and_padding_round() { + test_for_length_and_unalignment(136, 0); + } + + #[test] + fn keccak_256_unaligned_one_round() { + test_for_length_and_unalignment(50, 31); + } + + #[test] + fn keccak_256_unaligned_one_round_to_the_end() { + test_for_length_and_unalignment(135, 31); + } + + #[test] + fn keccak_256_unaligned_one_round_and_padding_round() { + test_for_length_and_unalignment(136, 31); + } + + #[test] + fn keccak_256_unaligned_two_rounds() { + test_for_length_and_unalignment(200, 31); + } + + #[test] + fn keccak_256_unaligned_two_rounds_but_one_read_round() { + test_for_length_and_unalignment(166, 22); + } +} diff --git a/crates/zkevm_circuits/src/lib.rs b/crates/zkevm_circuits/src/lib.rs index ddc2dcf..fe4420c 100644 --- a/crates/zkevm_circuits/src/lib.rs +++ b/crates/zkevm_circuits/src/lib.rs @@ -5,6 +5,7 @@ #![feature(generic_const_exprs)] #![feature(array_chunks)] #![feature(more_qualified_paths)] +#![feature(allocator_api)] use derivative::*; @@ -17,6 +18,7 @@ pub mod base_structures; pub mod code_unpacker_sha256; pub mod demux_log_queue; pub mod ecrecover; +pub mod eip_4844; pub mod fsm_input_output; pub mod keccak256_round_function; pub mod linear_hasher; diff --git a/crates/zkevm_circuits/src/log_sorter/mod.rs b/crates/zkevm_circuits/src/log_sorter/mod.rs index b29ccc9..be217ba 100644 --- a/crates/zkevm_circuits/src/log_sorter/mod.rs +++ b/crates/zkevm_circuits/src/log_sorter/mod.rs @@ -470,6 +470,8 @@ pub fn prepacked_long_comparison>( #[cfg(test)] mod tests { + use std::alloc::Global; + use super::*; use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; use boojum::cs::implementations::reference_cs::CSDevelopmentAssembly; @@ -563,12 +565,12 @@ mod tests { use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; let builder_impl = - CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 1 << 20); + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); use boojum::cs::cs_builder::new_builder; let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); // add tables let table = create_xor8_table(); @@ -628,7 +630,7 @@ mod tests { cs.pad_and_shrink(); let worker = Worker::new(); - let mut owned_cs = owned_cs.into_assembly(); + let mut owned_cs = owned_cs.into_assembly::(); owned_cs.print_gate_stats(); assert!(owned_cs.check_if_satisfied(&worker)); } diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/far_call.rs b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/far_call.rs index 125ee24..094a864 100644 --- a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/far_call.rs +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/far_call.rs @@ -1007,6 +1007,15 @@ where let new_r1 = final_fat_ptr.into_register(cs); + if crate::config::CIRCUIT_VERSOBE { + if (execute.witness_hook(&*cs))().unwrap_or(false) { + println!( + "R1 value after far call is {:?}", + new_r1.witness_hook(cs)().unwrap() + ); + } + } + let one = Num::allocated_constant(cs, F::ONE); let r2_low = Num::fma( diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/ret.rs b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/ret.rs index 6f9773e..f33bb2e 100644 --- a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/ret.rs +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/ret.rs @@ -98,6 +98,10 @@ where } } + // on panic, we should never return any data. in this case, zero out src0 data + let mut src0 = common_opcode_state.src0.clone(); + src0.conditionally_erase(cs, is_ret_panic); + let current_callstack_entry = draft_vm_state.callstack.current_context.saved_context; // we may want to return to label @@ -172,7 +176,7 @@ where let mut non_local_frame_exceptions = ArrayVec::, 4>::new(); let forward_fat_pointer = forwarding_data.forward_fat_pointer; - let src0_is_integer = common_opcode_state.src0_view.is_ptr.negated(cs); + let src0_is_integer = src0.is_pointer.negated(cs); let is_far_return = is_local_frame.negated(cs); // resolve returndata pointer if forwarded @@ -294,6 +298,18 @@ where ergs_left_after_growth.add_no_overflow(cs, new_callstack_entry.ergs_remaining); new_callstack_entry.ergs_remaining = new_ergs_left; + new_callstack_entry.heap_upper_bound = Selectable::conditionally_select( + cs, + is_local_frame, + &heap_bound, + &new_callstack_entry.heap_upper_bound, + ); + new_callstack_entry.aux_heap_upper_bound = Selectable::conditionally_select( + cs, + is_local_frame, + &aux_heap_bound, + &new_callstack_entry.aux_heap_upper_bound, + ); // resolve merging of the queues diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/log.rs b/crates/zkevm_circuits/src/main_vm/opcodes/log.rs index 83b3338..f3aaf83 100644 --- a/crates/zkevm_circuits/src/main_vm/opcodes/log.rs +++ b/crates/zkevm_circuits/src/main_vm/opcodes/log.rs @@ -107,18 +107,22 @@ pub(crate) fn apply_log< }; // modify the key by replacing parts for precompile call + let read_page_is_zero = key.inner[4].is_zero(cs); + let write_page_is_zero = key.inner[5].is_zero(cs); let precompile_memory_page_to_read = opcode_carry_parts.heap_page; let precompile_memory_page_to_write = opcode_carry_parts.heap_page; + let should_swap_read_page = Boolean::multi_and(cs, &[read_page_is_zero, is_precompile]); + let should_swap_write_page = Boolean::multi_and(cs, &[write_page_is_zero, is_precompile]); // replace bits 128..160 and 160..192 key.inner[4] = UInt32::conditionally_select( cs, - is_precompile, + should_swap_read_page, &precompile_memory_page_to_read, &key.inner[4], ); key.inner[5] = UInt32::conditionally_select( cs, - is_precompile, + should_swap_write_page, &precompile_memory_page_to_write, &key.inner[5], ); diff --git a/crates/zkevm_circuits/src/main_vm/pre_state.rs b/crates/zkevm_circuits/src/main_vm/pre_state.rs index ff7ecc9..fb4c505 100644 --- a/crates/zkevm_circuits/src/main_vm/pre_state.rs +++ b/crates/zkevm_circuits/src/main_vm/pre_state.rs @@ -448,8 +448,38 @@ pub fn create_prestate< let selected_src0 = src0; let selected_src1 = src1_register; - let src0 = VMRegister::conditionally_select(cs, swap_operands, &selected_src1, &selected_src0); - let src1 = VMRegister::conditionally_select(cs, swap_operands, &selected_src0, &selected_src1); + let mut src0 = + VMRegister::conditionally_select(cs, swap_operands, &selected_src1, &selected_src0); + let mut src1 = + VMRegister::conditionally_select(cs, swap_operands, &selected_src0, &selected_src1); + + // Potentially erase fat pointer data if opcode shouldn't take pointers and we're not in kernel + // mode + let not_kernel_mode = is_kernel_mode.negated(cs); + let should_erase_src0_ptr_data = { + use zkevm_opcode_defs::*; + let is_ret = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::Ret(RetOpcode::Ok)); + let is_ptr = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::Ptr(PtrOpcode::Add)); + let is_uma = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::UMA(UMAOpcode::AuxHeapRead)); + let is_far_call = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::FarCall(FarCallOpcode::Delegate)); + + let should_erase = + Boolean::multi_or(cs, &[is_ret, is_ptr, is_uma, is_far_call]).negated(cs); + Boolean::multi_and(cs, &[src0.is_pointer, should_erase, not_kernel_mode]) + }; + // We erase fat pointer data from src1 if it exists in non-kernel mode + let should_erase_src1_ptr_data = Boolean::multi_and(cs, &[src1.is_pointer, not_kernel_mode]); + + src0.conditionally_erase_fat_pointer_data(cs, should_erase_src0_ptr_data); + src1.conditionally_erase_fat_pointer_data(cs, should_erase_src1_ptr_data); let src0_view = RegisterInputView::from_input_value(cs, &src0); let src1_view = RegisterInputView::from_input_value(cs, &src1); diff --git a/crates/zkevm_circuits/src/main_vm/utils.rs b/crates/zkevm_circuits/src/main_vm/utils.rs index 9463d6e..cf04d7b 100644 --- a/crates/zkevm_circuits/src/main_vm/utils.rs +++ b/crates/zkevm_circuits/src/main_vm/utils.rs @@ -356,7 +356,7 @@ pub fn resolve_memory_region_and_index_for_dest, const N: usize #[cfg(test)] mod tests { + use std::alloc::Global; + use super::*; use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; use boojum::cs::gates::*; @@ -487,12 +489,12 @@ mod tests { use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; let builder_impl = - CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 1 << 20); + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); use boojum::cs::cs_builder::new_builder; let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); // add tables let table = create_xor8_table(); @@ -549,7 +551,7 @@ mod tests { cs.pad_and_shrink(); let worker = Worker::new(); - let mut owned_cs = owned_cs.into_assembly(); + let mut owned_cs = owned_cs.into_assembly::(); owned_cs.print_gate_stats(); assert!(owned_cs.check_if_satisfied(&worker)); } diff --git a/crates/zkevm_circuits/src/scheduler/auxiliary.rs b/crates/zkevm_circuits/src/scheduler/auxiliary.rs index 08c66b2..39ec706 100644 --- a/crates/zkevm_circuits/src/scheduler/auxiliary.rs +++ b/crates/zkevm_circuits/src/scheduler/auxiliary.rs @@ -14,6 +14,7 @@ use boojum::gadgets::traits::round_function::CircuitRoundFunction; use boojum::gadgets::{boolean::Boolean, num::Num, queue::*, traits::selectable::Selectable}; use crate::base_structures::precompile_input_outputs::*; +use crate::eip_4844::input::EIP4844OutputData; use crate::log_sorter::input::*; use crate::storage_application::input::*; use boojum::gadgets::u8::UInt8; diff --git a/crates/zkevm_circuits/src/scheduler/block_header/mod.rs b/crates/zkevm_circuits/src/scheduler/block_header/mod.rs index c54d2a6..011593c 100644 --- a/crates/zkevm_circuits/src/scheduler/block_header/mod.rs +++ b/crates/zkevm_circuits/src/scheduler/block_header/mod.rs @@ -20,6 +20,7 @@ use boojum::serde_utils::BigArraySerde; use boojum::gadgets::keccak256; pub const NUM_SHARDS: usize = 2; +pub const MAX_4844_BLOBS_PER_BLOCK: usize = 2; // Data that represents a pure state #[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] @@ -54,6 +55,8 @@ pub struct BlockAuxilaryOutput { pub rollup_state_diff_for_compression: [UInt8; 32], pub bootloader_heap_initial_content: [UInt8; 32], pub events_queue_state: [UInt8; 32], + pub eip4844_linear_hashes: [[UInt8; 32]; MAX_4844_BLOBS_PER_BLOCK], + pub eip4844_output_commitment_hashes: [[UInt8; 32]; MAX_4844_BLOBS_PER_BLOCK], } #[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] @@ -123,6 +126,14 @@ impl BlockAuxilaryOutput { result.extend_from_slice(&self.rollup_state_diff_for_compression); result.extend_from_slice(&self.bootloader_heap_initial_content); result.extend_from_slice(&self.events_queue_state); + for (linear_hash, blob_opening_commitment) in self + .eip4844_linear_hashes + .iter() + .zip(self.eip4844_output_commitment_hashes.iter()) + { + result.extend_from_slice(linear_hash); + result.extend_from_slice(blob_opening_commitment); + } result } diff --git a/crates/zkevm_circuits/src/scheduler/input.rs b/crates/zkevm_circuits/src/scheduler/input.rs index 1263b63..7d4f314 100644 --- a/crates/zkevm_circuits/src/scheduler/input.rs +++ b/crates/zkevm_circuits/src/scheduler/input.rs @@ -74,6 +74,10 @@ pub struct SchedulerCircuitInstanceWitness< pub previous_block_meta_hash: [u8; 32], pub previous_block_aux_hash: [u8; 32], + // eip4844 witnesses + pub eip4844_witnesses: Option<[EIP4844OutputDataWitness; MAX_4844_BLOBS_PER_BLOCK]>, + pub eip4844_proofs: VecDeque>, + // proofs for every individual circuit type's aggregation subtree #[derivative(Debug = "ignore")] pub proof_witnesses: VecDeque>, @@ -122,6 +126,9 @@ impl>, EXT: FieldExtension<2, Ba previous_block_meta_hash: [0u8; 32], previous_block_aux_hash: [0u8; 32], + eip4844_witnesses: None, + eip4844_proofs: VecDeque::new(), + proof_witnesses: VecDeque::new(), node_layer_vk_witness: VerificationKey::default(), leaf_layer_parameters: std::array::from_fn(|_| { diff --git a/crates/zkevm_circuits/src/scheduler/mod.rs b/crates/zkevm_circuits/src/scheduler/mod.rs index 817bf82..36dfb5c 100644 --- a/crates/zkevm_circuits/src/scheduler/mod.rs +++ b/crates/zkevm_circuits/src/scheduler/mod.rs @@ -11,6 +11,7 @@ pub use auxiliary as aux; use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::verifier::VerificationKey; use boojum::cs::traits::cs::ConstraintSystem; use boojum::field::SmallField; @@ -36,6 +37,7 @@ use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; use crate::linear_hasher::input::LinearHasherOutputData; use crate::recursion::VK_COMMITMENT_LENGTH; use crate::scheduler::auxiliary::NUM_CIRCUIT_TYPES_TO_SCHEDULE; +use crate::utils::is_equal_queue_state; use boojum::gadgets::num::Num; use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; @@ -56,6 +58,7 @@ use std::collections::HashMap; use crate::base_structures::vm_state::*; use crate::code_unpacker_sha256::input::*; use crate::demux_log_queue::input::*; +use crate::eip_4844::input::*; use crate::fsm_input_output::circuit_inputs::main_vm::*; use crate::fsm_input_output::*; use crate::log_sorter::input::*; @@ -71,6 +74,7 @@ pub const SCHEDULER_TIMESTAMP: u32 = 1; pub const NUM_SCHEDULER_PUBLIC_INPUTS: usize = 4; pub const LEAF_LAYER_PARAMETERS_COMMITMENT_LENGTH: usize = 4; pub const QUEUE_FINAL_STATE_COMMITMENT_LENGTH: usize = 4; +pub const IMPLEMENT_4844_FUNCTIONALITY: bool = false; pub const SEQUENCE_OF_CIRCUIT_TYPES: [BaseLayerCircuitType; NUM_CIRCUIT_TYPES_TO_SCHEDULE] = [ BaseLayerCircuitType::VM, @@ -115,12 +119,17 @@ pub fn scheduler_function< TransciptParameters = TR::TransciptParameters, >, POW: RecursivePoWRunner, + const USE_4844: bool, >( cs: &mut CS, mut witness: SchedulerCircuitInstanceWitness, round_function: &R, config: SchedulerConfig, verifier_builder: Box>, + eip4844_proof_config: Option, + eip4844_vk_fixed_parameters: Option, + eip4844_vk: Option>, + eip4844_verifier_builder: Option>>, transcript_params: TR::TransciptParameters, ) where [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, @@ -566,48 +575,112 @@ pub fn scheduler_function< // we can potentially skip some circuits let mut skip_flags = [None; NUM_CIRCUIT_TYPES_TO_SCHEDULE]; // we can skip everything except VM - skip_flags[(BaseLayerCircuitType::DecommitmentsFilter as u8 as usize) - 1] = Some( - decommittments_sorter_circuit_input + // and if we skip, then we should ensure some invariants over outputs! + + // decommits sorter must output empty queue + { + let should_skip = decommittments_sorter_circuit_input .initial_queue_state .tail .length - .is_zero(cs), - ); - skip_flags[(BaseLayerCircuitType::Decommiter as u8 as usize) - 1] = Some( - code_decommitter_circuit_input + .is_zero(cs); + + let output_queue_is_empty = decommits_sorter_observable_output + .final_queue_state + .tail + .length + .is_zero(cs); + output_queue_is_empty.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::DecommitmentsFilter as u8 as usize) - 1] = + Some(should_skip); + } + + // decommitter should produce the same memory sequence + { + let should_skip = code_decommitter_circuit_input .sorted_requests_queue_initial_state .tail .length - .is_zero(cs), - ); - skip_flags[(BaseLayerCircuitType::LogDemultiplexer as u8 as usize) - 1] = Some( - log_demux_circuit_input + .is_zero(cs); + + let input_state = code_decommitter_circuit_input.memory_queue_initial_state; + let output_state = code_decommitter_observable_output.memory_queue_final_state; + + let same_state = is_equal_queue_state(cs, &input_state, &output_state); + same_state.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::Decommiter as u8 as usize) - 1] = Some(should_skip); + } + + // demux must produce empty outputs + { + let should_skip = log_demux_circuit_input .initial_log_queue_state .tail .length - .is_zero(cs), - ); - skip_flags[(BaseLayerCircuitType::KeccakPrecompile as u8 as usize) - 1] = Some( - log_demuxer_observable_output + .is_zero(cs); + + for subqueue in log_demuxer_observable_output + .all_output_queues_refs() + .into_iter() + { + let output_queue_is_empty = subqueue.tail.length.is_zero(cs); + output_queue_is_empty.conditionally_enforce_true(cs, should_skip); + } + + skip_flags[(BaseLayerCircuitType::LogDemultiplexer as u8 as usize) - 1] = Some(should_skip); + } + + // keccak, sha256 and ecrecover must not modify memory + { + let should_skip = log_demuxer_observable_output .keccak256_access_queue_state .tail .length - .is_zero(cs), - ); - skip_flags[(BaseLayerCircuitType::Sha256Precompile as u8 as usize) - 1] = Some( - log_demuxer_observable_output + .is_zero(cs); + + let input_state = code_decommitter_observable_output.memory_queue_final_state; + let output_state = keccak256_observable_output.final_memory_state; + + let same_state = is_equal_queue_state(cs, &input_state, &output_state); + same_state.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::KeccakPrecompile as u8 as usize) - 1] = Some(should_skip); + } + { + let should_skip = log_demuxer_observable_output .sha256_access_queue_state .tail .length - .is_zero(cs), - ); - skip_flags[(BaseLayerCircuitType::EcrecoverPrecompile as u8 as usize) - 1] = Some( - log_demuxer_observable_output + .is_zero(cs); + + let input_state = keccak256_observable_output.final_memory_state; + let output_state = sha256_observable_output.final_memory_state; + + let same_state = is_equal_queue_state(cs, &input_state, &output_state); + same_state.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::Sha256Precompile as u8 as usize) - 1] = Some(should_skip); + } + { + let should_skip = log_demuxer_observable_output .ecrecover_access_queue_state .tail .length - .is_zero(cs), - ); + .is_zero(cs); + + let input_state = sha256_observable_output.final_memory_state; + let output_state = ecrecover_observable_output.final_memory_state; + + let same_state = is_equal_queue_state(cs, &input_state, &output_state); + same_state.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::EcrecoverPrecompile as u8 as usize) - 1] = + Some(should_skip); + } + + // well, in the very unlikely case of no RAM requests (that is unreachable because VM always starts) we just skip it as is skip_flags[(BaseLayerCircuitType::RamValidation as u8 as usize) - 1] = Some( ram_validation_circuit_input .unsorted_queue_initial_state @@ -615,24 +688,93 @@ pub fn scheduler_function< .length .is_zero(cs), ); - skip_flags[(BaseLayerCircuitType::StorageFilter as u8 as usize) - 1] = - Some(storage_queues_state[0].tail.length.is_zero(cs)); - skip_flags[(BaseLayerCircuitType::StorageApplicator as u8 as usize) - 1] = - Some(filtered_storage_queues_state[0].tail.length.is_zero(cs)); - skip_flags[(BaseLayerCircuitType::EventsRevertsFilter as u8 as usize) - 1] = Some( - log_demuxer_observable_output + // storage filter must produce an empty output + { + let should_skip = storage_queues_state[0].tail.length.is_zero(cs); + + let output_queue_is_empty = filtered_storage_queues_state[0].tail.length.is_zero(cs); + output_queue_is_empty.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::StorageFilter as u8 as usize) - 1] = Some(should_skip); + } + // storage application must leave root untouched + { + let should_skip = filtered_storage_queues_state[0].tail.length.is_zero(cs); + + let initial_root = initial_state_roots[0]; + let initial_enumeration_counter = initial_enumeration_counters[0]; + let final_root = final_state_roots[0]; + let final_enumeration_counter = final_enumeration_counters[0]; + + let diffs_hash = storage_diffs_for_compression[0]; + + let root_parts_are_equal: [Boolean; 32] = + std::array::from_fn(|i| UInt8::equals(cs, &initial_root[i], &final_root[i])); + let roots_are_equal = Boolean::multi_and(cs, &root_parts_are_equal); + + let enumeration_counters_are_equal_low = UInt32::equals( + cs, + &initial_enumeration_counter[0], + &final_enumeration_counter[0], + ); + let enumeration_counters_are_equal_high = UInt32::equals( + cs, + &initial_enumeration_counter[1], + &final_enumeration_counter[1], + ); + + let diffs_parts_are_zero: [Boolean; 32] = diffs_hash.map(|el| el.is_zero(cs)); + let diffs_hash_is_zero = Boolean::multi_and(cs, &diffs_parts_are_zero); + + let root_is_unchanged = Boolean::multi_and( + cs, + &[ + roots_are_equal, + enumeration_counters_are_equal_low, + enumeration_counters_are_equal_high, + diffs_hash_is_zero, + ], + ); + root_is_unchanged.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::StorageApplicator as u8 as usize) - 1] = + Some(should_skip); + } + // events and l2 to l1 messages filters should produce empty output + { + let should_skip = log_demuxer_observable_output .events_access_queue_state .tail .length - .is_zero(cs), - ); - skip_flags[(BaseLayerCircuitType::L1MessagesRevertsFilter as u8 as usize) - 1] = Some( - log_demuxer_observable_output + .is_zero(cs); + + let output_queue_is_empty = events_sorter_observable_output + .final_queue_state + .tail + .length + .is_zero(cs); + output_queue_is_empty.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::EventsRevertsFilter as u8 as usize) - 1] = + Some(should_skip); + } + { + let should_skip = log_demuxer_observable_output .l1messages_access_queue_state .tail .length - .is_zero(cs), - ); + .is_zero(cs); + + let output_queue_is_empty = l1messages_sorter_observable_output + .final_queue_state + .tail + .length + .is_zero(cs); + output_queue_is_empty.conditionally_enforce_true(cs, should_skip); + + skip_flags[(BaseLayerCircuitType::L1MessagesRevertsFilter as u8 as usize) - 1] = + Some(should_skip); + } // for (idx, el) in skip_flags.iter().enumerate() { // if let Some(el) = el { @@ -978,12 +1120,93 @@ pub fn scheduler_function< dst.reverse(); } + let (eip4844_linear_hashes, eip4844_output_commitment_hashes) = if USE_4844 { + // eip4844 circuit + let verifier_builder = + eip4844_verifier_builder.expect("if EIP 4844 is used then builder must be provided"); + let verifier = verifier_builder.create_recursive_verifier(cs); + let proof_config = + eip4844_proof_config.expect("if EIP 4844 is used then proof config must be provided"); + let vk_fixed_parameters = eip4844_vk_fixed_parameters + .expect("if EIP 4844 is used then vk fixed parameters must be provided"); + let vk = eip4844_vk.expect("if EIP 4844 is used then VK must be provided"); + + let eip4844_vk = AllocatedVerificationKey::::allocate_constant(cs, vk); + + let mut eip4844_linear_hashes = [[zero_u8; 32]; MAX_4844_BLOBS_PER_BLOCK]; + let mut eip4844_output_commitment_hashes = [[zero_u8; 32]; MAX_4844_BLOBS_PER_BLOCK]; + for i in 0..MAX_4844_BLOBS_PER_BLOCK { + let observable_output_data_witness = witness + .eip4844_witnesses + .as_ref() + .expect("if EIP 4844 is used then witness must be provided") + .get(i) + .cloned() + .unwrap_or(EIP4844OutputData::placeholder_witness()); + let observable_output_data = + EIP4844OutputData::allocate(cs, observable_output_data_witness); + let zeroes = observable_output_data.linear_hash.map(|el| el.is_zero(cs)); + let skip_verification = Boolean::multi_and(cs, &zeroes); + let should_verify = skip_verification.negated(cs); + let structured_input = EIP4844InputOutput { + start_flag: boolean_true, + completion_flag: boolean_true, + observable_input: (), + observable_output: observable_output_data, + hidden_fsm_input: (), + hidden_fsm_output: (), + }; + + let closed_form_input = + ClosedFormInputCompactForm::from_full_form(cs, &structured_input, round_function); + let expected_input_commitment: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &closed_form_input, round_function); + + let proof_witness = witness.eip4844_proofs.pop_front(); + + let proof = AllocatedProof::allocate_from_witness( + cs, + proof_witness, + &verifier, + &vk_fixed_parameters, + &proof_config, + ); + + let (is_valid, inputs) = verifier.verify::( + cs, + transcript_params.clone(), + &proof, + &vk_fixed_parameters, + &proof_config, + &eip4844_vk, + ); + is_valid.conditionally_enforce_true(cs, should_verify); + assert_eq!(inputs.len(), expected_input_commitment.len()); + + for (a, b) in inputs.iter().zip(expected_input_commitment.iter()) { + Num::conditionally_enforce_equal(cs, should_verify, a, b); + } + + eip4844_linear_hashes[i] = observable_output_data.linear_hash; + eip4844_output_commitment_hashes[i] = observable_output_data.output_hash; + } + + (eip4844_linear_hashes, eip4844_output_commitment_hashes) + } else { + ( + [[zero_u8; 32]; MAX_4844_BLOBS_PER_BLOCK], + [[zero_u8; 32]; MAX_4844_BLOBS_PER_BLOCK], + ) + }; + let aux_data = BlockAuxilaryOutput { rollup_state_diff_for_compression: storage_application_observable_output .state_diffs_keccak256_hash, bootloader_heap_initial_content, events_queue_state, l1_messages_linear_hash: l1messages_linear_hasher_observable_output.keccak256_hash, + eip4844_linear_hashes: eip4844_linear_hashes, + eip4844_output_commitment_hashes: eip4844_output_commitment_hashes, }; let block_content_header = BlockContentHeader { diff --git a/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs b/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs index 9c5b51b..4b9950d 100644 --- a/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs +++ b/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs @@ -398,6 +398,8 @@ fn concatenate_key>( #[cfg(test)] mod tests { + use std::alloc::Global; + use super::*; use crate::ethereum_types::U256; use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; @@ -489,12 +491,12 @@ mod tests { use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; let builder_impl = - CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 1 << 20); + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); use boojum::cs::cs_builder::new_builder; let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); // add tables let table = create_xor8_table(); @@ -556,7 +558,7 @@ mod tests { cs.pad_and_shrink(); let worker = Worker::new(); - let mut owned_cs = owned_cs.into_assembly(); + let mut owned_cs = owned_cs.into_assembly::(); owned_cs.print_gate_stats(); assert!(owned_cs.check_if_satisfied(&worker)); } diff --git a/crates/zkevm_circuits/src/storage_validity_by_grand_product/mod.rs b/crates/zkevm_circuits/src/storage_validity_by_grand_product/mod.rs index 356c481..9dee43f 100644 --- a/crates/zkevm_circuits/src/storage_validity_by_grand_product/mod.rs +++ b/crates/zkevm_circuits/src/storage_validity_by_grand_product/mod.rs @@ -655,8 +655,9 @@ where { let not_keys_are_equal = keys_are_equal.negated(cs); if _cycle == 0 { - // it must always be true if we start - not_keys_are_equal.conditionally_enforce_true(cs, is_start); + // it must always be true if we start and if we have items to work with + let enforce = is_start.and(cs, should_pop); + not_keys_are_equal.conditionally_enforce_true(cs, enforce); } // finish with the old one // if somewhere along the way we did encounter a read at rollback depth zero (not important if there were such), @@ -944,6 +945,8 @@ pub fn unpacked_long_comparison, const N: #[cfg(test)] mod tests { + use std::alloc::Global; + use super::*; use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; use boojum::cs::implementations::reference_cs::{ @@ -1041,12 +1044,12 @@ mod tests { use boojum::cs::cs_builder_reference::*; let builder_impl = - CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 1 << 20); + CsReferenceImplementationBuilder::::new(geometry, 1 << 20); use boojum::cs::cs_builder::new_builder; let builder = new_builder::<_, F>(builder_impl); let builder = configure(builder); - let mut owned_cs = builder.build(()); + let mut owned_cs = builder.build(1 << 26); // add tables let table = create_xor8_table(); @@ -1126,7 +1129,7 @@ mod tests { cs.pad_and_shrink(); let worker = Worker::new(); - let mut owned_cs = owned_cs.into_assembly(); + let mut owned_cs = owned_cs.into_assembly::(); owned_cs.print_gate_stats(); assert!(owned_cs.check_if_satisfied(&worker)); } diff --git a/crates/zkevm_circuits/src/utils.rs b/crates/zkevm_circuits/src/utils.rs index d0356d5..dbf94fe 100644 --- a/crates/zkevm_circuits/src/utils.rs +++ b/crates/zkevm_circuits/src/utils.rs @@ -4,7 +4,7 @@ use boojum::cs::Variable; use boojum::field::SmallField; use boojum::gadgets::boolean::Boolean; use boojum::gadgets::num::Num; -use boojum::gadgets::queue::QueueTailState; +use boojum::gadgets::queue::{QueueState, QueueTailState}; use boojum::gadgets::traits::round_function::CircuitRoundFunction; use boojum::gadgets::traits::selectable::Selectable; use boojum::gadgets::u32::UInt32; @@ -135,3 +135,21 @@ pub fn accumulate_grand_products< *rhs = Num::conditionally_select(cs, should_accumulate, &new_rhs, &rhs); } } + +pub fn is_equal_queue_state, const N: usize>( + cs: &mut CS, + a: &QueueState, + b: &QueueState, +) -> Boolean { + let head_parts_are_equal: [Boolean; N] = + std::array::from_fn(|i| Num::equals(cs, &a.head[i], &b.head[i])); + let heads_are_equal = Boolean::multi_and(cs, &head_parts_are_equal); + + let tail_parts_are_equal: [Boolean; N] = + std::array::from_fn(|i| Num::equals(cs, &a.tail.tail[i], &b.tail.tail[i])); + let tail_are_equal = Boolean::multi_and(cs, &tail_parts_are_equal); + + let lengths_are_equal = UInt32::equals(cs, &a.tail.length, &b.tail.length); + + Boolean::multi_and(cs, &[heads_are_equal, tail_are_equal, lengths_are_equal]) +} diff --git a/crates/zkevm_opcode_defs/Cargo.toml b/crates/zkevm_opcode_defs/Cargo.toml index 10e0c12..ae50bb5 100644 --- a/crates/zkevm_opcode_defs/Cargo.toml +++ b/crates/zkevm_opcode_defs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zkevm_opcode_defs" -version = "0.132.0" +version = "0.141.0" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" @@ -22,7 +22,7 @@ path = "src/circuit_pricing_generator/main.rs" bitflags = "2" lazy_static = "1.4" ethereum-types = "=0.14.1" -sha2 = { package = "sha2_ce", version = "=0.10.6" } -sha3 = { package = "sha3_ce", version = "=0.10.6" } -blake2 = { package = "blake2", version = "=0.10.6" } -k256 = { version = "0.11.6", features = ["arithmetic", "ecdsa"] } +sha2 = "=0.10.8" +sha3 = "=0.10.8" +blake2 = "0.10.*" +k256 = { version = "0.13.*", features = ["arithmetic", "ecdsa"] } diff --git a/crates/zkevm_opcode_defs/deny.toml b/crates/zkevm_opcode_defs/deny.toml index 77d7d23..a957d98 100644 --- a/crates/zkevm_opcode_defs/deny.toml +++ b/crates/zkevm_opcode_defs/deny.toml @@ -72,9 +72,7 @@ skip-tree = [ unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = [ - "https://github.com/RustCrypto/hashes.git", -] +allow-git = [] [sources.allow-org] #github = ["matter-labs"] diff --git a/crates/zkevm_opcode_defs/src/definitions/abi/precompile_call.rs b/crates/zkevm_opcode_defs/src/definitions/abi/precompile_call.rs index 9d62111..b20f24a 100644 --- a/crates/zkevm_opcode_defs/src/definitions/abi/precompile_call.rs +++ b/crates/zkevm_opcode_defs/src/definitions/abi/precompile_call.rs @@ -1,37 +1,9 @@ use super::*; +// Note: offsets and length params below can be byte or word, or in general +// callee's interpreted #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PrecompileCallABI { - pub input_memory_offset: u32, - pub input_memory_length: u32, - pub output_memory_offset: u32, - pub output_memory_length: u32, - pub per_precompile_interpreted: u64, -} - -impl PrecompileCallABI { - pub const fn from_u256(raw_value: U256) -> Self { - let raw = raw_value.0; - let input_memory_offset = raw[0] as u32; - let input_memory_length = (raw[0] >> 32) as u32; - - let output_memory_offset = raw[1] as u32; - let output_memory_length = (raw[1] >> 32) as u32; - - let per_precompile_interpreted = raw[3]; - - Self { - input_memory_offset, - input_memory_length, - output_memory_offset, - output_memory_length, - per_precompile_interpreted, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PrecompileCallInnerABI { pub input_memory_offset: u32, pub input_memory_length: u32, pub output_memory_offset: u32, @@ -41,7 +13,7 @@ pub struct PrecompileCallInnerABI { pub precompile_interpreted_data: u64, } -impl PrecompileCallInnerABI { +impl PrecompileCallABI { pub const fn from_u256(raw_value: U256) -> Self { let raw = raw_value.0; let input_memory_offset = raw[0] as u32; diff --git a/crates/zkevm_opcode_defs/src/definitions/add.rs b/crates/zkevm_opcode_defs/src/definitions/add.rs index c307330..e33f734 100644 --- a/crates/zkevm_opcode_defs/src/definitions/add.rs +++ b/crates/zkevm_opcode_defs/src/definitions/add.rs @@ -68,4 +68,12 @@ impl OpcodeProps for AddOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/all.rs b/crates/zkevm_opcode_defs/src/definitions/all.rs index bde4025..c3a28b2 100644 --- a/crates/zkevm_opcode_defs/src/definitions/all.rs +++ b/crates/zkevm_opcode_defs/src/definitions/all.rs @@ -268,4 +268,46 @@ impl Opcode { Opcode::UMA(sub) => sub.output_operands(version), } } + + pub fn src0_can_be_pointer(&self) -> bool { + match self { + Opcode::Invalid(sub) => sub.src0_can_be_pointer(), + Opcode::Nop(sub) => sub.src0_can_be_pointer(), + Opcode::Add(sub) => sub.src0_can_be_pointer(), + Opcode::Sub(sub) => sub.src0_can_be_pointer(), + Opcode::Mul(sub) => sub.src0_can_be_pointer(), + Opcode::Div(sub) => sub.src0_can_be_pointer(), + Opcode::Jump(sub) => sub.src0_can_be_pointer(), + Opcode::Context(sub) => sub.src0_can_be_pointer(), + Opcode::Shift(sub) => sub.src0_can_be_pointer(), + Opcode::Binop(sub) => sub.src0_can_be_pointer(), + Opcode::Ptr(sub) => sub.src0_can_be_pointer(), + Opcode::NearCall(sub) => sub.src0_can_be_pointer(), + Opcode::Log(sub) => sub.src0_can_be_pointer(), + Opcode::FarCall(sub) => sub.src0_can_be_pointer(), + Opcode::Ret(sub) => sub.src0_can_be_pointer(), + Opcode::UMA(sub) => sub.src0_can_be_pointer(), + } + } + + pub fn src1_can_be_pointer(&self) -> bool { + match self { + Opcode::Invalid(sub) => sub.src1_can_be_pointer(), + Opcode::Nop(sub) => sub.src1_can_be_pointer(), + Opcode::Add(sub) => sub.src1_can_be_pointer(), + Opcode::Sub(sub) => sub.src1_can_be_pointer(), + Opcode::Mul(sub) => sub.src1_can_be_pointer(), + Opcode::Div(sub) => sub.src1_can_be_pointer(), + Opcode::Jump(sub) => sub.src1_can_be_pointer(), + Opcode::Context(sub) => sub.src1_can_be_pointer(), + Opcode::Shift(sub) => sub.src1_can_be_pointer(), + Opcode::Binop(sub) => sub.src1_can_be_pointer(), + Opcode::Ptr(sub) => sub.src1_can_be_pointer(), + Opcode::NearCall(sub) => sub.src1_can_be_pointer(), + Opcode::Log(sub) => sub.src1_can_be_pointer(), + Opcode::FarCall(sub) => sub.src1_can_be_pointer(), + Opcode::Ret(sub) => sub.src1_can_be_pointer(), + Opcode::UMA(sub) => sub.src1_can_be_pointer(), + } + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/binop.rs b/crates/zkevm_opcode_defs/src/definitions/binop.rs index 76884eb..9ff6879 100644 --- a/crates/zkevm_opcode_defs/src/definitions/binop.rs +++ b/crates/zkevm_opcode_defs/src/definitions/binop.rs @@ -72,4 +72,12 @@ impl OpcodeProps for BinopOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/context.rs b/crates/zkevm_opcode_defs/src/definitions/context.rs index 760e79d..897b31b 100644 --- a/crates/zkevm_opcode_defs/src/definitions/context.rs +++ b/crates/zkevm_opcode_defs/src/definitions/context.rs @@ -124,4 +124,12 @@ impl OpcodeProps for ContextOpcode { _ => true, } } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/div.rs b/crates/zkevm_opcode_defs/src/definitions/div.rs index 156cd2b..4f7435d 100644 --- a/crates/zkevm_opcode_defs/src/definitions/div.rs +++ b/crates/zkevm_opcode_defs/src/definitions/div.rs @@ -68,4 +68,12 @@ impl OpcodeProps for DivOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/far_call.rs b/crates/zkevm_opcode_defs/src/definitions/far_call.rs index 7afe829..49151eb 100644 --- a/crates/zkevm_opcode_defs/src/definitions/far_call.rs +++ b/crates/zkevm_opcode_defs/src/definitions/far_call.rs @@ -96,4 +96,12 @@ impl OpcodeProps for FarCallOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + true + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/invalid_opcode.rs b/crates/zkevm_opcode_defs/src/definitions/invalid_opcode.rs index 99c4481..4f3dada 100644 --- a/crates/zkevm_opcode_defs/src/definitions/invalid_opcode.rs +++ b/crates/zkevm_opcode_defs/src/definitions/invalid_opcode.rs @@ -62,4 +62,12 @@ impl OpcodeProps for InvalidOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/jump.rs b/crates/zkevm_opcode_defs/src/definitions/jump.rs index 5348766..28b3ec9 100644 --- a/crates/zkevm_opcode_defs/src/definitions/jump.rs +++ b/crates/zkevm_opcode_defs/src/definitions/jump.rs @@ -62,4 +62,12 @@ impl OpcodeProps for JumpOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/log.rs b/crates/zkevm_opcode_defs/src/definitions/log.rs index f6fead7..5e7fcc2 100644 --- a/crates/zkevm_opcode_defs/src/definitions/log.rs +++ b/crates/zkevm_opcode_defs/src/definitions/log.rs @@ -145,11 +145,13 @@ impl OpcodeProps for LogOpcode { } fn input_operands(&self, _version: ISAVersion) -> Vec { match self { - LogOpcode::StorageWrite | LogOpcode::Event | LogOpcode::ToL1Message => { + LogOpcode::StorageWrite + | LogOpcode::Event + | LogOpcode::ToL1Message + | LogOpcode::PrecompileCall => { vec![Operand::RegOnly, Operand::RegOnly] } LogOpcode::StorageRead => vec![Operand::RegOnly], - LogOpcode::PrecompileCall => vec![Operand::RegOnly], } } fn output_operands(&self, _version: ISAVersion) -> Vec { @@ -171,4 +173,12 @@ impl OpcodeProps for LogOpcode { _ => true, } } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/mul.rs b/crates/zkevm_opcode_defs/src/definitions/mul.rs index 6b7fb39..c237d00 100644 --- a/crates/zkevm_opcode_defs/src/definitions/mul.rs +++ b/crates/zkevm_opcode_defs/src/definitions/mul.rs @@ -68,4 +68,12 @@ impl OpcodeProps for MulOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/near_call.rs b/crates/zkevm_opcode_defs/src/definitions/near_call.rs index 2a941e3..a9f7d30 100644 --- a/crates/zkevm_opcode_defs/src/definitions/near_call.rs +++ b/crates/zkevm_opcode_defs/src/definitions/near_call.rs @@ -62,4 +62,12 @@ impl OpcodeProps for NearCallOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/noop.rs b/crates/zkevm_opcode_defs/src/definitions/noop.rs index 072b8d5..67ea83e 100644 --- a/crates/zkevm_opcode_defs/src/definitions/noop.rs +++ b/crates/zkevm_opcode_defs/src/definitions/noop.rs @@ -62,4 +62,12 @@ impl OpcodeProps for NopOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/opcode_trait.rs b/crates/zkevm_opcode_defs/src/definitions/opcode_trait.rs index a71ee48..c4b010e 100644 --- a/crates/zkevm_opcode_defs/src/definitions/opcode_trait.rs +++ b/crates/zkevm_opcode_defs/src/definitions/opcode_trait.rs @@ -83,6 +83,8 @@ pub trait OpcodeProps: 'static + Send + Sync { } } } + fn src0_can_be_pointer(&self) -> bool; + fn src1_can_be_pointer(&self) -> bool; } pub trait OpcodeVariantProps: Sized + 'static + Send + Sync { diff --git a/crates/zkevm_opcode_defs/src/definitions/ptr.rs b/crates/zkevm_opcode_defs/src/definitions/ptr.rs index 609ba64..841cf4c 100644 --- a/crates/zkevm_opcode_defs/src/definitions/ptr.rs +++ b/crates/zkevm_opcode_defs/src/definitions/ptr.rs @@ -82,4 +82,12 @@ impl OpcodeProps for PtrOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + true + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/ret.rs b/crates/zkevm_opcode_defs/src/definitions/ret.rs index ba73ca3..ed506fc 100644 --- a/crates/zkevm_opcode_defs/src/definitions/ret.rs +++ b/crates/zkevm_opcode_defs/src/definitions/ret.rs @@ -79,4 +79,12 @@ impl OpcodeProps for RetOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + true + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/shift.rs b/crates/zkevm_opcode_defs/src/definitions/shift.rs index 7e74b2a..d41fda9 100644 --- a/crates/zkevm_opcode_defs/src/definitions/shift.rs +++ b/crates/zkevm_opcode_defs/src/definitions/shift.rs @@ -79,4 +79,12 @@ impl OpcodeProps for ShiftOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/sub.rs b/crates/zkevm_opcode_defs/src/definitions/sub.rs index b67a404..255a593 100644 --- a/crates/zkevm_opcode_defs/src/definitions/sub.rs +++ b/crates/zkevm_opcode_defs/src/definitions/sub.rs @@ -68,4 +68,12 @@ impl OpcodeProps for SubOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + false + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/definitions/uma.rs b/crates/zkevm_opcode_defs/src/definitions/uma.rs index 3ac129e..9b805cb 100644 --- a/crates/zkevm_opcode_defs/src/definitions/uma.rs +++ b/crates/zkevm_opcode_defs/src/definitions/uma.rs @@ -139,4 +139,12 @@ impl OpcodeProps for UMAOpcode { fn can_be_used_in_static_context(&self) -> bool { true } + + fn src0_can_be_pointer(&self) -> bool { + true + } + + fn src1_can_be_pointer(&self) -> bool { + false + } } diff --git a/crates/zkevm_opcode_defs/src/utils.rs b/crates/zkevm_opcode_defs/src/utils.rs index e265f23..fcf246a 100644 --- a/crates/zkevm_opcode_defs/src/utils.rs +++ b/crates/zkevm_opcode_defs/src/utils.rs @@ -1,4 +1,5 @@ use crate::decoding::{EncodingModeProduction, VmEncodingMode}; +use ethereum_types::U256; pub const fn split_as_u4(value: u8) -> (u8, u8) { (value & ((1u8 << 4) - 1), value >> 4) @@ -59,3 +60,12 @@ pub fn bytecode_to_code_hash_for_mode>( Ok(versioned_hash_bytes) } + +/// Erase start and page number from a fat pointer. To be used in the case of a fat pointer +/// being passed to an opcode which shouldn't receive one. +pub fn erase_fat_pointer_metadata(ptr: &mut U256) { + // Memory page is at 1<<32 - 1<<64, start is at 1<<64 - 1<<96 + // We also need to preserve the top 128 bits of the value + ptr.0[0] &= 0x00000000_ffffffffu64; + ptr.0[1] &= 0xffffffff_00000000u64; +}