diff --git a/crates/circuit_encodings/.gitignore b/crates/circuit_encodings/.gitignore new file mode 100644 index 00000000..37a4f983 --- /dev/null +++ b/crates/circuit_encodings/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +.idea +.DS_Store \ No newline at end of file diff --git a/crates/circuit_encodings/Cargo.toml b/crates/circuit_encodings/Cargo.toml new file mode 100644 index 00000000..949b413d --- /dev/null +++ b/crates/circuit_encodings/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "circuit_encodings" +version = "0.140.1" +edition = "2021" +authors = ["The Matter Labs Team "] +homepage = "https://zksync.io/" +repository = "https://github.com/matter-labs/era-zkevm_test_harness/" +license = "MIT OR Apache-2.0" +keywords = ["blockchain", "zksync"] +categories = ["cryptography"] +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 + +derivative = "2.2" +serde = {version = "1", features = ["derive"]} + +[features] +default = [] +log_tracing = ["zkevm_circuits/log_tracing"] diff --git a/crates/circuit_encodings/README.md b/crates/circuit_encodings/README.md new file mode 100644 index 00000000..cb774772 --- /dev/null +++ b/crates/circuit_encodings/README.md @@ -0,0 +1,4 @@ +# Circuit encodings + +Separate crate, to handle the encodings for circuits. +It is split away from circuits, to save on compilation time. \ No newline at end of file diff --git a/crates/circuit_encodings/src/callstack_entry.rs b/crates/circuit_encodings/src/callstack_entry.rs new file mode 100644 index 00000000..e2fdfdaa --- /dev/null +++ b/crates/circuit_encodings/src/callstack_entry.rs @@ -0,0 +1,189 @@ +use crate::boojum::field::SmallField; +use zk_evm::vm_state::CallStackEntry; + +use super::*; + +pub fn u128_as_u32_le(value: u128) -> [u32; 4] { + [ + value as u32, + (value >> 32) as u32, + (value >> 64) as u32, + (value >> 96) as u32, + ] +} + +// we need some extra data to preserve +#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] +#[serde(bound = "")] +pub struct ExtendedCallstackEntry { + pub callstack_entry: CallStackEntry, + pub rollback_queue_head: [F; QUEUE_STATE_WIDTH], + pub rollback_queue_tail: [F; QUEUE_STATE_WIDTH], + pub rollback_queue_segment_length: u32, +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct CallstackEntryRollbackState { + pub rollback_queue_head: [F; QUEUE_STATE_WIDTH], + pub rollback_queue_tail: [F; QUEUE_STATE_WIDTH], + pub rollback_queue_segment_length: u32, +} + +use zkevm_circuits::base_structures::vm_state::saved_context::EXECUTION_CONTEXT_RECORD_ENCODING_WIDTH; + +impl OutOfCircuitFixedLengthEncodable + for ExtendedCallstackEntry +{ + fn encoding_witness(&self) -> [F; EXECUTION_CONTEXT_RECORD_ENCODING_WIDTH] { + debug_assert!(F::CAPACITY_BITS >= 57); + // full field elements first for simplicity + let v0 = self.rollback_queue_head[0]; + let v1 = self.rollback_queue_head[1]; + let v2 = self.rollback_queue_head[2]; + let v3 = self.rollback_queue_head[3]; + + let v4 = self.rollback_queue_tail[0]; + let v5 = self.rollback_queue_tail[1]; + let v6 = self.rollback_queue_tail[2]; + let v7 = self.rollback_queue_tail[3]; + + let code_address = decompose_address_as_u32x5(self.callstack_entry.code_address); + let v8 = code_address[0].into_field(); + let v9 = code_address[1].into_field(); + let v10 = code_address[2].into_field(); + let v11 = code_address[3].into_field(); + let v12 = code_address[4].into_field(); + + let this = decompose_address_as_u32x5(self.callstack_entry.this_address); + let v13 = this[0].into_field(); + let v14 = this[1].into_field(); + let v15 = this[2].into_field(); + let v16 = this[3].into_field(); + let v17 = this[4].into_field(); + + let caller_address = decompose_address_as_u32x5(self.callstack_entry.msg_sender); + let v18 = caller_address[0].into_field(); + let v19 = caller_address[1].into_field(); + let v20 = caller_address[2].into_field(); + let v21 = caller_address[3].into_field(); + let v22 = caller_address[4].into_field(); + + let context_u128_value_composite = u128_as_u32_le(self.callstack_entry.context_u128_value); + + let v23 = context_u128_value_composite[0].into_field(); + let v24 = context_u128_value_composite[1].into_field(); + let v25 = context_u128_value_composite[2].into_field(); + let v26 = context_u128_value_composite[3].into_field(); + + // now we have left + // - code_page + // - base_page + // - heap_upper_bound + // - aux_heap_upper_bound + // - ergs_remaining + // - sp + // - pc + // - eh + // - reverted_queue_segment_len + // - shard ids + // - few boolean flags + + // as usual, take u32 and add something on top + + let v27 = linear_combination(&[ + (self.callstack_entry.code_page.0.into_field(), F::ONE), + ( + self.callstack_entry.pc.into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.callstack_entry.this_shard_id.into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ( + self.callstack_entry.is_static.into_field(), + F::from_u64_unchecked(1u64 << 56), + ), + ]); + + let is_kernel_mode = self.callstack_entry.is_kernel_mode(); + + let v28 = linear_combination(&[ + (self.callstack_entry.base_memory_page.0.into_field(), F::ONE), + ( + self.callstack_entry.sp.into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.callstack_entry.caller_shard_id.into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ( + is_kernel_mode.into_field(), + F::from_u64_unchecked(1u64 << 56), + ), + ]); + + let v29 = linear_combination(&[ + (self.callstack_entry.ergs_remaining.into_field(), F::ONE), + ( + self.callstack_entry.exception_handler_location.into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.callstack_entry.code_shard_id.into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ( + self.callstack_entry.is_local_frame.into_field(), + F::from_u64_unchecked(1u64 << 56), + ), + ]); + + // now we have left + // - heap_upper_bound + // - aux_heap_upper_bound + // - reverted_queue_segment_len + + let reverted_queue_segment_len_decomposition = + self.rollback_queue_segment_length.to_le_bytes(); + let v30 = linear_combination(&[ + (self.callstack_entry.heap_bound.into_field(), F::ONE), + ( + reverted_queue_segment_len_decomposition[0].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + reverted_queue_segment_len_decomposition[1].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ]); + + let v31 = linear_combination(&[ + (self.callstack_entry.aux_heap_bound.into_field(), F::ONE), + ( + reverted_queue_segment_len_decomposition[2].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + reverted_queue_segment_len_decomposition[3].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ]); + + [ + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + ] + } +} + +pub type CallstackSimulator = FullWidthStackSimulator< + F, + ExtendedCallstackEntry, + EXECUTION_CONTEXT_RECORD_ENCODING_WIDTH, + FULL_SPONGE_QUEUE_STATE_WIDTH, + 4, +>; +pub type CallstackSimulatorState = + FullWidthStackIntermediateStates; diff --git a/crates/circuit_encodings/src/decommittment_request.rs b/crates/circuit_encodings/src/decommittment_request.rs new file mode 100644 index 00000000..a00ac5af --- /dev/null +++ b/crates/circuit_encodings/src/decommittment_request.rs @@ -0,0 +1,98 @@ +use super::*; +use zk_evm::aux_structures::DecommittmentQuery; + +use zkevm_circuits::base_structures::decommit_query::DECOMMIT_QUERY_PACKED_WIDTH; + +impl OutOfCircuitFixedLengthEncodable + for DecommittmentQuery +{ + fn encoding_witness(&self) -> [F; DECOMMIT_QUERY_PACKED_WIDTH] { + debug_assert!(F::CAPACITY_BITS >= 56); + + let code_hash = decompose_u256_as_u32x8(self.hash); + + // we assume that page bytes are known, so it'll be nop anyway + let page_bytes = self.memory_page.0.to_le_bytes(); + let timestamp_bytes = self.timestamp.0.to_le_bytes(); + + let v0 = linear_combination(&[ + (code_hash[0].into_field(), F::ONE), + ( + page_bytes[0].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + page_bytes[1].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + page_bytes[2].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v1 = linear_combination(&[ + (code_hash[1].into_field(), F::ONE), + ( + page_bytes[3].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + timestamp_bytes[0].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + timestamp_bytes[1].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v2 = linear_combination(&[ + (code_hash[2].into_field(), F::ONE), + ( + timestamp_bytes[2].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + timestamp_bytes[3].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + self.is_fresh.into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v3 = code_hash[3].into_field(); + let v4 = code_hash[4].into_field(); + let v5 = code_hash[5].into_field(); + let v6 = code_hash[6].into_field(); + let v7 = code_hash[7].into_field(); + + [v0, v1, v2, v3, v4, v5, v6, v7] + } +} + +pub type DecommittmentQueueSimulator = FullWidthQueueSimulator< + F, + DecommittmentQuery, + DECOMMIT_QUERY_PACKED_WIDTH, + FULL_SPONGE_QUEUE_STATE_WIDTH, + 1, +>; +pub type DecommittmentQueueState = + FullWidthQueueIntermediateStates; + +impl CircuitEquivalentReflection for DecommittmentQuery { + type Destination = zkevm_circuits::base_structures::decommit_query::DecommitQuery; + fn reflect(&self) -> >::Witness { + use zkevm_circuits::base_structures::decommit_query::DecommitQueryWitness; + + DecommitQueryWitness { + timestamp: self.timestamp.0, + code_hash: self.hash, + is_first: self.is_fresh, + page: self.memory_page.0, + } + } +} diff --git a/crates/circuit_encodings/src/lib.rs b/crates/circuit_encodings/src/lib.rs new file mode 100644 index 00000000..6565a1bb --- /dev/null +++ b/crates/circuit_encodings/src/lib.rs @@ -0,0 +1,787 @@ +use crate::boojum::algebraic_props::round_function::{ + absorb_multiple_rounds, AbsorptionModeOverwrite, AlgebraicRoundFunction, +}; +use crate::boojum::field::SmallField; +use crate::boojum::gadgets::traits::allocatable::CSAllocatable; +use crate::boojum::gadgets::traits::round_function::*; +use crate::boojum::gadgets::u160::decompose_address_as_u32x5; +use crate::boojum::gadgets::u256::decompose_u256_as_u32x8; +use derivative::Derivative; +use std::collections::VecDeque; +use zkevm_circuits::base_structures::vm_state::{FULL_SPONGE_QUEUE_STATE_WIDTH, QUEUE_STATE_WIDTH}; + +use crate::boojum::implementations::poseidon2::Poseidon2Goldilocks; +pub use zk_evm::ethereum_types; + +pub type ZkSyncDefaultRoundFunction = Poseidon2Goldilocks; + +pub use zk_evm; +pub use zkevm_circuits; +pub use zkevm_circuits::boojum; + +// for we need to encode some structures as packed field elements +pub trait OutOfCircuitFixedLengthEncodable: Clone { + fn encoding_witness(&self) -> [F; N]; +} + +// all encodings must match circuit counterparts +pub mod callstack_entry; +pub mod decommittment_request; +pub mod log_query; +pub mod memory_query; +pub mod recursion_request; +pub mod state_diff_record; + +pub use self::log_query::*; + +pub(crate) fn make_round_function_pairs( + initial: [F; N], + intermediates: [[F; N]; ROUNDS], +) -> [([F; N], [F; N]); ROUNDS] { + let mut result = [([F::ZERO; N], [F::ZERO; N]); ROUNDS]; + result[0].0 = initial; + result[0].1 = intermediates[0]; + for idx in 1..ROUNDS { + result[idx].0 = result[idx - 1].1; + result[idx].1 = intermediates[idx]; + } + + result +} + +#[derive(Derivative)] +#[derivative(Debug, Clone(bound = ""), Copy(bound = ""))] +pub struct QueueIntermediateStates< + F: SmallField, + const T: usize, + const SW: usize, + const ROUNDS: usize, +> { + pub head: [F; T], + pub tail: [F; T], + pub previous_head: [F; T], + pub previous_tail: [F; T], + pub num_items: u32, + pub round_function_execution_pairs: [([F; SW], [F; SW]); ROUNDS], +} + +impl + QueueIntermediateStates +{ + pub fn empty() -> Self { + Self { + head: [F::ZERO; T], + tail: [F::ZERO; T], + previous_head: [F::ZERO; T], + previous_tail: [F::ZERO; T], + num_items: 0, + round_function_execution_pairs: [([F::ZERO; SW], [F::ZERO; SW]); ROUNDS], + } + } +} + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative( + Clone(bound = ""), + Default(bound = "[F; T]: Default, [F; N]: Default"), + Debug +)] +#[serde(bound = "[F; T]: serde::Serialize + serde::de::DeserializeOwned, + [F; N]: serde::Serialize + serde::de::DeserializeOwned, + I: serde::Serialize + serde::de::DeserializeOwned")] +pub struct QueueSimulator< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const T: usize, + const N: usize, + const ROUNDS: usize, +> { + pub head: [F; T], + pub tail: [F; T], + pub num_items: u32, + pub witness: VecDeque<([F; N], [F; T], I)>, +} + +impl< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const T: usize, + const N: usize, + const ROUNDS: usize, + > QueueSimulator +{ + pub fn empty() -> Self { + Self { + head: [F::ZERO; T], + tail: [F::ZERO; T], + num_items: 0, + witness: VecDeque::new(), + } + } + + pub fn split(mut self, at: u32) -> (Self, Self) { + if at >= self.num_items { + let mut artificial_empty = Self::empty(); + artificial_empty.head = self.tail; + artificial_empty.tail = self.tail; + return (self, artificial_empty); + } + + let first_wit: VecDeque<_> = self.witness.drain(..(at as usize)).collect(); + let rest_wit = self.witness; + + let splitting_point = rest_wit.front().unwrap().1; + + let first = Self { + head: self.head, + tail: splitting_point, + num_items: at, + witness: first_wit, + }; + + let rest = Self { + head: splitting_point, + tail: self.tail, + num_items: self.num_items - at, + witness: rest_wit, + }; + + (first, rest) + } + + pub fn merge(first: Self, second: Self) -> Self { + assert_eq!(first.tail, second.head); + + let mut wit = first.witness; + wit.extend(second.witness); + + Self { + head: first.head, + tail: second.tail, + num_items: first.num_items + second.num_items, + witness: wit, + } + } + + pub fn push< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const SW: usize, + const CW: usize, + >( + &mut self, + element: I, + round_function: &R, + ) { + let _ = self.push_and_output_intermediate_data(element, round_function); + } + + pub fn push_and_output_intermediate_data< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const SW: usize, + const CW: usize, + >( + &mut self, + element: I, + _round_function: &R, + ) -> ( + [F; T], // old tail + QueueIntermediateStates, // new head/tail, as well as round function ins/outs + ) { + let old_tail = self.tail; + let encoding = element.encoding_witness(); + let mut to_hash = Vec::with_capacity(N + T); + to_hash.extend_from_slice(&encoding); + to_hash.extend(self.tail); + + let mut state = R::initial_state(); + let states = absorb_multiple_rounds::( + &mut state, &to_hash, + ); + let new_tail = + >::state_into_commitment::(&state); + self.witness.push_back((encoding, old_tail, element)); + + let states = make_round_function_pairs(R::initial_state(), states); + + self.num_items += 1; + self.tail = new_tail; + + let intermediate_info = QueueIntermediateStates { + head: self.head, + tail: new_tail, + previous_head: self.head, // unchanged + previous_tail: old_tail, + num_items: self.num_items, + round_function_execution_pairs: states, + }; + + (old_tail, intermediate_info) + } + + pub fn pop_and_output_intermediate_data< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const SW: usize, + const CW: usize, + >( + &mut self, + _round_function: &R, + ) -> (I, QueueIntermediateStates) { + let old_head = self.head; + let (_, _, element) = self.witness.pop_front().unwrap(); + + let encoding = element.encoding_witness(); + let mut to_hash = Vec::with_capacity(N + T); + to_hash.extend_from_slice(&encoding); + to_hash.extend(self.head); + + let mut state = R::initial_state(); + let states = absorb_multiple_rounds::( + &mut state, &to_hash, + ); + let new_head = + >::state_into_commitment::(&state); + + let states = make_round_function_pairs(R::initial_state(), states); + + self.num_items -= 1; + self.head = new_head; + + if self.num_items == 0 { + assert_eq!(self.head, self.tail); + } + + let intermediate_info = QueueIntermediateStates { + head: self.head, + tail: self.tail, + previous_head: old_head, + previous_tail: self.tail, + num_items: self.num_items, + round_function_execution_pairs: states, + }; + + (element, intermediate_info) + } + + pub fn split_by< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const SW: usize, + const CW: usize, + >( + mut self, + chunk_size: usize, + round_function: &R, + ) -> Vec { + let mut result = vec![]; + if self.num_items == 0 { + return result; + } else { + assert_eq!(self.witness.len(), self.num_items as usize); + } + + while self.num_items > 0 { + let mut subqueue = Self::empty(); + subqueue.head = self.head; + subqueue.tail = self.head; + for _ in 0..chunk_size { + if self.num_items == 0 { + break; + } + let (el, _) = self.pop_and_output_intermediate_data(round_function); + subqueue.push(el, round_function); + } + + result.push(subqueue); + } + + assert_eq!(self.tail, result.last().unwrap().tail); + + result + } +} + +#[derive(Derivative)] +#[derivative(Debug, Clone(bound = ""), Copy(bound = ""))] +pub struct FullWidthQueueIntermediateStates { + pub head: [F; SW], + pub tail: [F; SW], + pub old_head: [F; SW], + pub old_tail: [F; SW], + pub num_items: u32, + pub round_function_execution_pairs: [([F; SW], [F; SW]); ROUNDS], +} + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Debug, Clone(bound = ""))] +#[serde(bound = "[F; SW]: serde::Serialize + serde::de::DeserializeOwned, + [F; N]: serde::Serialize + serde::de::DeserializeOwned, + I: serde::Serialize + serde::de::DeserializeOwned")] +pub struct FullWidthQueueSimulator< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const N: usize, + const SW: usize, + const ROUNDS: usize, +> { + pub head: [F; SW], + pub tail: [F; SW], + pub num_items: u32, + pub witness: VecDeque<([F; N], [F; SW], I)>, +} + +impl< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const N: usize, + const SW: usize, + const ROUNDS: usize, + > Default for FullWidthQueueSimulator +{ + fn default() -> Self { + Self::empty() + } +} + +impl< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const N: usize, + const SW: usize, + const ROUNDS: usize, + > FullWidthQueueSimulator +{ + pub fn empty() -> Self { + Self { + head: [F::ZERO; SW], + tail: [F::ZERO; SW], + num_items: 0, + witness: VecDeque::new(), + } + } + + pub fn merge(first: Self, second: Self) -> Self { + assert_eq!(first.tail, second.head); + + let mut wit = first.witness; + wit.extend(second.witness); + + Self { + head: first.head, + tail: second.tail, + num_items: first.num_items + second.num_items, + witness: wit, + } + } + + pub fn push< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + &mut self, + element: I, + round_function: &R, + ) { + let _ = self.push_and_output_intermediate_data(element, round_function); + } + + pub fn push_and_output_intermediate_data< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + &mut self, + element: I, + _round_function: &R, + ) -> ( + [F; SW], // old tail + FullWidthQueueIntermediateStates, + ) { + let old_tail = self.tail; + assert!(N % AW == 0); + let encoding = element.encoding_witness(); + + let mut state = old_tail; + let states = absorb_multiple_rounds::( + &mut state, &encoding, + ); + let new_tail = state; + + let states = make_round_function_pairs(old_tail, states); + + self.witness.push_back((encoding, new_tail, element)); + self.num_items += 1; + self.tail = new_tail; + + let intermediate_info = FullWidthQueueIntermediateStates { + head: self.head, + tail: new_tail, + old_head: self.head, + old_tail, + num_items: self.num_items, + round_function_execution_pairs: states, + }; + + (old_tail, intermediate_info) + } + + pub fn pop_and_output_intermediate_data< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + &mut self, + _round_function: &R, + ) -> (I, FullWidthQueueIntermediateStates) { + let old_head = self.head; + assert!(N % AW == 0); + let (_, _, element) = self.witness.pop_front().unwrap(); + let encoding = element.encoding_witness(); + + let mut state = old_head; + let states = absorb_multiple_rounds::( + &mut state, &encoding, + ); + let new_head = state; + + let states = make_round_function_pairs(old_head, states); + + self.num_items -= 1; + self.head = new_head; + + if self.num_items == 0 { + assert_eq!(self.head, self.tail); + } + + let intermediate_info = FullWidthQueueIntermediateStates { + head: self.head, + tail: self.tail, + old_head, + old_tail: self.tail, + num_items: self.num_items, + round_function_execution_pairs: states, + }; + + (element, intermediate_info) + } + + /// Splits the queue into the smaller queues of length `chunk_size`. The last queue might be shorter. + pub fn split_by< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + mut self, + chunk_size: usize, + round_function: &R, + ) -> Vec { + let mut result = vec![]; + if self.num_items == 0 { + return result; + } else { + assert_eq!(self.witness.len(), self.num_items as usize); + } + + while self.num_items > 0 { + let mut subqueue = Self::empty(); + subqueue.head = self.head; + subqueue.tail = self.head; + for _ in 0..chunk_size { + if self.num_items == 0 { + break; + } + let (el, _) = self.pop_and_output_intermediate_data(round_function); + subqueue.push(el, round_function); + } + + result.push(subqueue); + } + + assert_eq!(self.tail, result.last().unwrap().tail); + + result + } +} + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Debug, Clone(bound = ""), Copy(bound = ""))] +#[serde(bound = "")] +pub struct FullWidthStackIntermediateStates { + pub is_push: bool, + #[serde(with = "crate::boojum::serde_utils::BigArraySerde")] + pub previous_state: [F; SW], + #[serde(with = "crate::boojum::serde_utils::BigArraySerde")] + pub new_state: [F; SW], + pub depth: u32, + #[serde(skip)] + #[serde(default = "empty_array_of_arrays::")] + pub round_function_execution_pairs: [([F; SW], [F; SW]); ROUNDS], +} + +fn empty_array_of_arrays( +) -> [([F; SW], [F; SW]); ROUNDS] { + [([F::ZERO; SW], [F::ZERO; SW]); ROUNDS] +} + +pub struct FullWidthStackSimulator< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const N: usize, + const SW: usize, + const ROUNDS: usize, +> { + pub state: [F; SW], + pub num_items: u32, + pub witness: Vec<([F; N], [F; SW], I)>, +} + +impl< + F: SmallField, + I: OutOfCircuitFixedLengthEncodable, + const N: usize, + const SW: usize, + const ROUNDS: usize, + > FullWidthStackSimulator +{ + pub fn empty() -> Self { + Self { + state: [F::ZERO; SW], + num_items: 0, + witness: vec![], + } + } + + pub fn push< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + &mut self, + element: I, + round_function: &R, + ) { + let _ = self.push_and_output_intermediate_data(element, round_function); + } + + pub fn push_and_output_intermediate_data< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + &mut self, + element: I, + _round_function: &R, + ) -> FullWidthStackIntermediateStates { + assert!(N % AW == 0); + let encoding = element.encoding_witness(); + + let old_state = self.state; + + let mut state = old_state; + let states = absorb_multiple_rounds::( + &mut state, &encoding, + ); + let new_state = state; + + let states = make_round_function_pairs(old_state, states); + + self.witness.push((encoding, self.state, element)); + self.num_items += 1; + self.state = new_state; + + let intermediate_info = FullWidthStackIntermediateStates { + is_push: true, + previous_state: old_state, + new_state, + depth: self.num_items, + round_function_execution_pairs: states, + }; + + intermediate_info + } + + pub fn pop_and_output_intermediate_data< + R: CircuitRoundFunction + AlgebraicRoundFunction, + const AW: usize, + const CW: usize, + >( + &mut self, + _round_function: &R, + ) -> (I, FullWidthStackIntermediateStates) { + assert!(N % AW == 0); + + let current_state = self.state; + + let popped = self.witness.pop().unwrap(); + self.num_items -= 1; + + let (_element_encoding, previous_state, element) = popped; + let encoding = element.encoding_witness(); + + let mut state = previous_state; + let states = absorb_multiple_rounds::( + &mut state, &encoding, + ); + let new_state = state; + assert_eq!(new_state, self.state); + + let states = make_round_function_pairs(previous_state, states); + + self.state = previous_state; + + let intermediate_info = FullWidthStackIntermediateStates { + is_push: false, + previous_state: current_state, + new_state: previous_state, + depth: self.num_items, + round_function_execution_pairs: states, + }; + + (element, intermediate_info) + } +} + +pub trait CircuitEquivalentReflection: Clone { + type Destination: Clone + CSAllocatable; + fn reflect(&self) -> >::Witness; +} + +pub trait BytesSerializable: Clone { + fn serialize(&self) -> [u8; N]; +} + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Key(pub [u32; N]); + +pub(crate) trait IntoSmallField: Sized { + fn into_field(self) -> F; +} + +impl IntoSmallField for bool { + #[inline(always)] + fn into_field(self) -> F { + F::from_u64_unchecked(self as u64) + } +} + +impl IntoSmallField for u8 { + #[inline(always)] + fn into_field(self) -> F { + F::from_u64_unchecked(self as u64) + } +} + +impl IntoSmallField for u16 { + #[inline(always)] + fn into_field(self) -> F { + F::from_u64_unchecked(self as u64) + } +} + +impl IntoSmallField for u32 { + #[inline(always)] + fn into_field(self) -> F { + F::from_u64_unchecked(self as u64) + } +} + +#[inline(always)] +pub(crate) fn scale_and_accumulate>( + dst: &mut F, + src: T, + shift: usize, +) { + let mut tmp = src.into_field(); + tmp.mul_assign(&F::SHIFTS[shift]); + dst.add_assign(&tmp); +} + +#[inline(always)] +pub(crate) fn linear_combination(input: &[(F, F)]) -> F { + let mut result = F::ZERO; + for (a, b) in input.iter() { + let mut tmp = *a; + tmp.mul_assign(&b); + result.add_assign(&tmp); + } + + result +} + +#[cfg(test)] +mod tests { + //use franklin_crypto::boojum::field::goldilocks::GoldilocksField; + use crate::boojum::field::goldilocks::GoldilocksField; + + use crate::ZkSyncDefaultRoundFunction; + + use super::{recursion_request::RecursionRequest, *}; + + fn create_recursion_request(x: u64) -> RecursionRequest { + RecursionRequest { + circuit_type: GoldilocksField::from_nonreduced_u64(x), + public_input: [GoldilocksField::from_nonreduced_u64(x); 4], + } + } + + /// Basic test to cover push, pop and split. + #[test] + fn basic_queue_test() { + let recursion_request = RecursionRequest { + circuit_type: GoldilocksField::from_nonreduced_u64(0), + public_input: [GoldilocksField::from_nonreduced_u64(0); 4], + }; + + let mut queue: FullWidthQueueSimulator< + GoldilocksField, + RecursionRequest, + 8, + 12, + 1, + > = FullWidthQueueSimulator::default(); + + let empty_head = queue.head; + assert_eq!(queue.num_items, 0); + + // First push 1 element, and then remaining 9. + let round_function = ZkSyncDefaultRoundFunction::default(); + queue.push(recursion_request, &round_function); + assert_eq!(queue.num_items, 1); + let tail_after_first = queue.tail; + + for i in 1..10 { + queue.push(create_recursion_request(i), &round_function) + } + assert_eq!(queue.num_items, 10); + + // pop one element + let (element, data) = queue.pop_and_output_intermediate_data(&round_function); + // it should return the first one that we entered (with circuit 0). + assert_eq!(element.circuit_type, 0); + + assert_eq!(queue.num_items, 9); + assert_eq!(data.num_items, 9); + + assert_eq!(data.head, tail_after_first); + assert_eq!(data.old_head, empty_head); + assert_eq!(data.old_tail, data.tail); + + let mut parts = queue.split_by(3, &round_function); + + assert_eq!(3, parts.len()); + // The queue was cut in 3 pieces, check that head and tails are matching. + assert_eq!(parts[0].head, tail_after_first); + assert_eq!(parts[0].tail, parts[1].head); + assert_eq!(parts[1].tail, parts[2].head); + assert_eq!(parts[2].tail, data.tail); + for i in 0..3 { + assert_eq!(parts[i].num_items, 3); + } + let (element, _) = parts[2].pop_and_output_intermediate_data(&round_function); + assert_eq!(element.circuit_type, 7); + } +} diff --git a/crates/circuit_encodings/src/log_query.rs b/crates/circuit_encodings/src/log_query.rs new file mode 100644 index 00000000..12e2a50b --- /dev/null +++ b/crates/circuit_encodings/src/log_query.rs @@ -0,0 +1,534 @@ +use zk_evm::aux_structures::LogQuery; +use zk_evm::aux_structures::Timestamp; +use zk_evm::ethereum_types::H160; +use zk_evm::ethereum_types::U256; + +// Proxy, as we just need read-only +pub trait LogQueryLike: 'static + Clone + Send + Sync + std::fmt::Debug { + fn shard_id(&self) -> u8; + fn address(&self) -> H160; + fn key(&self) -> U256; + fn rw_flag(&self) -> bool; + fn rollback(&self) -> bool; + fn read_value(&self) -> U256; + fn written_value(&self) -> U256; + fn create_partially_filled_from_fields( + shard_id: u8, + address: H160, + key: U256, + read_value: U256, + written_value: U256, + rw_flag: bool, + ) -> Self; +} + +impl LogQueryLike for LogQuery { + fn shard_id(&self) -> u8 { + self.shard_id + } + fn address(&self) -> H160 { + self.address + } + fn key(&self) -> U256 { + self.key + } + fn rw_flag(&self) -> bool { + self.rw_flag + } + fn rollback(&self) -> bool { + self.rollback + } + fn read_value(&self) -> U256 { + self.read_value + } + fn written_value(&self) -> U256 { + self.written_value + } + fn create_partially_filled_from_fields( + shard_id: u8, + address: H160, + key: U256, + read_value: U256, + written_value: U256, + rw_flag: bool, + ) -> Self { + // only smaller number of field matters in practice + LogQuery { + timestamp: Timestamp(0), + tx_number_in_block: 0, + aux_byte: 0, + shard_id, + address, + key, + read_value, + written_value, + rw_flag, + rollback: false, + is_service: false, + } + } +} + +#[derive(Clone, Debug)] +pub struct LogQueryLikeWithExtendedEnumeration { + pub raw_query: L, + pub extended_timestamp: u32, +} + +use super::*; + +use zkevm_circuits::storage_validity_by_grand_product::input::PACKED_KEY_LENGTH; + +pub fn comparison_key(query: &LogQuery) -> Key { + let key = decompose_u256_as_u32x8(query.key); + let address = decompose_address_as_u32x5(query.address); + + let le_words = [ + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7], address[0], address[1], + address[2], address[3], address[4], + ]; + + Key(le_words) +} + +pub fn event_comparison_key(query: &LogQuery) -> Key<1> { + let le_words = [query.timestamp.0]; + + Key(le_words) +} + +use zkevm_circuits::base_structures::log_query::LOG_QUERY_PACKED_WIDTH; + +impl OutOfCircuitFixedLengthEncodable for LogQuery { + fn encoding_witness(&self) -> [F; LOG_QUERY_PACKED_WIDTH] { + debug_assert!(F::CAPACITY_BITS >= 56); + // we decompose "key" and mix it into other limbs because with high probability + // in VM decomposition of "key" will always exist beforehand + + let mut key_bytes = [0u8; 32]; + self.key.to_little_endian(&mut key_bytes); + let mut address_bytes = self.address.0; + address_bytes.reverse(); + + let read_value = decompose_u256_as_u32x8(self.read_value); + let written_value = decompose_u256_as_u32x8(self.written_value); + + // we want to pack tightly, so we "base" our packing on read and written values + + let v0 = linear_combination(&[ + (read_value[0].into_field(), F::ONE), + (key_bytes[0].into_field(), F::from_u64_unchecked(1u64 << 32)), + (key_bytes[1].into_field(), F::from_u64_unchecked(1u64 << 40)), + (key_bytes[2].into_field(), F::from_u64_unchecked(1u64 << 48)), + ]); + + let v1 = linear_combination(&[ + (read_value[1].into_field(), F::ONE), + (key_bytes[3].into_field(), F::from_u64_unchecked(1u64 << 32)), + (key_bytes[4].into_field(), F::from_u64_unchecked(1u64 << 40)), + (key_bytes[5].into_field(), F::from_u64_unchecked(1u64 << 48)), + ]); + + let v2 = linear_combination(&[ + (read_value[2].into_field(), F::ONE), + (key_bytes[6].into_field(), F::from_u64_unchecked(1u64 << 32)), + (key_bytes[7].into_field(), F::from_u64_unchecked(1u64 << 40)), + (key_bytes[8].into_field(), F::from_u64_unchecked(1u64 << 48)), + ]); + + let v3 = linear_combination(&[ + (read_value[3].into_field(), F::ONE), + (key_bytes[9].into_field(), F::from_u64_unchecked(1u64 << 32)), + ( + key_bytes[10].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[11].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v4 = linear_combination(&[ + (read_value[4].into_field(), F::ONE), + ( + key_bytes[12].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[13].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[14].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v5 = linear_combination(&[ + (read_value[5].into_field(), F::ONE), + ( + key_bytes[15].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[16].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[17].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v6 = linear_combination(&[ + (read_value[6].into_field(), F::ONE), + ( + key_bytes[18].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[19].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[20].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v7 = linear_combination(&[ + (read_value[7].into_field(), F::ONE), + ( + key_bytes[21].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[22].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[23].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + // continue with written value + + let v8 = linear_combination(&[ + (written_value[0].into_field(), F::ONE), + ( + key_bytes[24].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[25].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[26].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v9 = linear_combination(&[ + (written_value[1].into_field(), F::ONE), + ( + key_bytes[27].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[28].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[29].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + // continue mixing bytes, now from "address" + + let v10 = linear_combination(&[ + (written_value[2].into_field(), F::ONE), + ( + key_bytes[30].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[31].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[0].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v11 = linear_combination(&[ + (written_value[3].into_field(), F::ONE), + ( + address_bytes[1].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[2].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[3].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v12 = linear_combination(&[ + (written_value[4].into_field(), F::ONE), + ( + address_bytes[4].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[5].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[6].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v13 = linear_combination(&[ + (written_value[5].into_field(), F::ONE), + ( + address_bytes[7].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[8].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[9].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v14 = linear_combination(&[ + (written_value[6].into_field(), F::ONE), + ( + address_bytes[10].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[11].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[12].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v15 = linear_combination(&[ + (written_value[7].into_field(), F::ONE), + ( + address_bytes[13].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[14].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[15].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + // now we can pack using some other "large" items as base + + let v16 = linear_combination(&[ + (self.timestamp.0.into_field(), F::ONE), + ( + address_bytes[16].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[17].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[18].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v17 = linear_combination(&[ + (self.tx_number_in_block.into_field(), F::ONE), // NOTE: u16 out of circuit and u32 in circuit + ( + address_bytes[19].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.aux_byte.into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + self.shard_id.into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v18 = linear_combination(&[ + (self.rw_flag.into_field(), F::ONE), + (self.is_service.into_field(), F::TWO), + ]); + + // and the final into_field() is just rollback flag itself + + let v19 = self.rollback.into_field(); + + [ + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, + ] + } +} + +pub type LogQueryWithExtendedEnumeration = LogQueryLikeWithExtendedEnumeration; + +impl OutOfCircuitFixedLengthEncodable + for LogQueryWithExtendedEnumeration +{ + fn encoding_witness(&self) -> [F; LOG_QUERY_PACKED_WIDTH] { + let LogQueryWithExtendedEnumeration { + raw_query, + extended_timestamp, + } = self; + + let mut result = >::encoding_witness(raw_query); + use zkevm_circuits::storage_validity_by_grand_product::EXTENDED_TIMESTAMP_ENCODING_OFFSET; + + let mut shift = EXTENDED_TIMESTAMP_ENCODING_OFFSET; + use zkevm_circuits::storage_validity_by_grand_product::EXTENDED_TIMESTAMP_ENCODING_ELEMENT; + scale_and_accumulate::( + &mut result[EXTENDED_TIMESTAMP_ENCODING_ELEMENT], + *extended_timestamp, + shift, + ); + shift += 32; + assert!(shift <= F::CAPACITY_BITS as usize); + + result + } +} + +use zkevm_circuits::base_structures::log_query::LOG_QUERY_ABSORBTION_ROUNDS; + +pub type LogQueueSimulator = QueueSimulator< + F, + LogQuery, + QUEUE_STATE_WIDTH, + LOG_QUERY_PACKED_WIDTH, + LOG_QUERY_ABSORBTION_ROUNDS, +>; +pub type LogQueueState = QueueIntermediateStates< + F, + QUEUE_STATE_WIDTH, + FULL_SPONGE_QUEUE_STATE_WIDTH, + LOG_QUERY_ABSORBTION_ROUNDS, +>; + +pub type LogWithExtendedEnumerationQueueSimulator = QueueSimulator< + F, + LogQueryWithExtendedEnumeration, + QUEUE_STATE_WIDTH, + LOG_QUERY_PACKED_WIDTH, + LOG_QUERY_ABSORBTION_ROUNDS, +>; +// pub type LogQueueState = QueueIntermediateStates; + +pub fn log_query_into_circuit_log_query_witness( + query: &LogQuery, +) -> as CSAllocatable>::Witness { + use zkevm_circuits::base_structures::log_query::LogQueryWitness; + + LogQueryWitness { + address: query.address, + key: query.key, + read_value: query.read_value, + written_value: query.written_value, + rw_flag: query.rw_flag, + aux_byte: query.aux_byte, + rollback: query.rollback, + is_service: query.is_service, + shard_id: query.shard_id, + tx_number_in_block: query.tx_number_in_block as u32, + timestamp: query.timestamp.0, + } +} + +impl CircuitEquivalentReflection for LogQuery { + type Destination = zkevm_circuits::base_structures::log_query::LogQuery; + fn reflect(&self) -> >::Witness { + log_query_into_circuit_log_query_witness(self) + } +} + +pub fn log_query_into_timestamped_storage_record_witness( + query: &LogQueryWithExtendedEnumeration +) -> as CSAllocatable>::Witness{ + use zkevm_circuits::storage_validity_by_grand_product::TimestampedStorageLogRecordWitness; + + TimestampedStorageLogRecordWitness { + record: log_query_into_circuit_log_query_witness(&query.raw_query), + timestamp: query.extended_timestamp, + } +} + +impl CircuitEquivalentReflection for LogQueryWithExtendedEnumeration { + type Destination = + zkevm_circuits::storage_validity_by_grand_product::TimestampedStorageLogRecord; + fn reflect(&self) -> >::Witness { + log_query_into_timestamped_storage_record_witness(self) + } +} + +use zkevm_circuits::base_structures::log_query::L2_TO_L1_MESSAGE_BYTE_LENGTH; + +// for purposes of L1 messages +impl BytesSerializable for LogQuery { + fn serialize(&self) -> [u8; L2_TO_L1_MESSAGE_BYTE_LENGTH] { + let mut result = [0u8; L2_TO_L1_MESSAGE_BYTE_LENGTH]; + let mut offset = 0; + result[offset] = self.shard_id; + offset += 1; + result[offset] = self.is_service as u8; + offset += 1; + + let bytes_be = self.tx_number_in_block.to_be_bytes(); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + let bytes_be = self.address.to_fixed_bytes(); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + let mut bytes_be = [0u8; 32]; + self.key.to_big_endian(&mut bytes_be); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + let mut bytes_be = [0u8; 32]; + self.written_value.to_big_endian(&mut bytes_be); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + assert_eq!(offset, L2_TO_L1_MESSAGE_BYTE_LENGTH); + + result + } +} diff --git a/crates/circuit_encodings/src/memory_query.rs b/crates/circuit_encodings/src/memory_query.rs new file mode 100644 index 00000000..7db7e630 --- /dev/null +++ b/crates/circuit_encodings/src/memory_query.rs @@ -0,0 +1,144 @@ +use super::*; +use zk_evm::aux_structures::MemoryQuery; + +use zkevm_circuits::ram_permutation::input::{RAM_FULL_KEY_LENGTH, RAM_SORTING_KEY_LENGTH}; + +pub fn sorting_key(query: &MemoryQuery) -> Key { + let le_words = [ + query.timestamp.0, + query.location.index.0, + query.location.page.0, + ]; + + Key(le_words) +} + +pub fn comparison_key(query: &MemoryQuery) -> Key { + let le_words = [query.location.index.0, query.location.page.0]; + + Key(le_words) +} + +use zkevm_circuits::base_structures::memory_query::MEMORY_QUERY_PACKED_WIDTH; + +impl OutOfCircuitFixedLengthEncodable for MemoryQuery { + fn encoding_witness(&self) -> [F; MEMORY_QUERY_PACKED_WIDTH] { + // we assume the fact that capacity of F is quite close to 64 bits + debug_assert!(F::CAPACITY_BITS >= 56); + + let value = decompose_u256_as_u32x8(self.value); + + // strategy: we use 3 field elements to pack timestamp, decomposition of page, index and r/w flag, + // and 5 more elements to tightly pack 8xu32 of values + + let v0 = self.timestamp.0.into_field(); + let v1 = self.location.page.0.into_field(); + let v2 = linear_combination(&[ + (self.location.index.0.into_field(), F::ONE), + (self.rw_flag.into_field(), F::from_u64_unchecked(1u64 << 32)), + ( + self.value_is_pointer.into_field(), + F::from_u64_unchecked(1u64 << 33), + ), + ]); + + // value. Those in most of the cases will be nops + let decomposition_5 = value[5].to_le_bytes(); + let decomposition_6 = value[6].to_le_bytes(); + let decomposition_7 = value[7].to_le_bytes(); + + let v3 = linear_combination(&[ + (value[0].into_field(), F::ONE), + ( + decomposition_5[0].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_5[1].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_5[2].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v4 = linear_combination(&[ + (value[1].into_field(), F::ONE), + ( + decomposition_5[3].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_6[0].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_6[1].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v5 = linear_combination(&[ + (value[2].into_field(), F::ONE), + ( + decomposition_6[2].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_6[3].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_7[0].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v6 = linear_combination(&[ + (value[3].into_field(), F::ONE), + ( + decomposition_7[1].into_field(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_7[2].into_field(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_7[3].into_field(), + F::from_u64_unchecked(1u64 << 48), + ), + ]); + + let v7 = value[4].into_field(); + + [v0, v1, v2, v3, v4, v5, v6, v7] + } +} + +pub type MemoryQueueSimulator = FullWidthQueueSimulator< + F, + MemoryQuery, + MEMORY_QUERY_PACKED_WIDTH, + FULL_SPONGE_QUEUE_STATE_WIDTH, + 1, +>; +pub type MemoryQueueState = + FullWidthQueueIntermediateStates; + +impl CircuitEquivalentReflection for MemoryQuery { + type Destination = zkevm_circuits::base_structures::memory_query::MemoryQuery; + fn reflect(&self) -> >::Witness { + use zkevm_circuits::base_structures::memory_query::MemoryQueryWitness; + + MemoryQueryWitness { + timestamp: self.timestamp.0, + memory_page: self.location.page.0, + index: self.location.index.0, + rw_flag: self.rw_flag, + value: self.value, + is_ptr: self.value_is_pointer, + } + } +} diff --git a/crates/circuit_encodings/src/recursion_request.rs b/crates/circuit_encodings/src/recursion_request.rs new file mode 100644 index 00000000..ae4614d6 --- /dev/null +++ b/crates/circuit_encodings/src/recursion_request.rs @@ -0,0 +1,48 @@ +use super::*; +use zkevm_circuits::base_structures::recursion_query::*; +use zkevm_circuits::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Copy, Debug)] +#[serde(bound = "")] +pub struct RecursionRequest { + pub circuit_type: F, + pub public_input: [F; INPUT_OUTPUT_COMMITMENT_LENGTH], +} + +impl OutOfCircuitFixedLengthEncodable + for RecursionRequest +{ + fn encoding_witness(&self) -> [F; RECURSION_QUERY_PACKED_WIDTH] { + [ + self.circuit_type, + self.public_input[0], + self.public_input[1], + self.public_input[2], + self.public_input[3], + F::ZERO, + F::ZERO, + F::ZERO, + ] + } +} + +impl CircuitEquivalentReflection for RecursionRequest { + type Destination = zkevm_circuits::base_structures::recursion_query::RecursionQuery; + fn reflect(&self) -> >::Witness { + zkevm_circuits::base_structures::recursion_query::RecursionQueryWitness { + circuit_type: self.circuit_type, + input_commitment: self.public_input, + } + } +} + +pub type RecursionQueueSimulator = FullWidthQueueSimulator< + F, + RecursionRequest, + RECURSION_QUERY_PACKED_WIDTH, + FULL_SPONGE_QUEUE_STATE_WIDTH, + 1, +>; +pub type RecursionQueueState = + FullWidthQueueIntermediateStates; diff --git a/crates/circuit_encodings/src/state_diff_record.rs b/crates/circuit_encodings/src/state_diff_record.rs new file mode 100644 index 00000000..9f9dec3a --- /dev/null +++ b/crates/circuit_encodings/src/state_diff_record.rs @@ -0,0 +1,54 @@ +use zk_evm::ethereum_types::Address; + +use super::*; +use crate::ethereum_types::U256; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, Hash)] +pub struct StateDiffRecord { + pub address: Address, + pub key: U256, + pub derived_key: [u8; 32], + pub enumeration_index: u64, + pub initial_value: U256, + pub final_value: U256, +} + +use zkevm_circuits::base_structures::state_diff_record::STATE_DIFF_RECORD_BYTE_ENCODING_LEN; + +impl StateDiffRecord { + // the only thing we need is byte encoding + pub fn encode(&self) -> [u8; STATE_DIFF_RECORD_BYTE_ENCODING_LEN] { + let mut encoding = [0u8; STATE_DIFF_RECORD_BYTE_ENCODING_LEN]; + let mut offset = 0; + let mut end = 0; + + end += 20; + encoding[offset..end].copy_from_slice(self.address.as_fixed_bytes()); + offset = end; + + end += 32; + self.key.to_big_endian(&mut encoding[offset..end]); + offset = end; + + end += 32; + encoding[offset..end].copy_from_slice(&self.derived_key); + offset = end; + + end += 8; + encoding[offset..end].copy_from_slice(&self.enumeration_index.to_be_bytes()); + offset = end; + + end += 32; + self.initial_value.to_big_endian(&mut encoding[offset..end]); + offset = end; + + end += 32; + self.final_value.to_big_endian(&mut encoding[offset..end]); + offset = end; + + debug_assert_eq!(offset, encoding.len()); + + encoding + } +} diff --git a/crates/circuit_sequencer_api/Cargo.toml b/crates/circuit_sequencer_api/Cargo.toml index 3a8b4e1c..46182124 100644 --- a/crates/circuit_sequencer_api/Cargo.toml +++ b/crates/circuit_sequencer_api/Cargo.toml @@ -1,21 +1,23 @@ [package] name = "circuit_sequencer_api" -version = "0.133.0" +version = "0.140.1" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" -repository = "https://github.com/matter-labs/era-zkevm_circuits" +repository = "https://github.com/matter-labs/era-zkevm_test_harness/" license = "MIT OR Apache-2.0" keywords = ["blockchain", "zksync"] categories = ["cryptography"] -description = "ZKsync Era circuits for EraVM" +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 -zk_evm = { version = "=0.133.0", path = "../zk_evm" } -bellman = { package = "bellman_ce", version = "=0.7.0" } derivative = "2.2" serde = {version = "1", features = ["derive"]} diff --git a/crates/circuit_sequencer_api/src/commitments.rs b/crates/circuit_sequencer_api/src/commitments.rs new file mode 100644 index 00000000..4ca135b0 --- /dev/null +++ b/crates/circuit_sequencer_api/src/commitments.rs @@ -0,0 +1,74 @@ +use crate::{ + boojum::{ + algebraic_props::round_function::AlgebraicRoundFunction, field::SmallField, + gadgets::traits::round_function::*, + }, + utils::{calldata_to_aligned_data, finalize_queue_state, finalized_queue_state_as_bytes}, +}; +use circuit_encodings::{boojum::field::goldilocks::GoldilocksField, *}; +use zk_evm::aux_structures::LogQuery; + +pub fn initial_heap_content_commitment< + F: SmallField, + R: BuildableCircuitRoundFunction + AlgebraicRoundFunction, +>( + bootloader_heap_data: &Vec, + round_function: &R, +) -> [u8; 32] { + let heap_writes = calldata_to_aligned_data(bootloader_heap_data); + + use circuit_encodings::memory_query::MemoryQueueSimulator; + use zk_evm::abstractions::*; + use zk_evm::aux_structures::*; + + let mut memory_queue = MemoryQueueSimulator::empty(); + + for (idx, el) in heap_writes.into_iter().enumerate() { + let query = MemoryQuery { + timestamp: Timestamp(0), + location: MemoryLocation { + memory_type: MemoryType::Heap, + page: MemoryPage(zk_evm::zkevm_opcode_defs::BOOTLOADER_HEAP_PAGE), + index: MemoryIndex(idx as u32), + }, + rw_flag: true, + value: el, + value_is_pointer: false, + }; + memory_queue.push(query, round_function); + } + + let finalized_state = finalize_queue_state(memory_queue.tail, round_function); + finalized_queue_state_as_bytes(finalized_state) +} + +pub fn initial_heap_content_commitment_fixed(bootloader_heap_data: &Vec) -> [u8; 32] { + initial_heap_content_commitment::( + bootloader_heap_data, + &ZkSyncDefaultRoundFunction::default(), + ) +} + +pub fn events_queue_commitment< + F: SmallField, + R: BuildableCircuitRoundFunction + AlgebraicRoundFunction, +>( + sorted_and_deduplicated_events: &Vec, + round_function: &R, +) -> [u8; 32] { + let mut queue = LogQueueSimulator::empty(); + + for el in sorted_and_deduplicated_events.iter() { + queue.push(*el, round_function); + } + + let finalized_state = finalize_queue_state(queue.tail, round_function); + finalized_queue_state_as_bytes(finalized_state) +} + +pub fn events_queue_commitment_fixed(sorted_and_deduplicated_events: &Vec) -> [u8; 32] { + events_queue_commitment::( + sorted_and_deduplicated_events, + &ZkSyncDefaultRoundFunction::default(), + ) +} diff --git a/crates/circuit_sequencer_api/src/geometry_config.rs b/crates/circuit_sequencer_api/src/geometry_config.rs new file mode 100644 index 00000000..b1096d78 --- /dev/null +++ b/crates/circuit_sequencer_api/src/geometry_config.rs @@ -0,0 +1,20 @@ +// This file is auto-generated, do not edit it manually + +use crate::toolset::GeometryConfig; + +pub const fn get_geometry_config() -> GeometryConfig { + GeometryConfig { + cycles_per_vm_snapshot: 5692, + cycles_code_decommitter_sorter: 117500, + cycles_per_log_demuxer: 58750, + cycles_per_storage_sorter: 46921, + cycles_per_events_or_l1_messages_sorter: 31287, + cycles_per_ram_permutation: 136714, + cycles_per_code_decommitter: 2845, + cycles_per_storage_application: 33, + cycles_per_keccak256_circuit: 672, + cycles_per_sha256_circuit: 2206, + cycles_per_ecrecover_circuit: 2, + limit_for_l1_messages_pudata_hasher: 774, + } +} diff --git a/crates/circuit_sequencer_api/src/lib.rs b/crates/circuit_sequencer_api/src/lib.rs index 1f4fee41..e12ff3b7 100644 --- a/crates/circuit_sequencer_api/src/lib.rs +++ b/crates/circuit_sequencer_api/src/lib.rs @@ -1,4 +1,13 @@ +#![feature(array_chunks)] + +pub mod geometry_config; pub mod proof; pub mod sort_storage_access; +pub mod toolset; + +pub mod commitments; +pub mod utils; + +pub use circuit_encodings::boojum; pub const INITIAL_MONOTONIC_CYCLE_COUNTER: u32 = 1024; diff --git a/crates/circuit_sequencer_api/src/proof.rs b/crates/circuit_sequencer_api/src/proof.rs index 3cceffb1..79431a53 100644 --- a/crates/circuit_sequencer_api/src/proof.rs +++ b/crates/circuit_sequencer_api/src/proof.rs @@ -8,6 +8,8 @@ use bellman::{ // Wrapper for the final scheduler proof. // We use generic circuit here, as this is used only for serializing & deserializing in sequencer. +// The exact circuti type does not change the rules of (de)serialization, so we use a very lightweight +// circuit in places that only pass proofs around to avoid unnecessary heavy compilation in most places. pub type FinalProof = Proof; #[derive(Clone)] diff --git a/crates/circuit_sequencer_api/src/sort_storage_access.rs b/crates/circuit_sequencer_api/src/sort_storage_access.rs index f02232e6..fece5650 100644 --- a/crates/circuit_sequencer_api/src/sort_storage_access.rs +++ b/crates/circuit_sequencer_api/src/sort_storage_access.rs @@ -2,10 +2,10 @@ use derivative::Derivative; use rayon::prelude::*; use std::cmp::Ordering; use std::iter::IntoIterator; -use zk_evm::{ - aux_structures::{LogQuery, Timestamp}, - ethereum_types::{H160, U256}, -}; +use zk_evm::ethereum_types::U256; + +use circuit_encodings::LogQueryLike; +use circuit_encodings::LogQueryLikeWithExtendedEnumeration; #[derive(Derivative)] #[derivative(Default(bound = ""), Debug)] @@ -16,78 +16,6 @@ pub struct StorageSlotHistoryKeeper { pub did_read_at_depth_zero: bool, } -// Proxy, as we just need read-only -pub trait LogQueryLike: 'static + Clone + Send + Sync + std::fmt::Debug { - fn shard_id(&self) -> u8; - fn address(&self) -> H160; - fn key(&self) -> U256; - fn rw_flag(&self) -> bool; - fn rollback(&self) -> bool; - fn read_value(&self) -> U256; - fn written_value(&self) -> U256; - fn create_partially_filled_from_fields( - shard_id: u8, - address: H160, - key: U256, - read_value: U256, - written_value: U256, - rw_flag: bool, - ) -> Self; -} - -impl LogQueryLike for LogQuery { - fn shard_id(&self) -> u8 { - self.shard_id - } - fn address(&self) -> H160 { - self.address - } - fn key(&self) -> U256 { - self.key - } - fn rw_flag(&self) -> bool { - self.rw_flag - } - fn rollback(&self) -> bool { - self.rollback - } - fn read_value(&self) -> U256 { - self.read_value - } - fn written_value(&self) -> U256 { - self.written_value - } - fn create_partially_filled_from_fields( - shard_id: u8, - address: H160, - key: U256, - read_value: U256, - written_value: U256, - rw_flag: bool, - ) -> Self { - // only smaller number of field matters in practice - LogQuery { - timestamp: Timestamp(0), - tx_number_in_block: 0, - aux_byte: 0, - shard_id, - address, - key, - read_value, - written_value, - rw_flag, - rollback: false, - is_service: false, - } - } -} - -#[derive(Clone, Debug)] -pub struct LogQueryLikeWithExtendedEnumeration { - pub raw_query: L, - pub extended_timestamp: u32, -} - pub fn sort_storage_access_queries<'a, L: LogQueryLike, I: IntoIterator>( unsorted_storage_queries: I, ) -> (Vec>, Vec) { @@ -125,7 +53,9 @@ pub fn sort_storage_access_queries<'a, L: LogQueryLike, I: IntoIterator) -> Vec { + if calldata.len() == 0 { + return vec![]; + } + let mut capacity = calldata.len() / 32; + if calldata.len() % 32 != 0 { + capacity += 1; + } + let mut result = Vec::with_capacity(capacity); + let mut it = calldata.chunks_exact(32); + for el in &mut it { + let el = U256::from_big_endian(el); + result.push(el); + } + let remainder = it.remainder(); + if remainder.len() != 0 { + let mut buffer = [0u8; 32]; + buffer[0..remainder.len()].copy_from_slice(remainder); + let el = U256::from_big_endian(&buffer); + result.push(el); + } + + result +} + +pub fn finalize_queue_state< + F: SmallField, + R: BuildableCircuitRoundFunction + AlgebraicRoundFunction, + const N: usize, +>( + tail: [F; N], + _round_function: &R, +) -> [F; QUEUE_FINAL_STATE_COMMITMENT_LENGTH] { + // rescue prime paddings + let mut to_absorb = vec![]; + to_absorb.extend(tail); + to_absorb.push(F::ONE); + + let mut state = R::initial_state(); + use crate::boojum::algebraic_props::round_function::absorb_into_state_vararg; + absorb_into_state_vararg::(&mut state, &to_absorb); + let commitment = >::state_into_commitment::< + QUEUE_FINAL_STATE_COMMITMENT_LENGTH, + >(&state); + + commitment +} + +pub fn finalized_queue_state_as_bytes( + input: [F; QUEUE_FINAL_STATE_COMMITMENT_LENGTH], +) -> [u8; 32] { + let mut result = [0u8; 32]; + for (dst, src) in result.array_chunks_mut::<8>().zip(input.into_iter()) { + *dst = src.as_u64_reduced().to_be_bytes(); + } + + result +} diff --git a/crates/zk_evm/.github/workflows/ci.yaml b/crates/zk_evm/.github/workflows/ci.yaml index bd002d83..8b2b5191 100644 --- a/crates/zk_evm/.github/workflows/ci.yaml +++ b/crates/zk_evm/.github/workflows/ci.yaml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: stable + toolchain: nightly-2023-04-17 - run: cargo build --verbose --all-features - run: RUSTFLAGS="-Awarnings" cargo test --verbose --all-features diff --git a/crates/zk_evm/Cargo.toml b/crates/zk_evm/Cargo.toml index dbfbd549..ee2bf352 100644 --- a/crates/zk_evm/Cargo.toml +++ b/crates/zk_evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zk_evm" -version = "0.133.0" +version = "0.140.0" edition = "2021" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" diff --git a/crates/zk_evm/src/lib.rs b/crates/zk_evm/src/lib.rs index 80aaa845..407c5833 100644 --- a/crates/zk_evm/src/lib.rs +++ b/crates/zk_evm/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(dropping_references)] - pub mod block_properties; pub mod errors; pub mod flags; @@ -17,9 +15,9 @@ pub use zkevm_opcode_defs::{bitflags, ethereum_types}; use self::ethereum_types::{Address, U256}; -pub use zk_evm_abstractions; pub use zkevm_opcode_defs; +pub use zk_evm_abstractions; pub use zkevm_opcode_defs::blake2; pub use zkevm_opcode_defs::k256; pub use zkevm_opcode_defs::sha2; @@ -33,7 +31,3 @@ pub mod aux_structures { pub use zk_evm_abstractions::aux::*; pub use zk_evm_abstractions::queries::*; } - -pub mod precompiles { - pub use zk_evm_abstractions::precompiles::*; -} diff --git a/crates/zk_evm/src/opcodes/execution/add.rs b/crates/zk_evm/src/opcodes/execution/add.rs index 297c1a76..d53ca886 100644 --- a/crates/zk_evm/src/opcodes/execution/add.rs +++ b/crates/zk_evm/src/opcodes/execution/add.rs @@ -30,6 +30,7 @@ impl> DecodedOpcode { is_pointer: _, } = src1; + use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; let set_flags = self.variant.flags[SET_FLAGS_FLAG_IDX]; vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc; let (result, of) = src0.overflowing_add(src1); diff --git a/crates/zk_evm/src/opcodes/execution/binop.rs b/crates/zk_evm/src/opcodes/execution/binop.rs index 9b834a34..e31a4217 100644 --- a/crates/zk_evm/src/opcodes/execution/binop.rs +++ b/crates/zk_evm/src/opcodes/execution/binop.rs @@ -35,6 +35,8 @@ impl> DecodedOpcode { Opcode::Binop(inner) => inner, _ => unreachable!(), }; + + use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; let set_flags = self.variant.flags[SET_FLAGS_FLAG_IDX]; vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc; // it is always XOR unless flags are set diff --git a/crates/zk_evm/src/opcodes/execution/div.rs b/crates/zk_evm/src/opcodes/execution/div.rs index be5ff041..f09d9b9b 100644 --- a/crates/zk_evm/src/opcodes/execution/div.rs +++ b/crates/zk_evm/src/opcodes/execution/div.rs @@ -29,6 +29,8 @@ impl> DecodedOpcode { value: src1, is_pointer: _, } = src1; + + use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; let set_flags = self.variant.flags[SET_FLAGS_FLAG_IDX]; vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc; if src1.is_zero() { diff --git a/crates/zk_evm/src/opcodes/execution/far_call.rs b/crates/zk_evm/src/opcodes/execution/far_call.rs index d41c8f55..412f9109 100644 --- a/crates/zk_evm/src/opcodes/execution/far_call.rs +++ b/crates/zk_evm/src/opcodes/execution/far_call.rs @@ -1,9 +1,7 @@ use super::*; -use zk_evm_abstractions::aux::MemoryPage; -use zk_evm_abstractions::aux::Timestamp; +use zk_evm_abstractions::aux::*; use zk_evm_abstractions::queries::LogQuery; -use zk_evm_abstractions::vm::SpongeExecutionMarker; use zkevm_opcode_defs::definitions::far_call::*; use zkevm_opcode_defs::system_params::DEPLOYER_SYSTEM_CONTRACT_ADDRESS; use zkevm_opcode_defs::system_params::STORAGE_AUX_BYTE; @@ -22,7 +20,7 @@ bitflags! { const NOT_ENOUGH_ERGS_TO_GROW_MEMORY = 1u64 << 3; const MALFORMED_ABI_QUASI_POINTER = 1u64 << 4; const CALL_IN_NOW_CONSTRUCTED_SYSTEM_CONTRACT = 1u64 << 5; - const NOTE_ENOUGH_ERGS_FOR_EXTRA_FAR_CALL_COSTS = 1u64 << 6; + const NOT_ENOUGH_ERGS_FOR_EXTRA_FAR_CALL_COSTS = 1u64 << 6; } } @@ -146,12 +144,6 @@ impl> DecodedOpcode { let query = vm_state .access_storage(vm_state.local_state.monotonic_cycle_counter, partial_query); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::StorageLogReadOnly, - 1..4, - true, - ); let code_hash_from_storage = query.read_value; // mask for default AA @@ -321,6 +313,11 @@ impl> DecodedOpcode { } }; + // we mask out fat pointer based on: + // - invalid code hash format + // - call yet constructed kernel + // - not fat pointer when expected + // - invalid slice structure in ABI if exceptions.is_empty() == false { far_call_abi.memory_quasi_fat_pointer = FatPointer::empty(); // even though we will not pay for memory resize, @@ -412,7 +409,7 @@ impl> DecodedOpcode { remaining_ergs_after_growth - msg_value_stipend } else { exceptions.set( - FarCallExceptionFlags::NOTE_ENOUGH_ERGS_FOR_EXTRA_FAR_CALL_COSTS, + FarCallExceptionFlags::NOT_ENOUGH_ERGS_FOR_EXTRA_FAR_CALL_COSTS, true, ); // if tried to take and failed, but should not add it later on in this case @@ -448,12 +445,6 @@ impl> DecodedOpcode { memory_page_candidate_for_code_decommittment, timestamp_for_decommit, )?; - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::DecommittmentQuery, - 4..5, - true, - ); if processed_decommittment_query.is_fresh == false { // refund @@ -508,8 +499,6 @@ impl> DecodedOpcode { let new_context_is_static = current_stack.is_static | is_static_call; // no matter if we did execute a query or not, we need to save context at worst - vm_state.local_state.pending_port.pending_type = Some(PendingType::FarCall); - vm_state.increment_memory_pages_on_call(); // read address for mimic_call @@ -578,15 +567,6 @@ impl> DecodedOpcode { Timestamp(vm_state.local_state.timestamp), ); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::CallstackPush, - 5..8, - true, - ); - // mark the jump to refresh the memory word - vm_state.local_state.did_call_or_ret_recently = true; - // write down calldata information let r1_value = PrimitiveValue { diff --git a/crates/zk_evm/src/opcodes/execution/log.rs b/crates/zk_evm/src/opcodes/execution/log.rs index 5913d4b8..d99ca783 100644 --- a/crates/zk_evm/src/opcodes/execution/log.rs +++ b/crates/zk_evm/src/opcodes/execution/log.rs @@ -1,8 +1,6 @@ use super::*; -use zk_evm_abstractions::aux::Timestamp; use zk_evm_abstractions::queries::LogQuery; -use zk_evm_abstractions::vm::SpongeExecutionMarker; use zkevm_opcode_defs::{ LogOpcode, Opcode, PrecompileCallABI, PrecompileCallInnerABI, FIRST_MESSAGE_FLAG_IDX, }; @@ -68,6 +66,7 @@ impl> DecodedOpcode { .get_current_stack() .ergs_remaining; let is_rollup = shard_id == 0; + let timestamp_for_log = vm_state.timestamp_for_first_decommit_or_precompile_read(); let tx_number_in_block = vm_state.local_state.tx_number_in_block; @@ -86,7 +85,7 @@ impl> DecodedOpcode { let partial_query = LogQuery { timestamp: timestamp_for_log, - tx_number_in_block, + tx_number_in_block: tx_number_in_block, aux_byte: STORAGE_AUX_BYTE, shard_id, address, @@ -180,12 +179,6 @@ impl> DecodedOpcode { // we do not expect refunds for reads yet let query = vm_state .access_storage(vm_state.local_state.monotonic_cycle_counter, partial_query); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::StorageLogReadOnly, - 1..4, - false, - ); let result = PrimitiveValue { value: query.read_value, is_pointer: false, @@ -222,14 +215,6 @@ impl> DecodedOpcode { // we still do a formal query to execute write and record witness let _query = vm_state .access_storage(vm_state.local_state.monotonic_cycle_counter, partial_query); - - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::StorageLogWrite, - 1..5, - true, - ); - vm_state.local_state.pending_port.pending_type = Some(PendingType::WriteLog); } variant @ LogOpcode::Event | variant @ LogOpcode::ToL1Message => { if not_enough_power { @@ -261,13 +246,6 @@ impl> DecodedOpcode { is_service: is_first_message, }; vm_state.emit_event(vm_state.local_state.monotonic_cycle_counter, query); - vm_state.local_state.pending_port.pending_type = Some(PendingType::WriteLog); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::StorageLogWrite, - 1..5, - true, - ); } LogOpcode::PrecompileCall => { // add extra information about precompile abi in the "key" field @@ -343,12 +321,6 @@ impl> DecodedOpcode { is_service: is_first_message, }; vm_state.call_precompile(vm_state.local_state.monotonic_cycle_counter, query); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::StorageLogReadOnly, - 1..4, - false, - ); let result = PrimitiveValue { value: U256::from(1u64), is_pointer: false, diff --git a/crates/zk_evm/src/opcodes/execution/mod.rs b/crates/zk_evm/src/opcodes/execution/mod.rs index 09814811..1fee64f8 100644 --- a/crates/zk_evm/src/opcodes/execution/mod.rs +++ b/crates/zk_evm/src/opcodes/execution/mod.rs @@ -3,8 +3,6 @@ use crate::vm_state::*; use zkevm_opcode_defs::decoding::AllowedPcOrImm; use zkevm_opcode_defs::decoding::VmEncodingMode; -use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; - pub mod add; pub mod binop; pub mod context; diff --git a/crates/zk_evm/src/opcodes/execution/mul.rs b/crates/zk_evm/src/opcodes/execution/mul.rs index 49469ce0..21f44157 100644 --- a/crates/zk_evm/src/opcodes/execution/mul.rs +++ b/crates/zk_evm/src/opcodes/execution/mul.rs @@ -29,6 +29,8 @@ impl> DecodedOpcode { value: src1, is_pointer: _, } = src1; + + use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; let set_flags = self.variant.flags[SET_FLAGS_FLAG_IDX]; vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc; let tmp = src0.full_mul(src1).0; diff --git a/crates/zk_evm/src/opcodes/execution/near_call.rs b/crates/zk_evm/src/opcodes/execution/near_call.rs index 8549893a..fbe7eed9 100644 --- a/crates/zk_evm/src/opcodes/execution/near_call.rs +++ b/crates/zk_evm/src/opcodes/execution/near_call.rs @@ -1,4 +1,3 @@ -use zk_evm_abstractions::vm::SpongeExecutionMarker; use zkevm_opcode_defs::NearCallABI; use super::*; @@ -67,11 +66,5 @@ impl> DecodedOpcode { // perform some extra steps to ensure that our rollbacks are properly written and saved // both in storage and for witness vm_state.start_frame(vm_state.local_state.monotonic_cycle_counter, new_stack); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::CallstackPush, - 1..4, - false, - ); } } diff --git a/crates/zk_evm/src/opcodes/execution/ret.rs b/crates/zk_evm/src/opcodes/execution/ret.rs index 97b0c517..0803f21b 100644 --- a/crates/zk_evm/src/opcodes/execution/ret.rs +++ b/crates/zk_evm/src/opcodes/execution/ret.rs @@ -1,7 +1,6 @@ use super::*; use zk_evm_abstractions::aux::Timestamp; -use zk_evm_abstractions::vm::SpongeExecutionMarker; use zkevm_opcode_defs::definitions::ret::*; use zkevm_opcode_defs::FatPointerValidationException; use zkevm_opcode_defs::{FatPointer, Opcode, RetABI, RetForwardPageType, RetOpcode}; @@ -36,13 +35,6 @@ impl> DecodedOpcode { let ret_abi = RetABI::from_u256(src0); // we want to mark with one that was will become a new current (taken from stack) - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::CallstackPop, - 1..4, - false, - ); - let RetABI { mut memory_quasi_fat_pointer, page_forwarding_mode, @@ -208,7 +200,6 @@ impl> DecodedOpcode { Timestamp(vm_state.local_state.timestamp), ); - vm_state.local_state.did_call_or_ret_recently = true; vm_state.local_state.registers[RET_IMPLICIT_RETURNDATA_PARAMS_REGISTER as usize] = PrimitiveValue { value: returndata_fat_pointer.to_u256(), diff --git a/crates/zk_evm/src/opcodes/execution/shift.rs b/crates/zk_evm/src/opcodes/execution/shift.rs index df4e31aa..010181a9 100644 --- a/crates/zk_evm/src/opcodes/execution/shift.rs +++ b/crates/zk_evm/src/opcodes/execution/shift.rs @@ -38,6 +38,8 @@ impl> DecodedOpcode { Opcode::Shift(inner) => inner, _ => unreachable!(), }; + + use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; let set_flags = self.variant.flags[SET_FLAGS_FLAG_IDX]; vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc; let shift_abs = src1.low_u64() as u8; diff --git a/crates/zk_evm/src/opcodes/execution/sub.rs b/crates/zk_evm/src/opcodes/execution/sub.rs index f876dde0..19aec141 100644 --- a/crates/zk_evm/src/opcodes/execution/sub.rs +++ b/crates/zk_evm/src/opcodes/execution/sub.rs @@ -30,6 +30,7 @@ impl> DecodedOpcode { is_pointer: _, } = src1; + use zkevm_opcode_defs::SET_FLAGS_FLAG_IDX; let set_flags = self.variant.flags[SET_FLAGS_FLAG_IDX]; vm_state.local_state.callstack.get_current_stack_mut().pc = new_pc; let (result, of) = src0.overflowing_sub(src1); diff --git a/crates/zk_evm/src/opcodes/execution/uma.rs b/crates/zk_evm/src/opcodes/execution/uma.rs index 3cf5feb7..72b07b77 100644 --- a/crates/zk_evm/src/opcodes/execution/uma.rs +++ b/crates/zk_evm/src/opcodes/execution/uma.rs @@ -1,5 +1,5 @@ use zk_evm_abstractions::aux::*; -use zk_evm_abstractions::vm::{MemoryType, SpongeExecutionMarker}; +use zk_evm_abstractions::vm::MemoryType; use super::*; use zkevm_opcode_defs::{FatPointer, Opcode, UMAOpcode, UMA_INCREMENT_FLAG_IDX}; @@ -262,17 +262,8 @@ impl> DecodedOpcode { }; let word_0_read_value = if skip_memory_access == false { - let word_0_query = vm_state.read_memory( - vm_state.local_state.monotonic_cycle_counter, - key_0, - /* is_pended */ false, - ); - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 1..2, - /* is_pended */ false, - ); + let word_0_query = + vm_state.read_memory(vm_state.local_state.monotonic_cycle_counter, key_0); let word_0_read_value = word_0_query.value; @@ -287,18 +278,8 @@ impl> DecodedOpcode { timestamp: timestamp_to_read, }; - let word_1_query = vm_state.read_memory( - vm_state.local_state.monotonic_cycle_counter, - key_1, - /* is_pended */ true, - ); - - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 3..4, - /* is_pended */ true, - ); + let word_1_query = + vm_state.read_memory(vm_state.local_state.monotonic_cycle_counter, key_1); word_1_query.value } else { @@ -394,17 +375,9 @@ impl> DecodedOpcode { vm_state.local_state.monotonic_cycle_counter, key_0, new_word_0_value, - /* is_pended */ false, ); } - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 2..3, - /* is_pended */ false, - ); - // may be write word 1 if is_unaligned && skip_memory_access == false { let key_1 = MemoryKey { @@ -422,17 +395,7 @@ impl> DecodedOpcode { vm_state.local_state.monotonic_cycle_counter, key_1, new_word_1_value, - /* is_pended */ true, ); - - vm_state.witness_tracer.add_sponge_marker( - vm_state.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 4..5, - /* is_pended */ true, - ); - - vm_state.local_state.pending_port.pending_type = Some(PendingType::UMAWrite); } if set_panic == false { diff --git a/crates/zk_evm/src/reference_impls/memory.rs b/crates/zk_evm/src/reference_impls/memory.rs index bbb11c5e..59eb9b07 100644 --- a/crates/zk_evm/src/reference_impls/memory.rs +++ b/crates/zk_evm/src/reference_impls/memory.rs @@ -12,8 +12,6 @@ use zkevm_opcode_defs::{FatPointer, BOOTLOADER_CALLDATA_PAGE}; use super::*; -const MAX_HEAP_PAGE_SIZE_IN_WORDS: usize = (u16::MAX as usize) / 32; - pub struct ReusablePool< T: Sized, InitFn: Fn() -> T, @@ -173,7 +171,7 @@ pub struct SimpleMemory { } fn heap_init() -> Vec { - vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS] + vec![U256::zero(); 1 << 10] } fn stack_init() -> Vec { @@ -185,7 +183,6 @@ fn heap_on_pull(_el: &mut Vec) -> () {} fn stack_on_pull(_el: &mut Vec) -> () {} fn heap_on_return(el: &mut Vec) -> () { - assert_eq!(el.len(), MAX_HEAP_PAGE_SIZE_IN_WORDS); el.fill(U256::zero()); } @@ -194,6 +191,14 @@ fn stack_on_return(el: &mut Vec) -> () { el.fill(PrimitiveValue::empty()); } +fn resize_to_fit(el: &mut Vec, idx: usize) { + if el.len() >= idx + 1 { + return; + } + + el.resize(idx + 1, U256::zero()); +} + // as usual, if we rollback the current frame then we apply changes to storage immediately, // otherwise we carry rollbacks to the parent's frames @@ -222,16 +227,14 @@ impl SimpleMemory { // this one virtually exists always new.code_pages .insert(0u32, vec![U256::zero(); MAX_CODE_PAGE_SIZE_IN_WORDS]); - new.pages_with_extended_lifetime.insert( - BOOTLOADER_CALLDATA_PAGE, - vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS], - ); + new.pages_with_extended_lifetime + .insert(BOOTLOADER_CALLDATA_PAGE, vec![U256::zero(); 1 << 10]); new.page_numbers_indirections.insert(0, Indirection::Empty); // quicker lookup new.indirections_to_cleanup_on_return .push(HashSet::with_capacity_and_hasher(4, S::default())); new.heaps.push(( - (0u32, vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS]), - (0u32, vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS]), + (0u32, vec![U256::zero(); 1 << 10]), + (0u32, vec![U256::zero(); 1 << 20]), )); // formally, so we can access "last" new @@ -252,17 +255,12 @@ impl SimpleMemory { // this one virtually exists always new.code_pages .insert(0u32, vec![U256::zero(); MAX_CODE_PAGE_SIZE_IN_WORDS]); - new.pages_with_extended_lifetime.insert( - BOOTLOADER_CALLDATA_PAGE, - vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS], - ); + new.pages_with_extended_lifetime + .insert(BOOTLOADER_CALLDATA_PAGE, vec![]); new.page_numbers_indirections.insert(0, Indirection::Empty); // quicker lookup new.indirections_to_cleanup_on_return .push(HashSet::with_capacity_and_hasher(4, S::default())); - new.heaps.push(( - (0u32, vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS]), - (0u32, vec![U256::zero(); MAX_HEAP_PAGE_SIZE_IN_WORDS]), - )); // formally, so we can access "last" + new.heaps.push(((0u32, vec![]), (0u32, vec![]))); // formally, so we can access "last" new } @@ -288,20 +286,11 @@ impl SimpleMemory { // Can never populate stack or aux heap pub fn populate_heap(&mut self, values: Vec) { let heaps_data = self.heaps.last_mut().unwrap(); - let len = values.len(); - assert!(len <= MAX_HEAP_PAGE_SIZE_IN_WORDS); - let mut values = values; - values.resize(MAX_HEAP_PAGE_SIZE_IN_WORDS, U256::zero()); heaps_data.0 .1 = values; } pub fn polulate_bootloaders_calldata(&mut self, values: Vec) { - let len = values.len(); - assert!(len <= MAX_HEAP_PAGE_SIZE_IN_WORDS); - let mut values = values; - values.resize(MAX_HEAP_PAGE_SIZE_IN_WORDS, U256::zero()); - *self .pages_with_extended_lifetime .get_mut(&BOOTLOADER_CALLDATA_PAGE) @@ -406,7 +395,7 @@ impl SimpleMemory { } pub fn dump_full_page(&self, page_number: u32) -> Vec<[u8; 32]> { - let upper_bound = MAX_HEAP_PAGE_SIZE_IN_WORDS as u32; + let upper_bound = 1 << 10; self.dump_page_content(page_number, 0..upper_bound) } } @@ -428,10 +417,20 @@ impl Memory for SimpleMemory { value: query.value, is_pointer: query.value_is_pointer, }; + assert!( + (query.location.index.0 as usize) < page.len(), + "out of bounds for stack page for query {:?}", + query + ); page[query.location.index.0 as usize] = primitive } else { let (idx, page) = self.stack_pages.last().unwrap(); assert_eq!(*idx, page_number); + assert!( + (query.location.index.0 as usize) < page.len(), + "out of bounds for stack page for query {:?}", + query + ); let primitive = page[query.location.index.0 as usize]; query.value = primitive.value; query.value_is_pointer = primitive.is_pointer; @@ -446,9 +445,11 @@ impl Memory for SimpleMemory { ) = self.heaps.last_mut().unwrap(); if a == MemoryType::Heap { debug_assert_eq!(*current_heap_page, query.location.page.0); + resize_to_fit(current_heap_content, query.location.index.0 as usize); current_heap_content[query.location.index.0 as usize] = query.value; } else if a == MemoryType::AuxHeap { debug_assert_eq!(*current_aux_heap_page, query.location.page.0); + resize_to_fit(current_aux_heap_content, query.location.index.0 as usize); current_aux_heap_content[query.location.index.0 as usize] = query.value; } else { unreachable!() @@ -457,12 +458,14 @@ impl Memory for SimpleMemory { let ( (current_heap_page, current_heap_content), (current_aux_heap_page, current_aux_heap_content), - ) = self.heaps.last().unwrap(); + ) = self.heaps.last_mut().unwrap(); if a == MemoryType::Heap { debug_assert_eq!(*current_heap_page, query.location.page.0); + resize_to_fit(current_heap_content, query.location.index.0 as usize); query.value = current_heap_content[query.location.index.0 as usize]; } else if a == MemoryType::AuxHeap { debug_assert_eq!(*current_aux_heap_page, query.location.page.0); + resize_to_fit(current_aux_heap_content, query.location.index.0 as usize); query.value = current_aux_heap_content[query.location.index.0 as usize]; } else { unreachable!() @@ -477,23 +480,39 @@ impl Memory for SimpleMemory { .get(&page_number) .expect("fat pointer only points to reachable memory"); + // NOTE: we CAN have a situation when e.g. callee returned part of the heap that + // was NEVER written into, so it's page would NOT be resized to the index which we try + // to access (even though fat pointe IS in bounds), so we need to use .get() match indirection { Indirection::Heap(index) => { let forwarded_heap_data = &self.heaps[*index]; assert_eq!(forwarded_heap_data.0 .0, query.location.page.0); - query.value = forwarded_heap_data.0 .1[query.location.index.0 as usize]; + query.value = forwarded_heap_data + .0 + .1 + .get(query.location.index.0 as usize) + .copied() + .unwrap_or(U256::zero()); } Indirection::AuxHeap(index) => { let forwarded_heap_data = &self.heaps[*index]; assert_eq!(forwarded_heap_data.1 .0, query.location.page.0); - query.value = forwarded_heap_data.1 .1[query.location.index.0 as usize]; + query.value = forwarded_heap_data + .1 + .1 + .get(query.location.index.0 as usize) + .copied() + .unwrap_or(U256::zero()); } Indirection::ReturndataExtendedLifetime => { let page = self .pages_with_extended_lifetime .get(&page_number) .expect("indirection target must exist"); - query.value = page[query.location.index.0 as usize]; + query.value = page + .get(query.location.index.0 as usize) + .copied() + .unwrap_or(U256::zero()); } Indirection::Empty => { query.value = U256::zero(); diff --git a/crates/zk_evm/src/vm_state/cycle.rs b/crates/zk_evm/src/vm_state/cycle.rs index 2ab86c61..faca3cbc 100644 --- a/crates/zk_evm/src/vm_state/cycle.rs +++ b/crates/zk_evm/src/vm_state/cycle.rs @@ -2,10 +2,7 @@ use super::*; use crate::opcodes::parsing::*; use tracing::*; -use zk_evm_abstractions::{ - aux::*, - vm::{MemoryType, SpongeExecutionMarker}, -}; +use zk_evm_abstractions::{aux::*, vm::MemoryType}; use zkevm_opcode_defs::{ImmMemHandlerFlags, NopOpcode, Operand, RegOrImmFlags}; pub struct PreState = EncodingModeProduction> { @@ -18,9 +15,6 @@ pub struct PreState = EncodingModeProdu pub const OPCODES_PER_WORD_LOG_2: usize = 2; pub const OPCODES_PER_WORD: usize = 1 << OPCODES_PER_WORD_LOG_2; -pub const READ_OPCODE_SPONGE_IDX: usize = 0; -pub const READ_SRC_FROM_MEMORY_SPONGE_IDX: usize = 1; -pub const READ_DST_FROM_MEMORY_SPONGE_IDX: usize = 2; pub fn read_and_decode< const N: usize, @@ -48,18 +42,21 @@ pub fn read_and_decode< tracer.before_decoding(local_state, memory); } - let skip_cycle = local_state.pending_port.is_any_pending() || local_state.execution_has_ended(); - delayed_changes.reset_pending_port = local_state.pending_port.is_any_pending(); - + let execution_has_ended = local_state.execution_has_ended(); let pending_exception = local_state.pending_exception; + let code_page = local_state.callstack.get_current_stack().code_page; + delayed_changes.new_previous_code_memory_page = Some(code_page); + + let pc = local_state.callstack.get_current_stack().pc; + let previous_super_pc = local_state.previous_super_pc; + let code_pages_are_different = local_state.callstack.get_current_stack().code_page + != local_state.previous_code_memory_page; + let (super_pc, sub_pc) = E::split_pc(pc); + // if we do not skip cycle then we read memory for a new opcode - let opcode_encoding = if !skip_cycle && !pending_exception { - let pc = local_state.callstack.get_current_stack().pc; - let previous_super_pc = local_state.previous_super_pc; - let did_call_or_ret_recently = local_state.did_call_or_ret_recently; - let (super_pc, sub_pc) = E::split_pc(pc); - let raw_opcode_u64 = match (did_call_or_ret_recently, previous_super_pc == super_pc) { + let opcode_encoding = if execution_has_ended == false && pending_exception == false { + let raw_opcode_u64 = match (code_pages_are_different, previous_super_pc == super_pc) { (true, _) | (false, false) => { // we need to read the code word and select a proper subword let code_page = local_state.callstack.get_current_stack().code_page; @@ -72,7 +69,8 @@ pub fn read_and_decode< timestamp: local_state.timestamp_for_code_or_src_read(), location, }; - delayed_changes.reset_did_call_or_ret_recently = true; + + delayed_changes.new_previous_code_memory_page = Some(code_page); // code read is never pending let code_query = read_code( @@ -80,13 +78,6 @@ pub fn read_and_decode< witness_tracer, local_state.monotonic_cycle_counter, key, - /* is_pended */ false, - ); - witness_tracer.add_sponge_marker( - local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 0..1, - /* is_pended */ false, ); let u256_word = code_query.value; delayed_changes.new_previous_code_word = Some(u256_word); @@ -110,32 +101,33 @@ pub fn read_and_decode< }; raw_opcode_u64 - } else if !skip_cycle && pending_exception { + } else if pending_exception { // there are no cases that set pending exception and // simultaneously finish the execution - assert!(local_state.execution_has_ended() == false); - - // note that we do reset PC in VM for simplicity, so we do it here too - let pc = local_state.callstack.get_current_stack().pc; - let (super_pc, _) = E::split_pc(pc); - delayed_changes.new_previous_super_pc = Some(super_pc); + assert!(execution_has_ended == false); // so we can just remove the marker as soon as we are no longer pending delayed_changes.new_pending_exception = Some(false); + // anyway update super PC + delayed_changes.new_previous_super_pc = Some(super_pc); + E::exception_revert_encoding() } else { // we are skipping cycle for some reason, so we do nothing, // and do not touch any flags + // This only happens at the end of execution + if local_state.execution_has_ended() { assert!(pending_exception == false); - delayed_changes.reset_did_call_or_ret_recently = true; } E::nop_encoding() }; + let skip_cycle = execution_has_ended; + // now we have some candidate for opcode. If it's noop we are not expecting to have any problems, // so check for other meaningful exceptions @@ -158,7 +150,6 @@ pub fn read_and_decode< // we have already paid for it ergs_cost = 0; } - let (mut ergs_remaining, not_enough_power) = local_state .callstack .get_current_stack() @@ -235,7 +226,7 @@ pub fn read_and_decode< opcode_masked: partially_decoded, error_flags_accumulated: error_flags, resolved_condition, - did_skip_cycle: skip_cycle, + did_skip_cycle: false, }; tracer.after_decoding(local_state, data, memory); @@ -319,24 +310,10 @@ impl< // src read is never pending, but to keep consistent memory implementation we // need to branch here for a case of loading constants from code space let src0_query = if src0_mem_location.memory_type == MemoryType::Code { - self.read_code( - self.local_state.monotonic_cycle_counter, - key, - /* is_pended */ false, - ) + self.read_code(self.local_state.monotonic_cycle_counter, key) } else { - self.read_memory( - self.local_state.monotonic_cycle_counter, - key, - /* is_pended */ false, - ) + self.read_memory(self.local_state.monotonic_cycle_counter, key) }; - self.witness_tracer.add_sponge_marker( - self.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 1..2, - /* is_pended */ false, - ); let u256_word = src0_query.value; let is_pointer = src0_query.value_is_pointer; @@ -405,10 +382,6 @@ impl< after_masking_decoded.apply(self, prestate)?; - if self.local_state.pending_port.is_any_pending() { - debug_assert!(self.local_state.pending_cycles_left.is_none()); - } - if !skip_cycle { self.increment_timestamp_after_cycle(); } diff --git a/crates/zk_evm/src/vm_state/helpers.rs b/crates/zk_evm/src/vm_state/helpers.rs index 97f78320..ab7129e2 100644 --- a/crates/zk_evm/src/vm_state/helpers.rs +++ b/crates/zk_evm/src/vm_state/helpers.rs @@ -4,7 +4,7 @@ use super::*; use zk_evm_abstractions::aux::{MemoryKey, MemoryLocation}; use zk_evm_abstractions::queries::{DecommittmentQuery, LogQuery, MemoryQuery}; -use zk_evm_abstractions::vm::{RefundType, SpongeExecutionMarker}; +use zk_evm_abstractions::vm::RefundType; use zkevm_opcode_defs::UNMAPPED_PAGE; pub fn read_code< @@ -17,7 +17,6 @@ pub fn read_code< witness_tracer: &mut WT, monotonic_cycle_counter: u32, key: MemoryKey, - _is_pended: bool, ) -> MemoryQuery { let MemoryKey { location, @@ -52,12 +51,7 @@ impl< E: VmEncodingMode, > VmState { - pub fn read_memory( - &mut self, - monotonic_cycle_counter: u32, - key: MemoryKey, - _is_pended: bool, - ) -> MemoryQuery { + pub fn read_memory(&mut self, monotonic_cycle_counter: u32, key: MemoryKey) -> MemoryQuery { let MemoryKey { location, timestamp, @@ -82,18 +76,12 @@ impl< query } - pub fn read_code( - &mut self, - monotonic_cycle_counter: u32, - key: MemoryKey, - is_pended: bool, - ) -> MemoryQuery { + pub fn read_code(&mut self, monotonic_cycle_counter: u32, key: MemoryKey) -> MemoryQuery { read_code( &mut self.memory, &mut self.witness_tracer, monotonic_cycle_counter, key, - is_pended, ) } @@ -102,7 +90,6 @@ impl< monotonic_cycle_counter: u32, key: MemoryKey, value: PrimitiveValue, - _is_pended: bool, ) -> MemoryQuery { let MemoryKey { location, @@ -288,19 +275,7 @@ impl< location, timestamp: self.timestamp_for_dst_write(), }; - let _dst0_query = self.write_memory( - monotonic_cycle_counter, - key, - value, - /* is_pended */ false, - ); // no pending on dst0 writes - - self.witness_tracer.add_sponge_marker( - self.local_state.monotonic_cycle_counter, - SpongeExecutionMarker::MemoryQuery, - 2..3, - /* is_pended */ false, - ); + let _dst0_query = self.write_memory(monotonic_cycle_counter, key, value); } else { self.update_register_value(opcode.dst0_reg_idx, value); } diff --git a/crates/zk_evm/src/vm_state/mod.rs b/crates/zk_evm/src/vm_state/mod.rs index e174ae51..536b32bd 100644 --- a/crates/zk_evm/src/vm_state/mod.rs +++ b/crates/zk_evm/src/vm_state/mod.rs @@ -55,6 +55,7 @@ impl PrimitiveValue { #[derive(Clone, Debug, PartialEq)] pub struct VmLocalState = EncodingModeProduction> { pub previous_code_word: U256, + pub previous_code_memory_page: MemoryPage, pub registers: [PrimitiveValue; zkevm_opcode_defs::REGISTERS_COUNT], pub flags: Flags, pub timestamp: u32, @@ -67,19 +68,17 @@ pub struct VmLocalState = EncodingModeP pub absolute_execution_step: u32, pub current_ergs_per_pubdata_byte: u32, pub tx_number_in_block: u16, - pub did_call_or_ret_recently: bool, pub pending_exception: bool, pub previous_super_pc: E::PcOrImm, pub context_u128_register: u128, pub callstack: Callstack, - pub pending_port: SpongePendingPort, - pub pending_cycles_left: Option, } impl> VmLocalState { pub fn empty_state() -> Self { Self { previous_code_word: U256::zero(), + previous_code_memory_page: MemoryPage(0u32), registers: [PrimitiveValue::empty(); zkevm_opcode_defs::REGISTERS_COUNT], flags: Flags::empty(), timestamp: STARTING_TIMESTAMP, @@ -90,10 +89,7 @@ impl> VmLocalState { current_ergs_per_pubdata_byte: 0, tx_number_in_block: 0, previous_super_pc: E::PcOrImm::from_u64_clipped(0), - did_call_or_ret_recently: true, // to properly start the execution pending_exception: false, - pending_port: SpongePendingPort::empty(), - pending_cycles_left: None, context_u128_register: 0u128, callstack: Callstack::empty(), } @@ -117,35 +113,27 @@ pub struct DelayedLocalStateChanges< const N: usize = 8, E: VmEncodingMode = EncodingModeProduction, > { - pub reset_pending_port: bool, - pub reset_did_call_or_ret_recently: bool, pub new_ergs_remaining: Option, pub new_previous_code_word: Option, pub new_previous_super_pc: Option, pub new_pending_exception: Option, + pub new_previous_code_memory_page: Option, } impl> Default for DelayedLocalStateChanges { fn default() -> Self { Self { - reset_pending_port: false, - reset_did_call_or_ret_recently: false, new_ergs_remaining: None, new_previous_code_word: None, new_previous_super_pc: None, new_pending_exception: None, + new_previous_code_memory_page: None, } } } impl> DelayedLocalStateChanges { pub fn apply(self, local_state: &mut VmLocalState) { - if self.reset_pending_port { - local_state.pending_port.reset(); - } - if self.reset_did_call_or_ret_recently { - local_state.did_call_or_ret_recently = false; - } if let Some(ergs_remaining) = self.new_ergs_remaining { local_state.callstack.get_current_stack_mut().ergs_remaining = ergs_remaining; } @@ -160,6 +148,10 @@ impl> DelayedLocalStateChanges { if let Some(new_pending_exception) = self.new_pending_exception { local_state.pending_exception = new_pending_exception; } + + if let Some(new_previous_code_memory_page) = self.new_previous_code_memory_page { + local_state.previous_code_memory_page = new_previous_code_memory_page; + } } } @@ -218,23 +210,14 @@ impl< pub fn reset_flags(&mut self) { self.local_state.flags.reset(); } - pub fn is_any_pending(&self) -> bool { - self.local_state.pending_port.is_any_pending() - } pub fn callstack_is_full(&self) -> bool { self.local_state.callstack_is_full() } pub fn execution_has_ended(&self) -> bool { self.local_state.execution_has_ended() } - pub fn check_skip_cycles_due_to_pending(&mut self) -> bool { - let should_skip = self.local_state.pending_port.is_any_pending(); - self.local_state.pending_port.reset(); - - should_skip - } pub fn compute_if_should_skip_cycle(&mut self) -> bool { - self.check_skip_cycles_due_to_pending() || self.execution_has_ended() + self.execution_has_ended() } pub fn timestamp_for_code_or_src_read(&self) -> Timestamp { self.local_state.timestamp_for_code_or_src_read() diff --git a/crates/zk_evm/src/witness_trace/mod.rs b/crates/zk_evm/src/witness_trace/mod.rs index c994dd23..d552394f 100644 --- a/crates/zk_evm/src/witness_trace/mod.rs +++ b/crates/zk_evm/src/witness_trace/mod.rs @@ -1,12 +1,11 @@ use zk_evm_abstractions::{ queries::{DecommittmentQuery, LogQuery, MemoryQuery}, - vm::{PrecompileCyclesWitness, RefundType, SpongeExecutionMarker}, + vm::{PrecompileCyclesWitness, RefundType}, }; use zkevm_opcode_defs::decoding::VmEncodingMode; use super::*; use crate::vm_state::{CallStackEntry, VmLocalState}; -use std::ops::Range; #[allow(unused_variables)] pub trait VmWitnessTracer>: Clone + std::fmt::Debug { @@ -16,16 +15,6 @@ pub trait VmWitnessTracer>: Clone + std::fm #[inline] fn end_execution_cycle(&mut self, current_state: &VmLocalState) {} - #[inline] - fn add_sponge_marker( - &mut self, - monotonic_cycle_counter: u32, - marker: SpongeExecutionMarker, - sponges_range: Range, - is_pended: bool, - ) { - } - #[inline] fn add_memory_query(&mut self, monotonic_cycle_counter: u32, memory_query: MemoryQuery) {} diff --git a/crates/zkevm_circuits/.github/ISSUE_TEMPLATE/bug_report.md b/crates/zkevm_circuits/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..2d3e38a6 --- /dev/null +++ b/crates/zkevm_circuits/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Use this template for reporting issues +title: '' +labels: bug +assignees: '' +--- + +### 🐛 Bug Report + +#### 📝 Description + +Provide a clear and concise description of the bug. + +#### 🔄 Reproduction Steps + +Steps to reproduce the behaviour + +#### 🤔 Expected Behavior + +Describe what you expected to happen. + +#### 😯 Current Behavior + +Describe what actually happened. + +#### 🖥️ Environment + +Any relevant environment details. + +#### 📋 Additional Context + +Add any other context about the problem here. If applicable, add screenshots to help explain. + +#### 📎 Log Output + +``` +Paste any relevant log output here. +``` diff --git a/crates/zkevm_circuits/.github/ISSUE_TEMPLATE/feature_request.md b/crates/zkevm_circuits/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..d921e066 --- /dev/null +++ b/crates/zkevm_circuits/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Use this template for requesting features +title: '' +labels: feat +assignees: '' +--- + +### 🌟 Feature Request + +#### 📝 Description + +Provide a clear and concise description of the feature you'd like to see. + +#### 🤔 Rationale + +Explain why this feature is important and how it benefits the project. + +#### 📋 Additional Context + +Add any other context or information about the feature request here. diff --git a/crates/zkevm_circuits/.github/pull_request_template.md b/crates/zkevm_circuits/.github/pull_request_template.md new file mode 100644 index 00000000..8ce206c8 --- /dev/null +++ b/crates/zkevm_circuits/.github/pull_request_template.md @@ -0,0 +1,20 @@ +# What ❔ + + + + + +## Why ❔ + + + + +## Checklist + + + + +- [ ] PR title corresponds to the body of PR (we generate changelog entries from PRs). +- [ ] Tests for the changes have been added / updated. +- [ ] Documentation comments have been added / updated. +- [ ] Code has been formatted via `zk fmt` and `zk lint`. diff --git a/crates/zkevm_circuits/.github/workflows/cargo-license.yaml b/crates/zkevm_circuits/.github/workflows/cargo-license.yaml new file mode 100644 index 00000000..189b4716 --- /dev/null +++ b/crates/zkevm_circuits/.github/workflows/cargo-license.yaml @@ -0,0 +1,8 @@ +name: Cargo license check +on: pull_request +jobs: + cargo-deny: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 diff --git a/crates/zkevm_circuits/.github/workflows/ci.yaml b/crates/zkevm_circuits/.github/workflows/ci.yaml new file mode 100644 index 00000000..691210cd --- /dev/null +++ b/crates/zkevm_circuits/.github/workflows/ci.yaml @@ -0,0 +1,27 @@ +name: "Rust CI" +on: + pull_request: + +jobs: + build: + name: cargo build and test + runs-on: [ubuntu-22.04-github-hosted-32core] + steps: + - uses: actions/checkout@v3 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly-2023-09-20 + rustflags: "" + - run: cargo build --verbose --release + - run: cargo test --verbose --release --all + + formatting: + name: cargo fmt + runs-on: [ubuntu-latest] + steps: + - uses: actions/checkout@v3 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt + - name: Rustfmt Check + uses: actions-rust-lang/rustfmt@v1 diff --git a/crates/zkevm_circuits/.github/workflows/secrets_scanner.yaml b/crates/zkevm_circuits/.github/workflows/secrets_scanner.yaml new file mode 100644 index 00000000..54054cf7 --- /dev/null +++ b/crates/zkevm_circuits/.github/workflows/secrets_scanner.yaml @@ -0,0 +1,17 @@ +name: Leaked Secrets Scan +on: [pull_request] +jobs: + TruffleHog: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3 + with: + fetch-depth: 0 + - name: TruffleHog OSS + uses: trufflesecurity/trufflehog@0c66d30c1f4075cee1aada2e1ab46dabb1b0071a + with: + path: ./ + base: ${{ github.event.repository.default_branch }} + head: HEAD + extra_args: --debug --only-verified diff --git a/crates/zkevm_circuits/.gitignore b/crates/zkevm_circuits/.gitignore new file mode 100644 index 00000000..3ab5292b --- /dev/null +++ b/crates/zkevm_circuits/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +.idea diff --git a/crates/zkevm_circuits/CONTRIBUTING.md b/crates/zkevm_circuits/CONTRIBUTING.md new file mode 100644 index 00000000..dd3d4584 --- /dev/null +++ b/crates/zkevm_circuits/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# 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! + +## Ways to contribute + +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 diff --git a/crates/zkevm_circuits/Cargo.toml b/crates/zkevm_circuits/Cargo.toml new file mode 100644 index 00000000..faa92563 --- /dev/null +++ b/crates/zkevm_circuits/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "zkevm_circuits" +version = "0.140.2" +edition = "2021" +authors = ["The Matter Labs Team "] +homepage = "https://zksync.io/" +repository = "https://github.com/matter-labs/era-zkevm_circuits" +license = "MIT OR Apache-2.0" +keywords = ["blockchain", "zksync"] +categories = ["cryptography"] +description = "ZKsync Era circuits for EraVM" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +derivative = "2" +serde = { version = "1", features = ["derive"] } +rand = "0.4" +smallvec = { version = "1.13", features = [ + "const_generics", + "const_new", + "serde", +] } +arrayvec = "0.7" +bincode = "1.3" +serde_json = "1" +itertools = "0.10" +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. + +[features] +default = [] +log_tracing = ["boojum/log_tracing"] +verbose_circuits = [] diff --git a/crates/zkevm_circuits/LICENSE-APACHE b/crates/zkevm_circuits/LICENSE-APACHE new file mode 100644 index 00000000..d9a10c0d --- /dev/null +++ b/crates/zkevm_circuits/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/zkevm_circuits/LICENSE-MIT b/crates/zkevm_circuits/LICENSE-MIT new file mode 100644 index 00000000..2739ea6e --- /dev/null +++ b/crates/zkevm_circuits/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Matter Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/zkevm_circuits/README.md b/crates/zkevm_circuits/README.md new file mode 100644 index 00000000..f92a928d --- /dev/null +++ b/crates/zkevm_circuits/README.md @@ -0,0 +1,33 @@ +# CPU/GPU Based Prover for zkSync Era + +[![Logo](eraLogo.png)](https://zksync.io/) + +zkSync Era is a layer 2 rollup that uses zero-knowledge proofs to scale Ethereum without compromising on security or +decentralization. Since it's EVM compatible (Solidity/Vyper), 99% of Ethereum projects can redeploy without refactoring +or re-auditing a single line of code. zkSync Era also uses an LLVM-based compiler that will eventually let developers +write smart contracts in C++, Rust and other popular languages. + +## License + +The zkSync Era prover is distributed under the terms of either + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Official Links + +- [Website](https://zksync.io/) +- [GitHub](https://github.com/matter-labs) +- [Twitter](https://twitter.com/zksync) +- [Twitter for Devs](https://twitter.com/zkSyncDevs) +- [Discord](https://join.zksync.dev) + +## Disclaimer + +zkSync Era has been through lots of testing and audits. Although it is live, it is still in alpha state and will go +through more audits and bug bounties programs. We would love to hear our community's thoughts and suggestions about it! +It is important to state that forking it now can potentially lead to missing important security updates, critical +features, and performance improvements. + diff --git a/crates/zkevm_circuits/SECURITY.md b/crates/zkevm_circuits/SECURITY.md new file mode 100644 index 00000000..2f2871ce --- /dev/null +++ b/crates/zkevm_circuits/SECURITY.md @@ -0,0 +1,74 @@ +# Security Policy + +We truly appreciate efforts to discover and disclose security issues responsibly! + +## Vulnerabilities + +If you'd like to report a security issue in the repositories of matter-labs organization, please proceed to our +[Bug Bounty Program on Immunefi](https://era.zksync.io/docs/reference/troubleshooting/audit-bug-bounty.html#bug-bounty-program). + +## Other Security Issues + +We take an impact-first approach instead of a rules-first approach. Therefore, if you believe you found the impactful +issue but can't report it via the Bug Bounty, please email us at +[security@matterlabs.dev](mailto:security@matterlabs.dev). + +### PGP Key + +The following PGP key may be used to communicate sensitive information to developers: + +Fingerprint: `5FED B2D0 EA2C 4906 DD66 71D7 A2C5 0B40 CE3C F297` + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGEBmQkBEAD6tlkBEZFMvR8kOgxXX857nC2+oTik6TopJz4uCskuqDaeldMy +l+26BBzLkIeO1loS+bzVgnNFJRrGt9gv98MzNEHJVv6D7GsSLlUX/pz7Lxn0J4ry +o5XIk3MQTCUBdaXGs6GBLl5Xe8o+zNj4MKd4zjgDLinITNlE/YZCDsXyvYS3YFTQ +cwaUTNlawkKgw4BLaEqwB2JuyEhI9wx5X7ibjFL32sWMolYsNAlzFQzM09HCurTn +q0DYau9kPJARcEk9/DK2iq0z3gMCQ8iRTDaOWd8IbSP3HxcEoM5j5ZVAlULmjmUE +StDaMPLj0Kh01Tesh/j+vjchPXHT0n4zqi1+KOesAOk7SIwLadHfQMTpkU7G2fR1 +BrA5MtlzY+4Rm6o7qu3dpZ+Nc4iM3FUnaQRpvn4g5nTh8vjG94OCzX8DXWrCKyxx +amCs9PLDYOpx84fXYv4frkWpKh2digDSUGKhoHaOSnqyyvu3BNWXBCQZJ20rqEIu +sXOQMxWIoWCOOPRRvrHrKDA2hpoKjs3pGsProfpVRzb9702jhWpTfbDp9WjQlFtX +2ZIDxlwAxcugClgrp5JiUxvhg2A9lDNwCF7r1e68uNv5usBZQVKPJmnvS2nWgKy8 +x9oJsnwrEjxwiRHd34UvfMkwY9RENSJ+NoXqBdS7Lwz4m6vgbzq6K56WPQARAQAB +tCRaa1N5bmMgU2VjdXJpdHkgPHNlY3VyaXR5QHprc3luYy5pbz6JAk4EEwEKADgW +IQRf7bLQ6ixJBt1mcdeixQtAzjzylwUCYQGZCQIbAwULCQgHAgYVCgkICwIEFgID +AQIeAQIXgAAKCRCixQtAzjzyl5y8EAC/T3oq88Dak2b+5TlWdU2Gpm6924eAqlMt +y1KksDezzNQUlPiCUVllpin2PIjU/S+yzMWKXJA04LoVkEPfPOWjAaavLOjRumxu +MR6P2dVUg1InqzYVsJuRhKSpeexzNA5qO2BPM7/I2Iea1IoJPjogGbfXCo0r5kne +KU7a5GEa9eDHxpHTsbphQe2vpQ1239mUJrFpzAvILn6jV1tawMn5pNCXbsa8l6l2 +gtlyQPdOQECy77ZJxrgzaUBcs/RPzUGhwA/qNuvpF0whaCvZuUFMVuCTEu5LZka2 +I9Rixy+3jqBeONBgb+Fiz5phbiMX33M9JQwGONFaxdvpFTerLwPK2N1T8zcufa01 +ypzkWGheScFZemBxUwXwK4x579wjsnfrY11w0p1jtDgPTnLlXUA2mom4+7MyXPg0 +F75qh6vU1pdXaCVkruFgPVtIw+ccw2AxD50iZQ943ZERom9k165dR9+QxOVMXQ4P +VUxsFZWvK70/s8TLjsGljvSdSOa85iEUqSqh0AlCwIAxLMiDwh5s/ZgiHoIM6Xih +oCpuZyK9p0dn+DF/XkgAZ/S91PesMye3cGm6M5r0tS26aoc2Pk6X37Hha1pRALwo +MOHyaGjc/jjcXXxv6o55ALrOrzS0LQmLZ+EHuteCT15kmeY3kqYJ3og62KgiDvew +dKHENvg7d7kCDQRhAZleARAA6uD6WfdqGeKV5i170+kLsxR3QGav0qGNAbxpSJyn +iHQ8u7mQk3S+ziwN2AAopfBk1je+vCWtEGC3+DWRRfJSjLbtaBG8e6kLP3/cGA75 +qURz6glTG4nl5fcEAa6B1st0OxjVWiSLX3g/yjz8lznQb9awuRjdeHMnyx5DsJUN +d+Iu5KxGupQvKGOMKivSvC8VWk9taaQRpRF+++6stLCDk3ZtlxiopMs3X2jAp6xG +sOBbix1cv9BTsfaiL7XDL/gviqBPXYY5L42x6+jnPo5lROfnlLYkWrv6KZr7HD4k +tRXeaSwxLD2EkUyb16Jpp0be/ofvBtITGUDDLCGBiaXtx/v8d52MARjsyLJSYloj +1yiW01LfAiWHUC4z5jl2T7E7sicrlLH1M8Z6WbuqjdeaYwtfyPA2YCKr/3fn6pIo +D+pYaBSESmhA92P+XVaf5y2BZ6Qf8LveDpWwsVGdBGh9T0raA1ooe1GESLjmIjUa +z5AeQ/uXL5Md9I6bpMUUJYQiH19RPcFlJriI3phXyyf6Wlkk8oVEeCWyzcmw+x1V +deRTvE2x4WIwKGLXRNjin2j1AP7vU2HaNwlPrLijqdyi68+0irRQONoH7Qonr4ca +xWgL+pAaa3dWxf0xqK7uZFp4aTVWlr2uXtV/eaUtLmGMCU0jnjb109wg5L0F7WRT +PfEAEQEAAYkCNgQYAQoAIBYhBF/tstDqLEkG3WZx16LFC0DOPPKXBQJhAZleAhsM +AAoJEKLFC0DOPPKXAAEP/jK7ch9GkoaYlsuqY/aHtxEwVddUDOxjyn3FMDoln85L +/n8AmLQb2bcpKSqpaJwMbmfEyr5MDm8xnsBTfx3u6kgaLOWfKxjLQ6PM7kgIMdi4 +bfaRRuSEI1/R6c/hNpiGnzAeeexldH1we+eH1IVmh4crdat49S2xh7Qlv9ahvgsP +LfKl3rJ+aaX/Ok0AHzhvSfhFpPr1gAaGeaRt+rhlZsx2QyG4Ez8p2nDAcAzPiB3T +73ENoBIX6mTPfPm1UgrRyFKBqtUzAodz66j3r6ebBlWzIRg8iZenVMAxzjINAsxN +w1Bzfgsi5ZespfsSlmEaa7jJkqqDuEcLa2YuiFAue7Euqwz1aGeq1GfTicQioSCb +Ur/LGyz2Mj3ykbaP8p5mFVcUN51yQy6OcpvR/W1DfRT9SHFT/bCf9ixsjB2HlZGo +uxPJowwqmMgHd755ZzPDUM9YDgLI1yXdcYshObv3Wq537JAxnZJCGRK4Y8SwrMSh +8WRxlaM0AGWXiJFIDD4bQPIdnF3X8w0cGWE5Otkb8mMHOT+rFTVlDODwm1zF6oIG +PTwfVrpiZBwiUtfJol1exr/MzSPyGoJnYs3cRf2E3O+D1LbcR8w0LbjGuUy38Piz +ZO/vCeyJ3JZC5kE8nD+XBA4idwzh0BKEfH9t+WchQ3Up9rxyzLyQamoqt5Xby4pY +=xkM3 +-----END PGP PUBLIC KEY BLOCK----- +``` diff --git a/crates/zkevm_circuits/cs_derive/.gitignore b/crates/zkevm_circuits/cs_derive/.gitignore new file mode 100644 index 00000000..77147e20 --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.idea/ diff --git a/crates/zkevm_circuits/cs_derive/Cargo.toml b/crates/zkevm_circuits/cs_derive/Cargo.toml new file mode 100644 index 00000000..4a102f46 --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/Cargo.toml @@ -0,0 +1,15 @@ +[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 new file mode 100644 index 00000000..01117831 Binary files /dev/null and b/crates/zkevm_circuits/cs_derive/src/.DS_Store differ diff --git a/crates/zkevm_circuits/cs_derive/src/allocatable/mod.rs b/crates/zkevm_circuits/cs_derive/src/allocatable/mod.rs new file mode 100644 index 00000000..b1d494e7 --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/src/allocatable/mod.rs @@ -0,0 +1,283 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro_error::abort_call_site; +use quote::{quote}; +use syn::{DeriveInput, Data, TypePath, Generics, Fields, TypeArray, GenericParam, Attribute, Type, parse_macro_input, token::{Comma}}; + +use crate::utils::*; + +const SERDE_REMOVE_BOUNDS: &'static str = "SerdeRemoveBounds"; + +pub(crate) fn derive_allocatable(input: proc_macro::TokenStream) -> 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 new file mode 100644 index 00000000..b136eecd --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/src/lib.rs @@ -0,0 +1,100 @@ +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 new file mode 100644 index 00000000..1ebdf95b --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/src/selectable/mod.rs @@ -0,0 +1,105 @@ +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 new file mode 100644 index 00000000..7c076c59 --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/src/utils.rs @@ -0,0 +1,167 @@ +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 new file mode 100644 index 00000000..6a753abe --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/src/var_length_encodable/mod.rs @@ -0,0 +1,110 @@ +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 new file mode 100644 index 00000000..e6fa1a61 --- /dev/null +++ b/crates/zkevm_circuits/cs_derive/src/witness_hook/mod.rs @@ -0,0 +1,124 @@ +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/deny.toml b/crates/zkevm_circuits/deny.toml new file mode 100644 index 00000000..6977d43f --- /dev/null +++ b/crates/zkevm_circuits/deny.toml @@ -0,0 +1,79 @@ +all-features = false +no-default-features = false + +[advisories] +vulnerability = "deny" +unmaintained = "warn" +yanked = "warn" +notice = "warn" +ignore = [ + #"RUSTSEC-0000-0000", +] + +[licenses] +unlicensed = "deny" +allow = [ + #"Apache-2.0 WITH LLVM-exception", + "MIT", + "Apache-2.0", + "ISC", + "Unlicense", + "MPL-2.0", + "Unicode-DFS-2016", + "CC0-1.0", + "BSD-2-Clause", + "BSD-3-Clause", + "Zlib", +] +deny = [ + #"Nokia", +] +copyleft = "warn" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 0.8 +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +unused-allowed-license = "allow" + +[licenses.private] +ignore = false +registries = [ + #"https://sekretz.com/registry +] + +[bans] +multiple-versions = "warn" +wildcards = "allow" +highlight = "all" +workspace-default-features = "allow" +external-default-features = "allow" +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, +] + +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +[sources] +unknown-registry = "deny" +unknown-git = "allow" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] + +[sources.allow-org] +#github = ["matter-labs"] diff --git a/crates/zkevm_circuits/eraLogo.png b/crates/zkevm_circuits/eraLogo.png new file mode 100644 index 00000000..5d9480d8 Binary files /dev/null and b/crates/zkevm_circuits/eraLogo.png differ diff --git a/crates/zkevm_circuits/rust-toolchain.toml b/crates/zkevm_circuits/rust-toolchain.toml new file mode 100644 index 00000000..a671fa6a --- /dev/null +++ b/crates/zkevm_circuits/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2024-05-07" diff --git a/crates/zkevm_circuits/rustfmt.toml b/crates/zkevm_circuits/rustfmt.toml new file mode 100644 index 00000000..d100efd0 --- /dev/null +++ b/crates/zkevm_circuits/rustfmt.toml @@ -0,0 +1 @@ +max_width = 100 \ No newline at end of file diff --git a/crates/zkevm_circuits/src/base_structures/decommit_query/mod.rs b/crates/zkevm_circuits/src/base_structures/decommit_query/mod.rs new file mode 100644 index 00000000..affabdd9 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/decommit_query/mod.rs @@ -0,0 +1,185 @@ +use cs_derive::*; + +use super::*; +use crate::ethereum_types::U256; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::Variable; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt}; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::encodable::{CircuitEncodable, CircuitEncodableExt}; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u32::UInt32; +use boojum::{field::SmallField, gadgets::u256::UInt256}; + +#[derive(Derivative, CSAllocatable, CSSelectable, WitnessHookable, CSVarLengthEncodable)] +#[derivative(Clone, Copy, Debug)] +pub struct DecommitQuery { + pub code_hash: UInt256, + pub page: UInt32, + pub is_first: Boolean, + pub timestamp: UInt32, +} + +pub const DECOMMIT_QUERY_PACKED_WIDTH: usize = 8; + +impl CircuitEncodable for DecommitQuery { + fn encode>( + &self, + cs: &mut CS, + ) -> [Variable; DECOMMIT_QUERY_PACKED_WIDTH] { + debug_assert!(F::CAPACITY_BITS >= 56); + + // we assume that page bytes are known, so it'll be nop anyway + let page_bytes = self.page.decompose_into_bytes(cs); + + let timestamp_bytes = self.timestamp.decompose_into_bytes(cs); + + let v0 = Num::linear_combination( + cs, + &[ + (self.code_hash.inner[0].get_variable(), F::ONE), + ( + page_bytes[0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + page_bytes[1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + page_bytes[2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v1 = Num::linear_combination( + cs, + &[ + (self.code_hash.inner[1].get_variable(), F::ONE), + ( + page_bytes[3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + timestamp_bytes[0].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + timestamp_bytes[1].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v2 = Num::linear_combination( + cs, + &[ + (self.code_hash.inner[2].get_variable(), F::ONE), + ( + timestamp_bytes[2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + timestamp_bytes[3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + self.is_first.get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v3 = self.code_hash.inner[3].get_variable(); + let v4 = self.code_hash.inner[4].get_variable(); + let v5 = self.code_hash.inner[5].get_variable(); + let v6 = self.code_hash.inner[6].get_variable(); + let v7 = self.code_hash.inner[7].get_variable(); + + [v0, v1, v2, v3, v4, v5, v6, v7] + } +} + +impl CSAllocatableExt for DecommitQuery { + const INTERNAL_STRUCT_LEN: usize = 11; + + fn witness_from_set_of_values(values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + let code_hash: U256 = WitnessCastable::cast_from_source([ + values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], + ]); + + let page: u32 = WitnessCastable::cast_from_source(values[8]); + let is_first: bool = WitnessCastable::cast_from_source(values[9]); + let timestamp: u32 = WitnessCastable::cast_from_source(values[10]); + + Self::Witness { + code_hash, + page, + is_first, + timestamp, + } + } + + fn flatten_as_variables(&self) -> [Variable; Self::INTERNAL_STRUCT_LEN] + where + [(); Self::INTERNAL_STRUCT_LEN]:, + { + [ + self.code_hash.inner[0].get_variable(), + self.code_hash.inner[1].get_variable(), + self.code_hash.inner[2].get_variable(), + self.code_hash.inner[3].get_variable(), + self.code_hash.inner[4].get_variable(), + self.code_hash.inner[5].get_variable(), + self.code_hash.inner[6].get_variable(), + self.code_hash.inner[7].get_variable(), + self.page.get_variable(), + self.is_first.get_variable(), + self.timestamp.get_variable(), + ] + } + fn set_internal_variables_values(witness: Self::Witness, dst: &mut DstBuffer<'_, '_, F>) { + // NOTE: must be same sequence as in `flatten_as_variables` + UInt256::set_internal_variables_values(witness.code_hash, dst); + UInt32::set_internal_variables_values(witness.page, dst); + Boolean::set_internal_variables_values(witness.is_first, dst); + UInt32::set_internal_variables_values(witness.timestamp, dst); + } +} + +impl CircuitEncodableExt for DecommitQuery {} + +impl CSPlaceholder for DecommitQuery { + fn placeholder>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + Self { + code_hash: UInt256::::zero(cs), + page: zero_u32, + is_first: boolean_false, + timestamp: zero_u32, + } + } +} + +use boojum::gadgets::queue::full_state_queue::{ + FullStateCircuitQueue, FullStateCircuitQueueWitness, +}; + +pub type DecommitQueryQueue = + FullStateCircuitQueue, AW, SW, CW, DECOMMIT_QUERY_PACKED_WIDTH, R>; + +pub type DecommitQueue = DecommitQueryQueue; + +pub type DecommitQueueWitness = + FullStateCircuitQueueWitness, SW, DECOMMIT_QUERY_PACKED_WIDTH>; diff --git a/crates/zkevm_circuits/src/base_structures/log_query/mod.rs b/crates/zkevm_circuits/src/base_structures/log_query/mod.rs new file mode 100644 index 00000000..4d429959 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/log_query/mod.rs @@ -0,0 +1,686 @@ +use super::*; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::Variable; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt}; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::encodable::CircuitEncodableExt; +use boojum::gadgets::traits::encodable::{CircuitEncodable, CircuitVarLengthEncodable}; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u160::{recompose_address_from_u32x5, UInt160}; +use boojum::gadgets::u256::{recompose_u256_as_u32x8, UInt256}; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use cs_derive::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, WitnessHookable, CSVarLengthEncodable)] +#[derivative(Clone, Copy, Debug, Hash)] +pub struct LogQuery { + pub address: UInt160, + pub key: UInt256, + pub read_value: UInt256, + pub written_value: UInt256, + pub aux_byte: UInt8, + pub rw_flag: Boolean, + pub rollback: Boolean, + pub is_service: Boolean, + pub shard_id: UInt8, + pub tx_number_in_block: UInt32, + pub timestamp: UInt32, +} +impl CircuitEncodableExt for LogQuery {} + +pub const LOG_QUERY_PACKED_WIDTH: usize = 20; +pub const LOG_QUERY_ABSORBTION_ROUNDS: usize = 3; + +// NOTE: (shamatar): workaround for cost generics for now +pub(crate) const FLATTENED_VARIABLE_LENGTH: usize = 36; + +// because two logs that we add to the queue on write-like operation only differ by +// rollback flag, we want to specially define offset for rollback, so we can +// pack two cases for free. Also packing of the rollback should go into variable +// number 16 or later, so we can share sponges before it + +pub const ROLLBACK_PACKING_FLAG_VARIABLE_IDX: usize = 19; + +impl LogQuery { + pub fn update_packing_for_rollback>( + cs: &mut CS, + existing_packing: &mut [Variable; LOG_QUERY_PACKED_WIDTH], + ) { + let boolean_true = Boolean::allocated_constant(cs, true); + existing_packing[ROLLBACK_PACKING_FLAG_VARIABLE_IDX] = boolean_true.get_variable(); + } + + pub(crate) fn flatten_as_variables_impl(&self) -> [Variable; FLATTENED_VARIABLE_LENGTH] { + [ + self.address.inner[0].get_variable(), + self.address.inner[1].get_variable(), + self.address.inner[2].get_variable(), + self.address.inner[3].get_variable(), + self.address.inner[4].get_variable(), + self.key.inner[0].get_variable(), + self.key.inner[1].get_variable(), + self.key.inner[2].get_variable(), + self.key.inner[3].get_variable(), + self.key.inner[4].get_variable(), + self.key.inner[5].get_variable(), + self.key.inner[6].get_variable(), + self.key.inner[7].get_variable(), + self.read_value.inner[0].get_variable(), + self.read_value.inner[1].get_variable(), + self.read_value.inner[2].get_variable(), + self.read_value.inner[3].get_variable(), + self.read_value.inner[4].get_variable(), + self.read_value.inner[5].get_variable(), + self.read_value.inner[6].get_variable(), + self.read_value.inner[7].get_variable(), + self.written_value.inner[0].get_variable(), + self.written_value.inner[1].get_variable(), + self.written_value.inner[2].get_variable(), + self.written_value.inner[3].get_variable(), + self.written_value.inner[4].get_variable(), + self.written_value.inner[5].get_variable(), + self.written_value.inner[6].get_variable(), + self.written_value.inner[7].get_variable(), + self.aux_byte.get_variable(), + self.rw_flag.get_variable(), + self.rollback.get_variable(), + self.is_service.get_variable(), + self.shard_id.get_variable(), + self.tx_number_in_block.get_variable(), + self.timestamp.get_variable(), + ] + } +} + +impl CSPlaceholder for LogQuery { + fn placeholder>(cs: &mut CS) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + Self { + address: UInt160::::zero(cs), + key: UInt256::zero(cs), + read_value: UInt256::zero(cs), + written_value: UInt256::zero(cs), + rw_flag: boolean_false, + aux_byte: UInt8::zero(cs), + rollback: boolean_false, + is_service: boolean_false, + shard_id: UInt8::zero(cs), + tx_number_in_block: UInt32::zero(cs), + timestamp: UInt32::zero(cs), + } + } +} + +impl CircuitEncodable for LogQuery { + fn encode>(&self, cs: &mut CS) -> [Variable; LOG_QUERY_PACKED_WIDTH] { + debug_assert!(F::CAPACITY_BITS >= 56); + // we decompose "key" and mix it into other limbs because with high probability + // in VM decomposition of "key" will always exist beforehand + let key_bytes = self.key.inner.map(|el| el.decompose_into_bytes(cs)); + let address_bytes = self.address.inner.map(|el| el.decompose_into_bytes(cs)); + + // we want to pack tightly, so we "base" our packing on read and written values + + let v0 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[0].get_variable(), F::ONE), + ( + key_bytes[0][0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[0][1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[0][2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v1 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[1].get_variable(), F::ONE), + ( + key_bytes[0][3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[1][0].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[1][1].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v2 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[2].get_variable(), F::ONE), + ( + key_bytes[1][2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[1][3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[2][0].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v3 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[3].get_variable(), F::ONE), + ( + key_bytes[2][1].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[2][2].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[2][3].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v4 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[4].get_variable(), F::ONE), + ( + key_bytes[3][0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[3][1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[3][2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v5 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[5].get_variable(), F::ONE), + ( + key_bytes[3][3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[4][0].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[4][1].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v6 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[6].get_variable(), F::ONE), + ( + key_bytes[4][2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[4][3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[5][0].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v7 = Num::linear_combination( + cs, + &[ + (self.read_value.inner[7].get_variable(), F::ONE), + ( + key_bytes[5][1].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[5][2].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[5][3].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + // continue with written value + + let v8 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[0].get_variable(), F::ONE), + ( + key_bytes[6][0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[6][1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[6][2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v9 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[1].get_variable(), F::ONE), + ( + key_bytes[6][3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[7][0].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + key_bytes[7][1].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + // continue mixing bytes, now from "address" + + let v10 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[2].get_variable(), F::ONE), + ( + key_bytes[7][2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + key_bytes[7][3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[0][0].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v11 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[3].get_variable(), F::ONE), + ( + address_bytes[0][1].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[0][2].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[0][3].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v12 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[4].get_variable(), F::ONE), + ( + address_bytes[1][0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[1][1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[1][2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v13 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[5].get_variable(), F::ONE), + ( + address_bytes[1][3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[2][0].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[2][1].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v14 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[6].get_variable(), F::ONE), + ( + address_bytes[2][2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[2][3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[3][0].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v15 = Num::linear_combination( + cs, + &[ + (self.written_value.inner[7].get_variable(), F::ONE), + ( + address_bytes[3][1].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[3][2].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[3][3].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + // now we can pack using some other "large" items as base + + let v16 = Num::linear_combination( + cs, + &[ + (self.timestamp.get_variable(), F::ONE), + ( + address_bytes[4][0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + address_bytes[4][1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + address_bytes[4][2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v17 = Num::linear_combination( + cs, + &[ + (self.tx_number_in_block.get_variable(), F::ONE), + ( + address_bytes[4][3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.aux_byte.get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + self.shard_id.get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v18 = Num::linear_combination( + cs, + &[ + (self.rw_flag.get_variable(), F::ONE), + (self.is_service.get_variable(), F::TWO), + ], + ) + .get_variable(); + + // and the final variable is just rollback flag itself + + // NOTE: if you even change this encoding please ensure that corresponding part + // is updated in TimestampedStorageLogRecord + let v19 = self.rollback.get_variable(); + + [ + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, + ] + } +} + +pub(crate) fn log_query_witness_from_values( + values: [F; FLATTENED_VARIABLE_LENGTH], +) -> as CSAllocatable>::Witness { + let address: [u32; 5] = [ + WitnessCastable::cast_from_source(values[0]), + WitnessCastable::cast_from_source(values[1]), + WitnessCastable::cast_from_source(values[2]), + WitnessCastable::cast_from_source(values[3]), + WitnessCastable::cast_from_source(values[4]), + ]; + let address = recompose_address_from_u32x5(address); + + let key: [u32; 8] = [ + WitnessCastable::cast_from_source(values[5]), + WitnessCastable::cast_from_source(values[6]), + WitnessCastable::cast_from_source(values[7]), + WitnessCastable::cast_from_source(values[8]), + WitnessCastable::cast_from_source(values[9]), + WitnessCastable::cast_from_source(values[10]), + WitnessCastable::cast_from_source(values[11]), + WitnessCastable::cast_from_source(values[12]), + ]; + let key = recompose_u256_as_u32x8(key); + + let read_value: [u32; 8] = [ + WitnessCastable::cast_from_source(values[13]), + WitnessCastable::cast_from_source(values[14]), + WitnessCastable::cast_from_source(values[15]), + WitnessCastable::cast_from_source(values[16]), + WitnessCastable::cast_from_source(values[17]), + WitnessCastable::cast_from_source(values[18]), + WitnessCastable::cast_from_source(values[19]), + WitnessCastable::cast_from_source(values[20]), + ]; + let read_value = recompose_u256_as_u32x8(read_value); + + let written_value: [u32; 8] = [ + WitnessCastable::cast_from_source(values[21]), + WitnessCastable::cast_from_source(values[22]), + WitnessCastable::cast_from_source(values[23]), + WitnessCastable::cast_from_source(values[24]), + WitnessCastable::cast_from_source(values[25]), + WitnessCastable::cast_from_source(values[26]), + WitnessCastable::cast_from_source(values[27]), + WitnessCastable::cast_from_source(values[28]), + ]; + let written_value = recompose_u256_as_u32x8(written_value); + + let aux_byte: u8 = WitnessCastable::cast_from_source(values[29]); + let rw_flag: bool = WitnessCastable::cast_from_source(values[30]); + let rollback: bool = WitnessCastable::cast_from_source(values[31]); + let is_service: bool = WitnessCastable::cast_from_source(values[32]); + let shard_id: u8 = WitnessCastable::cast_from_source(values[33]); + let tx_number_in_block: u32 = WitnessCastable::cast_from_source(values[34]); + let timestamp: u32 = WitnessCastable::cast_from_source(values[35]); + + as CSAllocatable>::Witness { + address, + key, + read_value, + written_value, + aux_byte, + rw_flag, + rollback, + is_service, + shard_id, + tx_number_in_block, + timestamp, + } +} + +impl CSAllocatableExt for LogQuery { + const INTERNAL_STRUCT_LEN: usize = FLATTENED_VARIABLE_LENGTH; + + fn witness_from_set_of_values(values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + log_query_witness_from_values(values) + } + + // we should be able to allocate without knowing values yet + fn create_without_value>(cs: &mut CS) -> Self { + Self { + address: UInt160::allocate_without_value(cs), + key: UInt256::allocate_without_value(cs), + read_value: UInt256::allocate_without_value(cs), + written_value: UInt256::allocate_without_value(cs), + rw_flag: Boolean::allocate_without_value(cs), + aux_byte: UInt8::allocate_without_value(cs), + rollback: Boolean::allocate_without_value(cs), + is_service: Boolean::allocate_without_value(cs), + shard_id: UInt8::allocate_without_value(cs), + tx_number_in_block: UInt32::allocate_without_value(cs), + timestamp: UInt32::allocate_without_value(cs), + } + } + + fn flatten_as_variables(&self) -> [Variable; Self::INTERNAL_STRUCT_LEN] + where + [(); Self::INTERNAL_STRUCT_LEN]:, + { + self.flatten_as_variables_impl() + } + + fn set_internal_variables_values(witness: Self::Witness, dst: &mut DstBuffer<'_, '_, F>) { + // NOTE: must be same sequence as in `flatten_as_variables` + UInt160::set_internal_variables_values(witness.address, dst); + UInt256::set_internal_variables_values(witness.key, dst); + UInt256::set_internal_variables_values(witness.read_value, dst); + UInt256::set_internal_variables_values(witness.written_value, dst); + UInt8::set_internal_variables_values(witness.aux_byte, dst); + Boolean::set_internal_variables_values(witness.rw_flag, dst); + Boolean::set_internal_variables_values(witness.rollback, dst); + Boolean::set_internal_variables_values(witness.is_service, dst); + UInt8::set_internal_variables_values(witness.shard_id, dst); + UInt32::set_internal_variables_values(witness.tx_number_in_block, dst); + UInt32::set_internal_variables_values(witness.timestamp, dst); + } +} + +use crate::base_structures::vm_state::QUEUE_STATE_WIDTH; +use boojum::gadgets::queue::CircuitQueue; + +pub type LogQueryQueue = + CircuitQueue, AW, SW, CW, QUEUE_STATE_WIDTH, LOG_QUERY_PACKED_WIDTH, R>; + +// we will output L2 to L1 messages as byte packed messages, so let's make it + +pub const L2_TO_L1_MESSAGE_BYTE_LENGTH: usize = 88; + +impl ByteSerializable for LogQuery { + fn into_bytes>( + &self, + cs: &mut CS, + ) -> [UInt8; L2_TO_L1_MESSAGE_BYTE_LENGTH] { + let zero_u8 = UInt8::zero(cs); + + let mut result = [zero_u8; L2_TO_L1_MESSAGE_BYTE_LENGTH]; + let mut offset = 0; + result[offset] = self.shard_id; + offset += 1; + result[offset] = unsafe { UInt8::from_variable_unchecked(self.is_service.get_variable()) }; + offset += 1; + + let bytes_be = self.tx_number_in_block.to_be_bytes(cs); + result[offset..(offset + (bytes_be.len() - 2))].copy_from_slice(&bytes_be[2..]); + offset += bytes_be.len() - 2; + + // we truncated, so let's enforce that those were unsused + for el in bytes_be[..2].iter() { + Num::enforce_equal(cs, &zero_u8.into_num(), &el.into_num()); + } + + let bytes_be = self.address.to_be_bytes(cs); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + let bytes_be = self.key.to_be_bytes(cs); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + let bytes_be = self.written_value.to_be_bytes(cs); + result[offset..(offset + bytes_be.len())].copy_from_slice(&bytes_be); + offset += bytes_be.len(); + + assert_eq!(offset, L2_TO_L1_MESSAGE_BYTE_LENGTH); + + result + } +} diff --git a/crates/zkevm_circuits/src/base_structures/memory_query/mod.rs b/crates/zkevm_circuits/src/base_structures/memory_query/mod.rs new file mode 100644 index 00000000..990e01b3 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/memory_query/mod.rs @@ -0,0 +1,314 @@ +use super::*; + +use boojum::field::SmallField; + +use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueue; +use boojum::gadgets::u256::UInt256; + +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::u32::UInt32; + +use crate::ethereum_types::U256; +use boojum::config::*; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::Place; +use boojum::cs::Variable; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt}; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::encodable::{CircuitEncodable, CircuitEncodableExt}; +use boojum::gadgets::traits::selectable::Selectable; + +use boojum::gadgets::traits::witnessable::WitnessHookable; +use cs_derive::*; + +pub const MEMORY_QUERY_PACKED_WIDTH: usize = 8; + +#[derive(Derivative, CSSelectable, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug, Hash)] +pub struct MemoryQuery { + pub timestamp: UInt32, + pub memory_page: UInt32, + pub index: UInt32, + pub rw_flag: Boolean, + pub is_ptr: Boolean, + pub value: UInt256, +} + +impl CircuitEncodableExt for MemoryQuery {} + +// in practice we use memory queue, so we need to have a nice way to pack memory query into +// 8 field elements. In addition we can exploit the fact that when we will process the elements +// we will only need to exploit timestamp, page, index, value and r/w flag in their types, but +// actual value can be packed more tightly into full field elements as it will only be compared, +// without access to it's bitwidth + +pub const MEMORY_QUERY_UNROLLED_WIDTH: usize = 13; + +impl CSAllocatableExt for MemoryQuery { + const INTERNAL_STRUCT_LEN: usize = MEMORY_QUERY_UNROLLED_WIDTH; + + fn flatten_as_variables(&self) -> [Variable; Self::INTERNAL_STRUCT_LEN] { + [ + self.timestamp.get_variable(), + self.memory_page.get_variable(), + self.index.get_variable(), + self.rw_flag.get_variable(), + self.is_ptr.get_variable(), + self.value.inner[0].get_variable(), + self.value.inner[1].get_variable(), + self.value.inner[2].get_variable(), + self.value.inner[3].get_variable(), + self.value.inner[4].get_variable(), + self.value.inner[5].get_variable(), + self.value.inner[6].get_variable(), + self.value.inner[7].get_variable(), + ] + } + + fn set_internal_variables_values(witness: Self::Witness, dst: &mut DstBuffer<'_, '_, F>) { + // NOTE: must be same sequence as in `flatten_as_variables` + UInt32::set_internal_variables_values(witness.timestamp, dst); + UInt32::set_internal_variables_values(witness.memory_page, dst); + UInt32::set_internal_variables_values(witness.index, dst); + Boolean::set_internal_variables_values(witness.rw_flag, dst); + Boolean::set_internal_variables_values(witness.is_ptr, dst); + UInt256::set_internal_variables_values(witness.value, dst); + } + + fn witness_from_set_of_values(values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + let timestamp: u32 = WitnessCastable::cast_from_source(values[0]); + let memory_page: u32 = WitnessCastable::cast_from_source(values[1]); + let index: u32 = WitnessCastable::cast_from_source(values[2]); + let rw_flag: bool = WitnessCastable::cast_from_source(values[3]); + let is_ptr: bool = WitnessCastable::cast_from_source(values[4]); + + let value: U256 = WitnessCastable::cast_from_source([ + values[5], values[6], values[7], values[8], values[9], values[10], values[11], + values[12], + ]); + + Self::Witness { + timestamp, + memory_page, + index, + rw_flag, + is_ptr, + value, + } + } +} + +impl CircuitEncodable for MemoryQuery { + fn encode>( + &self, + cs: &mut CS, + ) -> [Variable; MEMORY_QUERY_PACKED_WIDTH] { + // we assume the fact that capacity of F is quite close to 64 bits + debug_assert!(F::CAPACITY_BITS >= 56); + + // strategy: we use 3 field elements to pack timestamp, decomposition of page, index and r/w flag, + // and 5 more elements to tightly pack 8xu32 of values + + let v0 = self.timestamp.get_variable(); + let v1 = self.memory_page.get_variable(); + let v2 = Num::linear_combination( + cs, + &[ + (self.index.get_variable(), F::ONE), + ( + self.rw_flag.get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.is_ptr.get_variable(), + F::from_u64_unchecked(1u64 << 33), + ), + ], + ) + .get_variable(); + + // value. Those in most of the cases will be nops + let decomposition_5 = self.value.inner[5].decompose_into_bytes(cs); + let decomposition_6 = self.value.inner[6].decompose_into_bytes(cs); + let decomposition_7 = self.value.inner[7].decompose_into_bytes(cs); + + let v3 = Num::linear_combination( + cs, + &[ + (self.value.inner[0].get_variable(), F::ONE), + ( + decomposition_5[0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_5[1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_5[2].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v4 = Num::linear_combination( + cs, + &[ + (self.value.inner[1].get_variable(), F::ONE), + ( + decomposition_5[3].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_6[0].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_6[1].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v5 = Num::linear_combination( + cs, + &[ + (self.value.inner[2].get_variable(), F::ONE), + ( + decomposition_6[2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_6[3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_7[0].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v6 = Num::linear_combination( + cs, + &[ + (self.value.inner[3].get_variable(), F::ONE), + ( + decomposition_7[1].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + decomposition_7[2].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ( + decomposition_7[3].get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ], + ) + .get_variable(); + + let v7 = self.value.inner[4].get_variable(); + + [v0, v1, v2, v3, v4, v5, v6, v7] + } +} + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, Hash)] +pub struct MemoryValue { + pub is_ptr: Boolean, + pub value: UInt256, +} + +use crate::main_vm::witness_oracle::MemoryWitness; +use boojum::gadgets::u256::decompose_u256_as_u32x8; + +impl MemoryValue { + pub fn allocate_from_closure_and_dependencies< + CS: ConstraintSystem, + FN: FnOnce(&[F]) -> MemoryWitness + 'static + Send + Sync, + >( + cs: &mut CS, + witness_closure: FN, + dependencies: &[Place], + ) -> Self { + let outputs = cs.alloc_multiple_variables_without_values::<9>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: &[F], output_buffer: &mut DstBuffer<'_, '_, F>| { + debug_assert!(F::CAPACITY_BITS >= 32); + let witness = (witness_closure)(inputs); + let chunks = decompose_u256_as_u32x8(witness.value); + output_buffer.extend(chunks.map(|el| F::from_u64_unchecked(el as u64))); + output_buffer.push(F::from_u64_unchecked(witness.is_ptr as u64)); + }; + + cs.set_values_with_dependencies_vararg( + &dependencies, + &Place::from_variables(outputs), + value_fn, + ); + } + + let [l0, l1, l2, l3, l4, l5, l6, l7, b] = outputs; + + let chunks = + [l0, l1, l2, l3, l4, l5, l6, l7].map(|el| UInt32::from_variable_checked(cs, el)); + let is_ptr = Boolean::from_variable_checked(cs, b); + + Self { + is_ptr, + value: UInt256 { inner: chunks }, + } + } + + pub fn allocate_from_closure_and_dependencies_non_pointer< + CS: ConstraintSystem, + FN: FnOnce(&[F]) -> MemoryWitness + 'static + Send + Sync, + >( + cs: &mut CS, + witness_closure: FN, + dependencies: &[Place], + ) -> Self { + let outputs = cs.alloc_multiple_variables_without_values::<8>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: &[F], output_buffer: &mut DstBuffer<'_, '_, F>| { + debug_assert!(F::CAPACITY_BITS >= 32); + let witness = (witness_closure)(inputs); + let chunks = decompose_u256_as_u32x8(witness.value); + output_buffer.extend(chunks.map(|el| F::from_u64_unchecked(el as u64))); + }; + + cs.set_values_with_dependencies_vararg( + &dependencies, + &Place::from_variables(outputs), + value_fn, + ); + } + + let chunks = outputs.map(|el| UInt32::from_variable_checked(cs, el)); + let is_ptr = Boolean::allocated_constant(cs, false); + + Self { + is_ptr, + value: UInt256 { inner: chunks }, + } + } +} + +use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueueWitness; + +pub type MemoryQueryQueue = + FullStateCircuitQueue, AW, SW, CW, MEMORY_QUERY_PACKED_WIDTH, R>; +pub type MemoryQueue = MemoryQueryQueue; + +pub type MemoryQueryQueueWitness = + FullStateCircuitQueueWitness, SW, MEMORY_QUERY_PACKED_WIDTH>; diff --git a/crates/zkevm_circuits/src/base_structures/mod.rs b/crates/zkevm_circuits/src/base_structures/mod.rs new file mode 100644 index 00000000..8374debe --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/mod.rs @@ -0,0 +1,18 @@ +use boojum::field::SmallField; +use boojum::{cs::traits::cs::ConstraintSystem, gadgets::u8::UInt8}; + +use super::*; + +pub mod decommit_query; +pub mod log_query; +pub mod memory_query; +pub mod recursion_query; +pub mod register; +pub mod vm_state; + +pub mod precompile_input_outputs; +pub mod state_diff_record; + +pub trait ByteSerializable { + fn into_bytes>(&self, cs: &mut CS) -> [UInt8; N]; +} diff --git a/crates/zkevm_circuits/src/base_structures/precompile_input_outputs/mod.rs b/crates/zkevm_circuits/src/base_structures/precompile_input_outputs/mod.rs new file mode 100644 index 00000000..cc314207 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/precompile_input_outputs/mod.rs @@ -0,0 +1,52 @@ +use super::*; +// universal precompiles passthrough input/output +// takes requests queue + memory state +// outputs memory state + +use crate::base_structures::vm_state::*; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::Variable; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use cs_derive::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct PrecompileFunctionInputData { + pub initial_log_queue_state: QueueState, + pub initial_memory_queue_state: QueueState, +} + +impl CSPlaceholder for PrecompileFunctionInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + initial_log_queue_state: QueueState::::placeholder(cs), + initial_memory_queue_state: QueueState::::placeholder( + cs, + ), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct PrecompileFunctionOutputData { + pub final_memory_state: QueueState, +} + +impl CSPlaceholder for PrecompileFunctionOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + final_memory_state: QueueState::::placeholder(cs), + } + } +} diff --git a/crates/zkevm_circuits/src/base_structures/recursion_query/mod.rs b/crates/zkevm_circuits/src/base_structures/recursion_query/mod.rs new file mode 100644 index 00000000..1d463237 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/recursion_query/mod.rs @@ -0,0 +1,110 @@ +use boojum::cs::gates::ConstantAllocatableCS; +use cs_derive::*; + +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; + +use super::*; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::Variable; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt}; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::encodable::{CircuitEncodable, CircuitEncodableExt}; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, WitnessHookable, CSVarLengthEncodable)] +#[derivative(Clone, Copy, Debug)] +pub struct RecursionQuery { + pub circuit_type: Num, + pub input_commitment: [Num; INPUT_OUTPUT_COMMITMENT_LENGTH], +} + +pub const RECURSION_QUERY_PACKED_WIDTH: usize = 8; + +impl CircuitEncodable for RecursionQuery { + fn encode>( + &self, + cs: &mut CS, + ) -> [Variable; RECURSION_QUERY_PACKED_WIDTH] { + let zero = cs.allocate_constant(F::ZERO); + let [t0, t1, t2, t3] = self.input_commitment.map(|el| el.get_variable()); + [ + self.circuit_type.get_variable(), + t0, + t1, + t2, + t3, + zero, + zero, + zero, + ] + } +} + +impl CSAllocatableExt for RecursionQuery { + const INTERNAL_STRUCT_LEN: usize = 5; + + fn witness_from_set_of_values(values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + let circuit_type = values[0]; + + let t0 = values[1]; + let t1 = values[2]; + let t2 = values[3]; + let t3 = values[4]; + + Self::Witness { + circuit_type, + input_commitment: [t0, t1, t2, t3], + } + } + + fn flatten_as_variables(&self) -> [Variable; Self::INTERNAL_STRUCT_LEN] + where + [(); Self::INTERNAL_STRUCT_LEN]:, + { + [ + self.circuit_type.get_variable(), + self.input_commitment[0].get_variable(), + self.input_commitment[1].get_variable(), + self.input_commitment[2].get_variable(), + self.input_commitment[3].get_variable(), + ] + } + fn set_internal_variables_values(witness: Self::Witness, dst: &mut DstBuffer<'_, '_, F>) { + Num::set_internal_variables_values(witness.circuit_type, dst); + for src in witness.input_commitment.into_iter() { + Num::set_internal_variables_values(src, dst); + } + } +} + +impl CircuitEncodableExt for RecursionQuery {} + +impl CSPlaceholder for RecursionQuery { + fn placeholder>(cs: &mut CS) -> Self { + let zero_num = Num::zero(cs); + + Self { + circuit_type: zero_num, + input_commitment: [zero_num; INPUT_OUTPUT_COMMITMENT_LENGTH], + } + } +} + +use boojum::gadgets::queue::full_state_queue::{ + FullStateCircuitQueue, FullStateCircuitQueueWitness, +}; + +pub type RecursionQueryQueue = + FullStateCircuitQueue, AW, SW, CW, RECURSION_QUERY_PACKED_WIDTH, R>; + +pub type RecursionQueue = RecursionQueryQueue; + +pub type RecursionQueueWitness = + FullStateCircuitQueueWitness, SW, RECURSION_QUERY_PACKED_WIDTH>; diff --git a/crates/zkevm_circuits/src/base_structures/register/mod.rs b/crates/zkevm_circuits/src/base_structures/register/mod.rs new file mode 100644 index 00000000..12a33db2 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/register/mod.rs @@ -0,0 +1,86 @@ +use super::*; +use boojum::field::SmallField; +use boojum::gadgets::u256::UInt256; + +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::u16::UInt16; +use boojum::gadgets::u32::UInt32; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::Variable; +use boojum::gadgets::traits::allocatable::{CSAllocatable, CSAllocatableExt}; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; + +use cs_derive::*; + +#[derive(Derivative, CSSelectable, CSAllocatable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug, Hash)] +pub struct VMRegister { + pub is_pointer: Boolean, + pub value: UInt256, +} + +impl VMRegister { + pub fn zero>(cs: &mut CS) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + let zero_u256 = UInt256::zero(cs); + + Self { + is_pointer: boolean_false, + value: zero_u256, + } + } + + pub fn from_imm>(cs: &mut CS, imm: UInt16) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + let zero_u32 = UInt32::zero(cs); + + Self { + is_pointer: boolean_false, + value: UInt256 { + inner: [ + unsafe { UInt32::from_variable_unchecked(imm.get_variable()) }, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + ], + }, + } + } +} + +impl CSAllocatableExt for VMRegister { + const INTERNAL_STRUCT_LEN: usize = 9; + + fn flatten_as_variables(&self) -> [Variable; Self::INTERNAL_STRUCT_LEN] { + // NOTE: CSAllocatable is done by the macro, so it allocates in the order of declaration, + // and we should do the same here! + + [ + self.is_pointer.get_variable(), + self.value.inner[0].get_variable(), + self.value.inner[1].get_variable(), + self.value.inner[2].get_variable(), + self.value.inner[3].get_variable(), + self.value.inner[4].get_variable(), + self.value.inner[5].get_variable(), + self.value.inner[6].get_variable(), + self.value.inner[7].get_variable(), + ] + } + + fn set_internal_variables_values(_witness: Self::Witness, _dst: &mut DstBuffer<'_, '_, F>) { + todo!() + } + + fn witness_from_set_of_values(_values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + todo!() + } +} diff --git a/crates/zkevm_circuits/src/base_structures/state_diff_record/mod.rs b/crates/zkevm_circuits/src/base_structures/state_diff_record/mod.rs new file mode 100644 index 00000000..b14ffefa --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/state_diff_record/mod.rs @@ -0,0 +1,75 @@ +use super::*; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u8::UInt8; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; + +use boojum::gadgets::keccak256::KECCAK_RATE_BYTES; + +pub const STATE_DIFF_RECORD_BYTE_ENCODING_LEN: usize = 20 + 32 + 32 + 8 + 32 + 32; +pub const NUM_KECCAK256_ROUNDS_PER_RECORD_ACCUMULATION: usize = 2; +const _: () = if STATE_DIFF_RECORD_BYTE_ENCODING_LEN + <= KECCAK_RATE_BYTES * NUM_KECCAK256_ROUNDS_PER_RECORD_ACCUMULATION +{ + () +} else { + panic!() +}; + +#[derive(Derivative, CSAllocatable, CSSelectable, WitnessHookable)] +#[derivative(Clone, Copy, Debug, Hash)] +pub struct StateDiffRecord { + pub address: [UInt8; 20], + pub key: [UInt8; 32], + pub derived_key: [UInt8; 32], + pub enumeration_index: [UInt8; 8], + pub initial_value: [UInt8; 32], + pub final_value: [UInt8; 32], +} + +impl StateDiffRecord { + // the only thing we need is byte encoding + pub fn encode>( + &self, + cs: &mut CS, + ) -> [UInt8; STATE_DIFF_RECORD_BYTE_ENCODING_LEN] { + let zero_u8 = UInt8::zero(cs); + let mut encoding = [zero_u8; STATE_DIFF_RECORD_BYTE_ENCODING_LEN]; + let mut offset = 0; + let mut end = 0; + + end += self.address.len(); + encoding[offset..end].copy_from_slice(&self.address); + offset = end; + + end += self.key.len(); + encoding[offset..end].copy_from_slice(&self.key); + offset = end; + + end += self.derived_key.len(); + encoding[offset..end].copy_from_slice(&self.derived_key); + offset = end; + + end += self.enumeration_index.len(); + encoding[offset..end].copy_from_slice(&self.enumeration_index); + offset = end; + + end += self.initial_value.len(); + encoding[offset..end].copy_from_slice(&self.initial_value); + offset = end; + + end += self.final_value.len(); + encoding[offset..end].copy_from_slice(&self.final_value); + offset = end; + + debug_assert_eq!(offset, encoding.len()); + + encoding + } +} diff --git a/crates/zkevm_circuits/src/base_structures/vm_state/callstack.rs b/crates/zkevm_circuits/src/base_structures/vm_state/callstack.rs new file mode 100644 index 00000000..4ab9ac97 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/vm_state/callstack.rs @@ -0,0 +1,98 @@ +use super::*; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[CSSelectableBound( + "where [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:" +)] +pub struct Callstack { + pub current_context: FullExecutionContext, + pub context_stack_depth: UInt32, + pub stack_sponge_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], +} + +impl Callstack { + pub fn empty>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + let zero_num = Num::zero(cs); + Self { + current_context: FullExecutionContext::uninitialized(cs), + context_stack_depth: zero_u32, + stack_sponge_state: [zero_num; FULL_SPONGE_QUEUE_STATE_WIDTH], + } + } + + pub fn is_empty>(&self, cs: &mut CS) -> Boolean { + self.context_stack_depth.is_zero(cs) + } + + pub fn is_full>(&self, cs: &mut CS) -> Boolean { + let max_depth = + UInt32::allocated_constant(cs, zkevm_opcode_defs::system_params::VM_MAX_STACK_DEPTH); + UInt32::equals(cs, &self.context_stack_depth, &max_depth) + } +} + +use boojum::gadgets::traits::allocatable::CSAllocatableExt; + +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; + +// execution context that keeps all explicit data about the current execution frame, +// and avoid recomputing of quantities that also do not change between calls +#[derive(Derivative, CSAllocatable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct FullExecutionContext { + pub saved_context: ExecutionContextRecord, + pub log_queue_forward_tail: [Num; 4], + pub log_queue_forward_part_length: UInt32, +} + +impl Selectable for FullExecutionContext +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + fn conditionally_select>( + cs: &mut CS, + flag: Boolean, + a: &Self, + b: &Self, + ) -> Self { + let saved_context = ExecutionContextRecord::conditionally_select( + cs, + flag, + &a.saved_context, + &b.saved_context, + ); + let log_queue_forward_tail = Num::parallel_select( + cs, + flag, + &a.log_queue_forward_tail, + &b.log_queue_forward_tail, + ); + let log_queue_forward_part_length = UInt32::conditionally_select( + cs, + flag, + &a.log_queue_forward_part_length, + &b.log_queue_forward_part_length, + ); + + Self { + saved_context, + log_queue_forward_tail, + log_queue_forward_part_length, + } + } +} + +impl FullExecutionContext { + pub fn uninitialized>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + let zero_num = Num::zero(cs); + Self { + saved_context: ExecutionContextRecord::uninitialized(cs), + log_queue_forward_tail: [zero_num; 4], + log_queue_forward_part_length: zero_u32, + } + } +} diff --git a/crates/zkevm_circuits/src/base_structures/vm_state/mod.rs b/crates/zkevm_circuits/src/base_structures/vm_state/mod.rs new file mode 100644 index 00000000..4147eb03 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/vm_state/mod.rs @@ -0,0 +1,171 @@ +use super::register::VMRegister; +use super::*; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::Variable; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::*; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::selectable::*; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u16::UInt16; +use boojum::gadgets::u160::UInt160; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; + +pub mod callstack; +pub mod saved_context; + +use crate::base_structures::vm_state::callstack::Callstack; + +pub const FULL_SPONGE_QUEUE_STATE_WIDTH: usize = 12; +pub const QUEUE_STATE_WIDTH: usize = 4; + +pub(crate) const REGISTERS_COUNT: usize = 15; + +#[derive(Derivative, CSAllocatable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct ArithmeticFlagsPort { + pub overflow_or_less_than: Boolean, + pub equal: Boolean, + pub greater_than: Boolean, +} + +impl Selectable for ArithmeticFlagsPort { + fn conditionally_select>( + cs: &mut CS, + flag: Boolean, + a: &Self, + b: &Self, + ) -> Self { + // use parallel select + + let boolean_false = Boolean::allocated_constant(cs, false); + + let a = [ + a.overflow_or_less_than, + a.equal, + a.greater_than, + boolean_false, + ]; + let b = [ + b.overflow_or_less_than, + b.equal, + b.greater_than, + boolean_false, + ]; + + let [overflow_or_less_than, equal, greater_than, _] = + Boolean::parallel_select(cs, flag, &a, &b); + + Self { + overflow_or_less_than, + equal, + greater_than, + } + } +} + +impl ArithmeticFlagsPort { + pub fn reseted_flags>(cs: &mut CS) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + Self { + overflow_or_less_than: boolean_false, + equal: boolean_false, + greater_than: boolean_false, + } + } +} + +#[derive(Derivative, CSSelectable, CSAllocatable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[CSSelectableBound( + "where [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:" +)] +#[DerivePrettyComparison("true")] +pub struct VmLocalState { + pub previous_code_word: UInt256, + pub registers: [VMRegister; REGISTERS_COUNT], + pub flags: ArithmeticFlagsPort, + pub timestamp: UInt32, + pub memory_page_counter: UInt32, + pub tx_number_in_block: UInt32, + pub previous_code_page: UInt32, + pub previous_super_pc: UInt16, + pub pending_exception: Boolean, + pub ergs_per_pubdata_byte: UInt32, + pub callstack: Callstack, + pub memory_queue_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + pub memory_queue_length: UInt32, + pub code_decommittment_queue_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + pub code_decommittment_queue_length: UInt32, + pub context_composite_u128: [UInt32; 4], +} + +impl VmLocalState { + pub fn uninitialized>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + let zero_u16 = UInt16::zero(cs); + let zero_num = Num::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + let zero_u256 = UInt256::zero(cs); + let callstack = Callstack::empty(cs); + let empty_reg = VMRegister { + is_pointer: boolean_false, + value: zero_u256, + }; + + Self { + previous_code_word: zero_u256, + registers: [empty_reg; REGISTERS_COUNT], + flags: ArithmeticFlagsPort { + overflow_or_less_than: boolean_false, + equal: boolean_false, + greater_than: boolean_false, + }, + timestamp: zero_u32, + memory_page_counter: zero_u32, + tx_number_in_block: zero_u32, + previous_code_page: zero_u32, + previous_super_pc: zero_u16, + pending_exception: boolean_false, + ergs_per_pubdata_byte: zero_u32, + callstack, + memory_queue_state: [zero_num; FULL_SPONGE_QUEUE_STATE_WIDTH], + memory_queue_length: zero_u32, + code_decommittment_queue_state: [zero_num; FULL_SPONGE_QUEUE_STATE_WIDTH], + code_decommittment_queue_length: zero_u32, + context_composite_u128: [zero_u32; 4], + } + } +} + +impl CSPlaceholder for VmLocalState { + fn placeholder>(cs: &mut CS) -> Self { + Self::uninitialized(cs) + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct GlobalContext { + pub zkporter_is_available: Boolean, + pub default_aa_code_hash: UInt256, +} + +impl CSPlaceholder for GlobalContext { + fn placeholder>(cs: &mut CS) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + let zero_u256 = UInt256::zero(cs); + Self { + zkporter_is_available: boolean_false, + default_aa_code_hash: zero_u256, + } + } +} diff --git a/crates/zkevm_circuits/src/base_structures/vm_state/saved_context.rs b/crates/zkevm_circuits/src/base_structures/vm_state/saved_context.rs new file mode 100644 index 00000000..f51071a2 --- /dev/null +++ b/crates/zkevm_circuits/src/base_structures/vm_state/saved_context.rs @@ -0,0 +1,548 @@ +use boojum::cs::gates::assert_no_placeholder_variables; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::Variable; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::encodable::CircuitEncodable; +use boojum::gadgets::traits::selectable::parallel_select_variables; +use cs_derive::*; +use ethereum_types::Address; + +use super::*; + +// here we store only part of the context that keeps the data that +// needs to be stored or restored between the calls + +// repeated note on how joining of rollback queues work +// - first we use some non-determinism to declare a rollback_tail == current_rollback_head +// - when we do write we add element into front as forward_tail = hash(forward_tail, log_element) +// and also declare some rollback_head, such that current_rollback_head = hash(rollback_head, log_element) +// - if we return "ok" then we join as +// - forward_tail = callee forward_tail +// - rollback_head = callee rollback_head +// - else +// - forward_tail = rollback_tail +// - caller's rollback_head is unchanged +// - require callee forward_tail == rollback_head +// +// so to proceed with joining we need to only maintain +// - global forward_tail and length of the forward segment +// - per-context declared rollback_tail +// - per-context computed rollback segment length +// - per-context rollback_head + +#[derive(Derivative, CSAllocatable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct ExecutionContextRecord { + pub this: UInt160, // unfortunately delegatecall mangles this field - it can not be restored from callee's caller + pub caller: UInt160, + pub code_address: UInt160, + + pub code_page: UInt32, + pub base_page: UInt32, + + pub heap_upper_bound: UInt32, + pub aux_heap_upper_bound: UInt32, + + pub reverted_queue_head: [Num; 4], + pub reverted_queue_tail: [Num; 4], + pub reverted_queue_segment_len: UInt32, + + pub pc: UInt16, + pub sp: UInt16, + pub exception_handler_loc: UInt16, + pub ergs_remaining: UInt32, + + pub is_static_execution: Boolean, + pub is_kernel_mode: Boolean, + + pub this_shard_id: UInt8, + pub caller_shard_id: UInt8, + pub code_shard_id: UInt8, + + pub context_u128_value_composite: [UInt32; 4], + + pub is_local_call: Boolean, +} + +impl ExecutionContextRecord { + pub fn uninitialized>(cs: &mut CS) -> Self { + let zero_u160 = UInt160::zero(cs); + let zero_u32 = UInt32::zero(cs); + let zero_u16 = UInt16::zero(cs); + let zero_u8 = UInt8::zero(cs); + let zero_num = Num::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + Self { + this: zero_u160, + caller: zero_u160, + code_address: zero_u160, + code_page: zero_u32, + base_page: zero_u32, + + heap_upper_bound: zero_u32, + aux_heap_upper_bound: zero_u32, + + reverted_queue_head: [zero_num; 4], + reverted_queue_tail: [zero_num; 4], + reverted_queue_segment_len: zero_u32, + + pc: zero_u16, + sp: zero_u16, + exception_handler_loc: zero_u16, + ergs_remaining: zero_u32, + + is_static_execution: boolean_false, + is_kernel_mode: boolean_false, + + this_shard_id: zero_u8, + caller_shard_id: zero_u8, + code_shard_id: zero_u8, + + context_u128_value_composite: [zero_u32; 4], + + is_local_call: boolean_false, + } + } +} + +pub const EXECUTION_CONTEXT_RECORD_ENCODING_WIDTH: usize = 32; + +impl CircuitEncodable + for ExecutionContextRecord +{ + fn encode>( + &self, + cs: &mut CS, + ) -> [Variable; EXECUTION_CONTEXT_RECORD_ENCODING_WIDTH] { + debug_assert!(F::CAPACITY_BITS >= 57); + // full field elements first for simplicity + let v0 = self.reverted_queue_head[0].get_variable(); + let v1 = self.reverted_queue_head[1].get_variable(); + let v2 = self.reverted_queue_head[2].get_variable(); + let v3 = self.reverted_queue_head[3].get_variable(); + + let v4 = self.reverted_queue_tail[0].get_variable(); + let v5 = self.reverted_queue_tail[1].get_variable(); + let v6 = self.reverted_queue_tail[2].get_variable(); + let v7 = self.reverted_queue_tail[3].get_variable(); + + let v8 = self.code_address.inner[0].get_variable(); + let v9 = self.code_address.inner[1].get_variable(); + let v10 = self.code_address.inner[2].get_variable(); + let v11 = self.code_address.inner[3].get_variable(); + let v12 = self.code_address.inner[4].get_variable(); + + let v13 = self.this.inner[0].get_variable(); + let v14 = self.this.inner[1].get_variable(); + let v15 = self.this.inner[2].get_variable(); + let v16 = self.this.inner[3].get_variable(); + let v17 = self.this.inner[4].get_variable(); + + let v18 = self.caller.inner[0].get_variable(); + let v19 = self.caller.inner[1].get_variable(); + let v20 = self.caller.inner[2].get_variable(); + let v21 = self.caller.inner[3].get_variable(); + let v22 = self.caller.inner[4].get_variable(); + + let v23 = self.context_u128_value_composite[0].get_variable(); + let v24 = self.context_u128_value_composite[1].get_variable(); + let v25 = self.context_u128_value_composite[2].get_variable(); + let v26 = self.context_u128_value_composite[3].get_variable(); + + // now we have left + // - code_page + // - base_page + // - heap_upper_bound + // - aux_heap_upper_bound + // - ergs_remaining + // - sp + // - pc + // - eh + // - reverted_queue_segment_len + // - shard ids + // - few boolean flags + + // as usual, take u32 and add something on top + + let v27 = Num::linear_combination( + cs, + &[ + (self.code_page.get_variable(), F::ONE), + (self.pc.get_variable(), F::from_u64_unchecked(1u64 << 32)), + ( + self.this_shard_id.get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ( + self.is_static_execution.get_variable(), + F::from_u64_unchecked(1u64 << 56), + ), + ], + ) + .get_variable(); + + let v28 = Num::linear_combination( + cs, + &[ + (self.base_page.get_variable(), F::ONE), + (self.sp.get_variable(), F::from_u64_unchecked(1u64 << 32)), + ( + self.caller_shard_id.get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ( + self.is_kernel_mode.get_variable(), + F::from_u64_unchecked(1u64 << 56), + ), + ], + ) + .get_variable(); + + let v29 = Num::linear_combination( + cs, + &[ + (self.ergs_remaining.get_variable(), F::ONE), + ( + self.exception_handler_loc.get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + self.code_shard_id.get_variable(), + F::from_u64_unchecked(1u64 << 48), + ), + ( + self.is_local_call.get_variable(), + F::from_u64_unchecked(1u64 << 56), + ), + ], + ) + .get_variable(); + + // now we have left + // - heap_upper_bound + // - aux_heap_upper_bound + // - reverted_queue_segment_len + + let reverted_queue_segment_len_decomposition = + self.reverted_queue_segment_len.decompose_into_bytes(cs); + let v30 = Num::linear_combination( + cs, + &[ + (self.heap_upper_bound.get_variable(), F::ONE), + ( + reverted_queue_segment_len_decomposition[0].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + reverted_queue_segment_len_decomposition[1].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ], + ) + .get_variable(); + + let v31 = Num::linear_combination( + cs, + &[ + (self.aux_heap_upper_bound.get_variable(), F::ONE), + ( + reverted_queue_segment_len_decomposition[2].get_variable(), + F::from_u64_unchecked(1u64 << 32), + ), + ( + reverted_queue_segment_len_decomposition[3].get_variable(), + F::from_u64_unchecked(1u64 << 40), + ), + ], + ) + .get_variable(); + + [ + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + ] + } +} + +// we also need allocate extended + +impl CSAllocatableExt for ExecutionContextRecord { + const INTERNAL_STRUCT_LEN: usize = 42; + + fn create_without_value>(cs: &mut CS) -> Self { + // TODO: use more optimal allocation for bytes + CSAllocatable::allocate_without_value(cs) + } + + fn flatten_as_variables(&self) -> [Variable; Self::INTERNAL_STRUCT_LEN] { + [ + self.this.inner[0].get_variable(), + self.this.inner[1].get_variable(), + self.this.inner[2].get_variable(), + self.this.inner[3].get_variable(), + self.this.inner[4].get_variable(), + self.caller.inner[0].get_variable(), + self.caller.inner[1].get_variable(), + self.caller.inner[2].get_variable(), + self.caller.inner[3].get_variable(), + self.caller.inner[4].get_variable(), + self.code_address.inner[0].get_variable(), + self.code_address.inner[1].get_variable(), + self.code_address.inner[2].get_variable(), + self.code_address.inner[3].get_variable(), + self.code_address.inner[4].get_variable(), + self.code_page.get_variable(), + self.base_page.get_variable(), + self.heap_upper_bound.get_variable(), + self.aux_heap_upper_bound.get_variable(), + self.reverted_queue_head[0].get_variable(), + self.reverted_queue_head[1].get_variable(), + self.reverted_queue_head[2].get_variable(), + self.reverted_queue_head[3].get_variable(), + self.reverted_queue_tail[0].get_variable(), + self.reverted_queue_tail[1].get_variable(), + self.reverted_queue_tail[2].get_variable(), + self.reverted_queue_tail[3].get_variable(), + self.reverted_queue_segment_len.get_variable(), + self.pc.get_variable(), + self.sp.get_variable(), + self.exception_handler_loc.get_variable(), + self.ergs_remaining.get_variable(), + self.is_static_execution.get_variable(), + self.is_kernel_mode.get_variable(), + self.this_shard_id.get_variable(), + self.caller_shard_id.get_variable(), + self.code_shard_id.get_variable(), + self.context_u128_value_composite[0].get_variable(), + self.context_u128_value_composite[1].get_variable(), + self.context_u128_value_composite[2].get_variable(), + self.context_u128_value_composite[3].get_variable(), + self.is_local_call.get_variable(), + ] + } + + fn from_variables_set(variables: [Variable; Self::INTERNAL_STRUCT_LEN]) -> Self { + unsafe { + Self { + this: UInt160::from_variables_unchecked([ + variables[0], + variables[1], + variables[2], + variables[3], + variables[4], + ]), + caller: UInt160::from_variables_unchecked([ + variables[5], + variables[6], + variables[7], + variables[8], + variables[9], + ]), + code_address: UInt160::from_variables_unchecked([ + variables[10], + variables[11], + variables[12], + variables[13], + variables[14], + ]), + code_page: UInt32::from_variable_unchecked(variables[15]), + base_page: UInt32::from_variable_unchecked(variables[16]), + + heap_upper_bound: UInt32::from_variable_unchecked(variables[17]), + aux_heap_upper_bound: UInt32::from_variable_unchecked(variables[18]), + + reverted_queue_head: [variables[19], variables[20], variables[21], variables[22]] + .map(|el| Num::from_variable(el)), + + reverted_queue_tail: [variables[23], variables[24], variables[25], variables[26]] + .map(|el| Num::from_variable(el)), + + reverted_queue_segment_len: UInt32::from_variable_unchecked(variables[27]), + + pc: UInt16::from_variable_unchecked(variables[28]), + sp: UInt16::from_variable_unchecked(variables[29]), + exception_handler_loc: UInt16::from_variable_unchecked(variables[30]), + ergs_remaining: UInt32::from_variable_unchecked(variables[31]), + + is_static_execution: Boolean::from_variable_unchecked(variables[32]), + is_kernel_mode: Boolean::from_variable_unchecked(variables[33]), + + this_shard_id: UInt8::from_variable_unchecked(variables[34]), + caller_shard_id: UInt8::from_variable_unchecked(variables[35]), + code_shard_id: UInt8::from_variable_unchecked(variables[36]), + + context_u128_value_composite: [ + variables[37], + variables[38], + variables[39], + variables[40], + ] + .map(|el| UInt32::from_variable_unchecked(el)), + + is_local_call: Boolean::from_variable_unchecked(variables[41]), + } + } + } + + fn set_internal_variables_values(witness: Self::Witness, dst: &mut DstBuffer<'_, '_, F>) { + let src = WitnessCastable::cast_into_source(witness.this); + dst.extend(src); + + let src = WitnessCastable::cast_into_source(witness.caller); + dst.extend(src); + + let src = WitnessCastable::cast_into_source(witness.code_address); + dst.extend(src); + + dst.push(WitnessCastable::cast_into_source(witness.code_page)); + dst.push(WitnessCastable::cast_into_source(witness.base_page)); + dst.push(WitnessCastable::cast_into_source(witness.heap_upper_bound)); + dst.push(WitnessCastable::cast_into_source( + witness.aux_heap_upper_bound, + )); + + dst.extend(witness.reverted_queue_head); + dst.extend(witness.reverted_queue_tail); + dst.push(WitnessCastable::cast_into_source( + witness.reverted_queue_segment_len, + )); + + dst.push(WitnessCastable::cast_into_source(witness.pc)); + dst.push(WitnessCastable::cast_into_source(witness.sp)); + dst.push(WitnessCastable::cast_into_source( + witness.exception_handler_loc, + )); + dst.push(WitnessCastable::cast_into_source(witness.ergs_remaining)); + + dst.push(WitnessCastable::cast_into_source( + witness.is_static_execution, + )); + dst.push(WitnessCastable::cast_into_source(witness.is_kernel_mode)); + dst.push(WitnessCastable::cast_into_source(witness.this_shard_id)); + dst.push(WitnessCastable::cast_into_source(witness.caller_shard_id)); + dst.push(WitnessCastable::cast_into_source(witness.code_shard_id)); + + dst.extend(WitnessCastable::cast_into_source( + witness.context_u128_value_composite, + )); + + dst.push(WitnessCastable::cast_into_source(witness.is_local_call)); + } + + fn witness_from_set_of_values(values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + let this: Address = WitnessCastable::cast_from_source([ + values[0], values[1], values[2], values[3], values[4], + ]); + + let caller: Address = WitnessCastable::cast_from_source([ + values[5], values[6], values[7], values[8], values[9], + ]); + + let code_address: Address = WitnessCastable::cast_from_source([ + values[10], values[11], values[12], values[13], values[14], + ]); + + let code_page: u32 = WitnessCastable::cast_from_source(values[15]); + let base_page: u32 = WitnessCastable::cast_from_source(values[16]); + + let heap_upper_bound: u32 = WitnessCastable::cast_from_source(values[17]); + let aux_heap_upper_bound: u32 = WitnessCastable::cast_from_source(values[18]); + + let reverted_queue_head = [values[19], values[20], values[21], values[22]]; + + let reverted_queue_tail = [values[23], values[24], values[25], values[26]]; + + let reverted_queue_segment_len: u32 = WitnessCastable::cast_from_source(values[27]); + + let pc: u16 = WitnessCastable::cast_from_source(values[28]); + let sp: u16 = WitnessCastable::cast_from_source(values[29]); + let exception_handler_loc: u16 = WitnessCastable::cast_from_source(values[30]); + + let ergs_remaining: u32 = WitnessCastable::cast_from_source(values[31]); + + let is_static_execution: bool = WitnessCastable::cast_from_source(values[32]); + let is_kernel_mode: bool = WitnessCastable::cast_from_source(values[33]); + + let this_shard_id: u8 = WitnessCastable::cast_from_source(values[34]); + let caller_shard_id: u8 = WitnessCastable::cast_from_source(values[35]); + let code_shard_id: u8 = WitnessCastable::cast_from_source(values[36]); + + let context_u128_value_composite: [u32; 4] = + WitnessCastable::cast_from_source([values[37], values[38], values[39], values[40]]); + + let is_local_call: bool = WitnessCastable::cast_from_source(values[41]); + + Self::Witness { + this, + caller, + code_address, + + code_page, + base_page, + + heap_upper_bound, + aux_heap_upper_bound, + + reverted_queue_head, + reverted_queue_tail, + reverted_queue_segment_len, + + pc, + sp, + exception_handler_loc, + ergs_remaining, + + is_static_execution, + is_kernel_mode, + + this_shard_id, + caller_shard_id, + code_shard_id, + + context_u128_value_composite, + + is_local_call, + } + } +} + +impl Selectable for ExecutionContextRecord +where + [(); Self::INTERNAL_STRUCT_LEN]:, +{ + fn conditionally_select>( + cs: &mut CS, + flag: Boolean, + a: &Self, + b: &Self, + ) -> Self { + let a_as_variables = a.flatten_as_variables(); + let b_as_variables = b.flatten_as_variables(); + + let mut dst = [Variable::placeholder(); ExecutionContextRecord::::INTERNAL_STRUCT_LEN]; + + let it = a_as_variables + .into_iter() + .zip(b_as_variables.into_iter()) + .zip(dst.iter_mut()) + .filter_map(|((a, b), dst)| { + if a == b { + // skip and assign any + *dst = a; + + None + } else { + Some(((a, b), dst)) + } + }); + + parallel_select_variables(cs, flag, it); + + // cast back + + assert_no_placeholder_variables(&dst); + + Self::from_variables_set(dst) + } +} diff --git a/crates/zkevm_circuits/src/code_unpacker_sha256/input.rs b/crates/zkevm_circuits/src/code_unpacker_sha256/input.rs new file mode 100644 index 00000000..51148cd6 --- /dev/null +++ b/crates/zkevm_circuits/src/code_unpacker_sha256/input.rs @@ -0,0 +1,140 @@ +use crate::base_structures::vm_state::*; +use crate::ethereum_types::U256; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, + u16::UInt16, + u256::UInt256, + u32::UInt32, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct CodeDecommittmentFSM { + pub sha256_inner_state: [UInt32; 8], // 8 uint32 words of internal sha256 state + pub hash_to_compare_against: UInt256, + pub current_index: UInt32, + pub current_page: UInt32, + pub timestamp: UInt32, + pub num_rounds_left: UInt16, + pub length_in_bits: UInt32, + pub state_get_from_queue: Boolean, + pub state_decommit: Boolean, + pub finished: Boolean, +} + +impl CSPlaceholder for CodeDecommittmentFSM { + fn placeholder>(cs: &mut CS) -> Self { + let bool_false = Boolean::allocated_constant(cs, false); + let zero_uint16 = UInt16::zero(cs); + let zero_uint32 = UInt32::zero(cs); + let zero_uint256 = UInt256::zero(cs); + + Self { + sha256_inner_state: [zero_uint32; 8], + hash_to_compare_against: zero_uint256, + current_index: zero_uint32, + current_page: zero_uint32, + timestamp: zero_uint32, + num_rounds_left: zero_uint16, + length_in_bits: zero_uint32, + state_get_from_queue: bool_false, + state_decommit: bool_false, + finished: bool_false, + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct CodeDecommitterFSMInputOutput { + pub internal_fsm: CodeDecommittmentFSM, + pub decommittment_requests_queue_state: QueueState, + pub memory_queue_state: QueueState, +} + +impl CSPlaceholder for CodeDecommitterFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + internal_fsm: CodeDecommittmentFSM::::placeholder(cs), + decommittment_requests_queue_state: + QueueState::::placeholder(cs), + memory_queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct CodeDecommitterInputData { + pub memory_queue_initial_state: QueueState, + pub sorted_requests_queue_initial_state: QueueState, +} + +impl CSPlaceholder for CodeDecommitterInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + memory_queue_initial_state: QueueState::::placeholder( + cs, + ), + sorted_requests_queue_initial_state: + QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct CodeDecommitterOutputData { + pub memory_queue_final_state: QueueState, +} + +impl CSPlaceholder for CodeDecommitterOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + memory_queue_final_state: QueueState::::placeholder( + cs, + ), + } + } +} + +pub type CodeDecommitterCycleInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + CodeDecommitterFSMInputOutput, + CodeDecommitterInputData, + CodeDecommitterOutputData, +>; +pub type CodeDecommitterCycleInputOutputWitness = + crate::fsm_input_output::ClosedFormInputWitness< + F, + CodeDecommitterFSMInputOutput, + CodeDecommitterInputData, + CodeDecommitterOutputData, + >; + +use crate::code_unpacker_sha256::full_state_queue::FullStateCircuitQueueRawWitness; +use crate::code_unpacker_sha256::{DecommitQuery, DECOMMIT_QUERY_PACKED_WIDTH}; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct CodeDecommitterCircuitInstanceWitness { + pub closed_form_input: CodeDecommitterCycleInputOutputWitness, + + pub sorted_requests_queue_witness: + FullStateCircuitQueueRawWitness, 12, DECOMMIT_QUERY_PACKED_WIDTH>, + pub code_words: Vec>, +} diff --git a/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs b/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs new file mode 100644 index 00000000..5c555a0a --- /dev/null +++ b/crates/zkevm_circuits/src/code_unpacker_sha256/mod.rs @@ -0,0 +1,716 @@ +pub mod input; + +use input::*; + +use crate::ethereum_types::U256; +use std::collections::VecDeque; +use std::sync::{Arc, RwLock}; + +use crate::base_structures::vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH; +use crate::base_structures::{decommit_query::*, memory_query::*}; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::{gates::*, traits::cs::ConstraintSystem}; +use boojum::field::SmallField; +use boojum::gadgets::sha256::round_function::round_function_over_uint32; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{ + allocatable::{CSAllocatable, CSAllocatableExt}, + selectable::Selectable, + }, + u16::UInt16, + u256::UInt256, + u32::UInt32, +}; + +use crate::fsm_input_output::{circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH, *}; + +use crate::storage_application::ConditionalWitnessAllocator; + +pub fn unpack_code_into_memory_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: CodeDecommitterCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); UInt256::::INTERNAL_STRUCT_LEN]:, + [(); UInt256::::INTERNAL_STRUCT_LEN + 1]:, +{ + let CodeDecommitterCircuitInstanceWitness { + closed_form_input, + sorted_requests_queue_witness, + code_words, + } = witness; + + let code_words: VecDeque = code_words.into_iter().flatten().collect(); + + let mut structured_input = + CodeDecommitterCycleInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + + let requests_queue_state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &structured_input + .observable_input + .sorted_requests_queue_initial_state, + &structured_input + .hidden_fsm_input + .decommittment_requests_queue_state, + ); + + let mut requests_queue = DecommitQueue::::from_state(cs, requests_queue_state); + + use crate::code_unpacker_sha256::full_state_queue::FullStateCircuitQueueWitness; + requests_queue.witness = Arc::new(FullStateCircuitQueueWitness::from_inner_witness( + sorted_requests_queue_witness, + )); + + let memory_queue_state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &structured_input.observable_input.memory_queue_initial_state, + &structured_input.hidden_fsm_input.memory_queue_state, + ); + + let mut memory_queue = MemoryQueryQueue::::from_state(cs, memory_queue_state); + + use boojum::gadgets::traits::allocatable::CSPlaceholder; + let mut starting_fsm_state = CodeDecommittmentFSM::placeholder(cs); + starting_fsm_state.state_get_from_queue = Boolean::allocated_constant(cs, true); + + let initial_state = CodeDecommittmentFSM::conditionally_select( + cs, + structured_input.start_flag, + &starting_fsm_state, + &structured_input.hidden_fsm_input.internal_fsm, + ); + + let code_words_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(code_words)), + }; + + let final_state = unpack_code_into_memory_inner( + cs, + &mut memory_queue, + &mut requests_queue, + initial_state, + code_words_allocator, + round_function, + limit, + ); + + let final_memory_state = memory_queue.into_state(); + let final_requets_state = requests_queue.into_state(); + // form the final state + let done = final_state.finished; + structured_input.completion_flag = done; + structured_input.observable_output = CodeDecommitterOutputData::placeholder(cs); + + structured_input.observable_output.memory_queue_final_state = QueueState::conditionally_select( + cs, + structured_input.completion_flag, + &final_memory_state, + &structured_input.observable_output.memory_queue_final_state, + ); + + structured_input.hidden_fsm_output.internal_fsm = final_state; + structured_input + .hidden_fsm_output + .decommittment_requests_queue_state = final_requets_state; + structured_input.hidden_fsm_output.memory_queue_state = final_memory_state; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +// we take a request to decommit hash H into memory page X. Following our internal conventions +// we decommit individual elements starting from the index 1 in the page, and later on set a full length +// into index 0. All elements are 32 bytes +pub fn unpack_code_into_memory_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + memory_queue: &mut MemoryQueue, + unpack_requests_queue: &mut DecommitQueue, + initial_state: CodeDecommittmentFSM, + code_word_witness: ConditionalWitnessAllocator>, + _round_function: &R, + limit: usize, +) -> CodeDecommittmentFSM +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); UInt256::::INTERNAL_STRUCT_LEN]:, + [(); UInt256::::INTERNAL_STRUCT_LEN + 1]:, +{ + assert!(limit <= u32::MAX as usize); + + // const POP_QUEUE_OR_WRITE_ID: u64 = 0; + // const FINALIZE_SHA256: u64 = 1; + + let mut state = initial_state; + + let mut half = F::ONE; + half.double(); + half = half.inverse().unwrap(); + let half_num = Num::allocated_constant(cs, half); + + let words_to_bits = UInt32::allocated_constant(cs, 32 * 8); + + let initial_state_uint32 = boojum::gadgets::sha256::ivs_as_uint32(cs); + + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + + let zero_u32 = UInt32::zero(cs); + + use zkevm_opcode_defs::VersionedHashDef; + let versioned_hash_top_16_bits = + (zkevm_opcode_defs::versioned_hash::ContractCodeSha256::VERSION_BYTE as u16) << 8; + let versioned_hash_top_16_bits = UInt16::allocated_constant(cs, versioned_hash_top_16_bits); + + for _cycle in 0..limit { + // we need exactly 3 sponges per cycle: + // - two memory reads when we work on the existing decommittment + // - and may be queue pop before it + let (may_be_new_request, _) = + unpack_requests_queue.pop_front(cs, state.state_get_from_queue); + + let hash = may_be_new_request.code_hash; + + let chunks = decompose_uint32_to_uint16s(cs, &hash.inner[7]); + + let version_hash_matches = UInt16::equals(cs, &chunks[1], &versioned_hash_top_16_bits); + // if we did get a fresh request from queue we expect it with a proper version bytes + version_hash_matches.conditionally_enforce_true(cs, state.state_get_from_queue); + + let uint_16_one = UInt16::allocated_constant(cs, 1); + let length_in_words = chunks[0]; + let length_in_words = UInt16::conditionally_select( + cs, + state.state_get_from_queue, + &length_in_words, + &uint_16_one, + ); + + let length_in_rounds = unsafe { length_in_words.increment_unchecked(cs) }; + + let length_in_rounds = length_in_rounds.into_num().mul(cs, &half_num); + // length is always a multiple of 2 since we decided so + + let length_in_rounds = UInt16::from_variable_checked(cs, length_in_rounds.get_variable()); + + let length_in_bits_may_be = unsafe { + UInt32::from_variable_unchecked(length_in_words.get_variable()) + .non_widening_mul(cs, &words_to_bits) + }; + + // turn over the endianess + // we IGNORE the highest 4 bytes + let uint32_zero = UInt32::allocated_constant(cs, 0); + let mut cutted_hash = hash; + cutted_hash.inner[7] = uint32_zero; + + state.num_rounds_left = UInt16::conditionally_select( + cs, + state.state_get_from_queue, + &length_in_rounds, + &state.num_rounds_left, + ); + state.length_in_bits = UInt32::conditionally_select( + cs, + state.state_get_from_queue, + &length_in_bits_may_be, + &state.length_in_bits, + ); + state.timestamp = UInt32::conditionally_select( + cs, + state.state_get_from_queue, + &may_be_new_request.timestamp, + &state.timestamp, + ); + state.current_page = UInt32::conditionally_select( + cs, + state.state_get_from_queue, + &may_be_new_request.page, + &state.current_page, + ); + state.hash_to_compare_against = UInt256::conditionally_select( + cs, + state.state_get_from_queue, + &cutted_hash, + &state.hash_to_compare_against, + ); + state.current_index = UInt32::conditionally_select( + cs, + state.state_get_from_queue, + &uint32_zero, + &state.current_index, + ); + state.sha256_inner_state = <[UInt32; 8]>::conditionally_select( + cs, + state.state_get_from_queue, + &initial_state_uint32, + &state.sha256_inner_state, + ); + + // we decommit if we either decommit or just got a new request + state.state_decommit = state.state_decommit.or(cs, state.state_get_from_queue); + state.state_get_from_queue = boolean_false; + + // even though it's not that useful, we will do it in a checked way for ease of witness + let may_be_num_rounds_left = unsafe { state.num_rounds_left.decrement_unchecked(cs) }; + state.num_rounds_left = UInt16::conditionally_select( + cs, + state.state_decommit, + &may_be_num_rounds_left, + &state.num_rounds_left, + ); + + let last_round = state.num_rounds_left.is_zero(cs); + let finalize = last_round.and(cs, state.state_decommit); + let not_last_round = last_round.negated(cs); + let process_second_word = not_last_round.and(cs, state.state_decommit); + + // we either pop from the queue, or absorb-decommit, or finalize hash + let code_word_0 = code_word_witness.conditionally_allocate(cs, state.state_decommit); + let code_word_0_be_bytes = code_word_0.to_be_bytes(cs); + + // NOTE: we have to enforce a sequence of access to witness, so we always wait for code_word_0 to be resolved + let code_word_1 = code_word_witness.conditionally_allocate_biased( + cs, + process_second_word, + code_word_0.inner[0].get_variable(), + ); + let code_word_1_be_bytes = code_word_1.to_be_bytes(cs); + + // perform two writes. It's never a "pointer" type + let mem_query_0 = MemoryQuery { + timestamp: state.timestamp, + memory_page: state.current_page, + index: state.current_index, + rw_flag: boolean_true, + value: code_word_0, + is_ptr: boolean_false, + }; + + let state_index_incremented = unsafe { state.current_index.increment_unchecked(cs) }; + + state.current_index = UInt32::conditionally_select( + cs, + state.state_decommit, + &state_index_incremented, + &state.current_index, + ); + + let mem_query_1 = MemoryQuery { + timestamp: state.timestamp, + memory_page: state.current_page, + index: state.current_index, + rw_flag: boolean_true, + value: code_word_1, + is_ptr: boolean_false, + }; + + // even if we do not write in practice then we will never use next value too + + let state_index_incremented = unsafe { state.current_index.increment_unchecked(cs) }; + + state.current_index = UInt32::conditionally_select( + cs, + process_second_word, + &state_index_incremented, + &state.current_index, + ); + + memory_queue.push(cs, mem_query_0, state.state_decommit); + memory_queue.push(cs, mem_query_1, process_second_word); + + // mind endianess! + let mut sha256_input = [zero_u32; 16]; + for (dst, src) in sha256_input.iter_mut().zip( + code_word_0_be_bytes + .array_chunks::<4>() + .chain(code_word_1_be_bytes.array_chunks::<4>()), + ) { + *dst = UInt32::from_be_bytes(cs, *src); + } + + // then conditionally form the second half of the block + + let mut sha256_padding = [zero_u32; 8]; + + // padding of single byte of 1<<7 and some zeroes after, and interpret it as BE integer + sha256_padding[0] = UInt32::allocated_constant(cs, 1 << 31); + // last word is just number of bits + sha256_padding[7] = state.length_in_bits; + + assert_eq!(sha256_input.len(), 16); + + for (dst, src) in sha256_input[8..].iter_mut().zip(sha256_padding.iter()) { + *dst = UInt32::conditionally_select(cs, finalize, src, dst); + } + + let sha256_input: [_; 16] = sha256_input.try_into().unwrap(); + + let mut new_internal_state = state.sha256_inner_state; + round_function_over_uint32(cs, &mut new_internal_state, &sha256_input); + + state.sha256_inner_state = <[UInt32; 8]>::conditionally_select( + cs, + state.state_decommit, + &new_internal_state, + &state.sha256_inner_state, + ); + + // make it into uint256, and do not forget to ignore highest four bytes + let hash = UInt256 { + inner: [ + new_internal_state[7], + new_internal_state[6], + new_internal_state[5], + new_internal_state[4], + new_internal_state[3], + new_internal_state[2], + new_internal_state[1], + UInt32::allocated_constant(cs, 0), + ], + }; + + for (part_of_first, part_of_second) in hash + .inner + .iter() + .zip(state.hash_to_compare_against.inner.iter()) + { + Num::conditionally_enforce_equal( + cs, + finalize, + &part_of_first.into_num(), + &part_of_second.into_num(), + ); + } + + // finish + let is_empty = unpack_requests_queue.is_empty(cs); + let not_empty = is_empty.negated(cs); + let done = is_empty.and(cs, finalize); + state.finished = state.finished.or(cs, done); + let proceed_next = not_empty.and(cs, finalize); + state.state_get_from_queue = proceed_next; + let continue_decommit = process_second_word; + state.state_decommit = continue_decommit; + } + + unpack_requests_queue.enforce_consistency(cs); + + state +} + +fn decompose_uint32_to_uint16s>( + cs: &mut CS, + value: &UInt32, +) -> [UInt16; 2] { + let [byte_0, byte_1, byte_2, byte_3] = value.decompose_into_bytes(cs); + + [ + UInt16::from_le_bytes(cs, [byte_0, byte_1]), + UInt16::from_le_bytes(cs, [byte_2, byte_3]), + ] +} + +#[cfg(test)] +mod tests { + use crate::base_structures::decommit_query; + + use super::*; + use crate::base_structures::vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH; + use crate::ethereum_types::{Address, U256}; + use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; + use boojum::cs::implementations::reference_cs::CSDevelopmentAssembly; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueueWitness; + use boojum::gadgets::tables::*; + use boojum::gadgets::traits::allocatable::{CSAllocatable, CSPlaceholder}; + use boojum::gadgets::u160::UInt160; + use boojum::gadgets::u256::UInt256; + use boojum::gadgets::u8::UInt8; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::worker::Worker; + + type F = GoldilocksField; + type P = GoldilocksField; + + #[test] + fn test_code_unpacker_inner() { + // Create a constraint system with proper configuration + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + use boojum::cs::cs_builder::*; + + fn configure< + T: CsBuilderImpl, + GC: GateConfigurationHolder, + TB: StaticToolboxHolder, + >( + builder: CsBuilder, + ) -> CsBuilder, impl StaticToolboxHolder> { + let builder = builder.allow_lookup( + LookupParameters::UseSpecializedColumnsWithTableIdAsConstant { + width: 4, + 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 = 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 = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 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 table = create_maj4_table(); + owned_cs.add_lookup_table::(table); + + let table = create_tri_xor_table(); + owned_cs.add_lookup_table::(table); + + let table = create_ch4_table(); + owned_cs.add_lookup_table::(table); + + let table = create_4bit_chunk_split_table::(); + owned_cs.add_lookup_table::, 4>(table); + let table = create_4bit_chunk_split_table::(); + owned_cs.add_lookup_table::, 4>(table); + + let cs = &mut owned_cs; + + // Create inputs for the inner function + let execute = Boolean::allocated_constant(cs, true); + let mut memory_queue = MemoryQueryQueue::::empty(cs); + let mut decommit_queue = DecommitQueue::::empty(cs); + + let decommit_queue_witness = create_request_queue_witness(cs); + for el in decommit_queue_witness { + decommit_queue.push(cs, el, execute); + } + + let round_function = Poseidon2Goldilocks; + let limit = 40; + + let mut starting_fsm_state = CodeDecommittmentFSM::placeholder(cs); + starting_fsm_state.state_get_from_queue = Boolean::allocated_constant(cs, true); + + let word_witness = create_witness_allocator(cs); + + // Run the inner function + let _final_state = unpack_code_into_memory_inner( + cs, + &mut memory_queue, + &mut decommit_queue, + starting_fsm_state, + word_witness, + &round_function, + limit, + ); + + // Check the corectness + let boolean_true = Boolean::allocated_constant(cs, true); + let no_decommits_left = decommit_queue.is_empty(cs); + no_decommits_left.conditionally_enforce_true(cs, boolean_true); + + let final_memory_queue_state = compute_memory_queue_state(cs); + for (lhs, rhs) in final_memory_queue_state + .tail + .tail + .iter() + .zip(memory_queue.tail.iter()) + { + Num::enforce_equal(cs, lhs, rhs); + } + + cs.pad_and_shrink(); + let worker = Worker::new(); + let mut owned_cs = owned_cs.into_assembly(); + owned_cs.print_gate_stats(); + assert!(owned_cs.check_if_satisfied(&worker)); + } + + fn create_witness_allocator>( + _cs: &mut CS, + ) -> ConditionalWitnessAllocator> { + let code_words_witness = get_byte_code_witness(); + + let code_words_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(code_words_witness.into())), + }; + + code_words_allocator + } + + fn create_request_queue_witness>(cs: &mut CS) -> Vec> { + let code_hash = get_code_hash_witness(); + + let witness = DecommitQueryWitness:: { + code_hash, + page: 2368, + is_first: true, + timestamp: 40973, + }; + + let result = DecommitQuery::allocate(cs, witness); + + vec![result] + } + + fn compute_memory_queue_state>( + cs: &mut CS, + ) -> QueueState { + let boolean_true = Boolean::allocate(cs, true); + let mut memory_queue = MemoryQueryQueue::::empty(cs); + + for (index, byte_code) in get_byte_code_witness().into_iter().enumerate() { + let code_word = byte_code; + + let witness = MemoryQueryWitness:: { + timestamp: 40973, + memory_page: 2368, + index: index as u32, + rw_flag: true, + value: code_word, + is_ptr: false, + }; + + let mem_query = MemoryQuery::allocate(cs, witness); + memory_queue.push(cs, mem_query, boolean_true); + } + + memory_queue.into_state() + } + + fn get_code_hash_witness() -> U256 { + U256::from_dec_str( + "452313746998214869734508634865817576060841700842481516984674100922521850987", + ) + .unwrap() + } + + fn get_byte_code_witness() -> [U256; 33] { + [ + "1766847064778396883786768274127037193463854637992579046408942445291110457", + "20164191265753785488708351879363656296986696452890681959140868413", + "230371115084700836133063546338735802439952161310848373590933373335", + "315943403191476362254281516594763167672429992099855389200211772263", + "2588391838226565201098400008033996167521966238460106068954150751699824", + "211454133716954493758258064291659686758464834211097193222448873537", + "755661767828730679150831008859380024863962510235974769491784599863434", + "792722291272121268575835624931127487546448993321488813145330852157", + "422901469332729376614825971396751231447294514335432723148645467189", + "1725759105948670702159993994396013188152302135650842820951922845941817", + "14404925315129564325488546414599286071787685034596420832328728893", + "105318844962770555588522034545405007512793608572268457634281029689", + "809009025005867921013546649078805150441502566947026555373812841472317", + "105319039552922728847306638821735002815060289446290876141406257209", + "211036116403988059590520874377734556453268914677076498415914844236", + "1860236630555339893722514217592548129137355495764522944782381539722297", + "7764675265080516174154537029624996607702824353047648753753695920848961", + "166085834804801355241996194532094100761080533058945135475412415734372892697", + "210624740264657758079982132082095218827023230710668103574628597825", + "6588778667853837091013175373259869215603100833946083743887393845", + "970663392666895392257512002784069674802928967218673951998329152864412", + "107163641223087021704532235449659860614865516518490400358586646684", + "212475932891644255176568460416224004946153631397142661140395918382", + "2588266777670668978723371609412889835982022923910173388865216276661294", + "2588155298151653810924065765928146445685410508309586403046357418901504", + "53919893334301279589334030174039261347274288845081144962207220498432", + "1461501637330902918203684832716283019655932542975", + "432420386565659656852420866394968145599", + "432420386565659656852420866394968145600", + "35408467139433450592217433187231851964531694900788300625387963629091585785856", + "79228162514264337610723819524", + "4294967295", + "0", + ] + .map(|el| U256::from_dec_str(el).unwrap()) + } +} diff --git a/crates/zkevm_circuits/src/config.rs b/crates/zkevm_circuits/src/config.rs new file mode 100644 index 00000000..5c11895f --- /dev/null +++ b/crates/zkevm_circuits/src/config.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "verbose_circuits")] +pub const CIRCUIT_VERSOBE: bool = true; + +#[cfg(not(feature = "verbose_circuits"))] +pub const CIRCUIT_VERSOBE: bool = false; diff --git a/crates/zkevm_circuits/src/demux_log_queue/input.rs b/crates/zkevm_circuits/src/demux_log_queue/input.rs new file mode 100644 index 00000000..119a5fc9 --- /dev/null +++ b/crates/zkevm_circuits/src/demux_log_queue/input.rs @@ -0,0 +1,106 @@ +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + vm_state::*, +}; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use cs_derive::*; +use derivative::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct LogDemuxerFSMInputOutput { + pub initial_log_queue_state: QueueState, + pub storage_access_queue_state: QueueState, + pub events_access_queue_state: QueueState, + pub l1messages_access_queue_state: QueueState, + pub keccak256_access_queue_state: QueueState, + pub sha256_access_queue_state: QueueState, + pub ecrecover_access_queue_state: QueueState, +} + +impl CSPlaceholder for LogDemuxerFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + initial_log_queue_state: QueueState::::placeholder(cs), + storage_access_queue_state: QueueState::::placeholder(cs), + events_access_queue_state: QueueState::::placeholder(cs), + l1messages_access_queue_state: QueueState::::placeholder(cs), + keccak256_access_queue_state: QueueState::::placeholder(cs), + sha256_access_queue_state: QueueState::::placeholder(cs), + ecrecover_access_queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct LogDemuxerInputData { + pub initial_log_queue_state: QueueState, +} + +impl CSPlaceholder for LogDemuxerInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + initial_log_queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct LogDemuxerOutputData { + pub storage_access_queue_state: QueueState, + pub events_access_queue_state: QueueState, + pub l1messages_access_queue_state: QueueState, + pub keccak256_access_queue_state: QueueState, + pub sha256_access_queue_state: QueueState, + pub ecrecover_access_queue_state: QueueState, +} + +impl CSPlaceholder for LogDemuxerOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + storage_access_queue_state: QueueState::::placeholder(cs), + events_access_queue_state: QueueState::::placeholder(cs), + l1messages_access_queue_state: QueueState::::placeholder(cs), + keccak256_access_queue_state: QueueState::::placeholder(cs), + sha256_access_queue_state: QueueState::::placeholder(cs), + ecrecover_access_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type LogDemuxerInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + LogDemuxerFSMInputOutput, + LogDemuxerInputData, + LogDemuxerOutputData, +>; + +pub type LogDemuxerInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + LogDemuxerFSMInputOutput, + LogDemuxerInputData, + LogDemuxerOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct LogDemuxerCircuitInstanceWitness { + pub closed_form_input: LogDemuxerInputOutputWitness, + pub initial_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, +} diff --git a/crates/zkevm_circuits/src/demux_log_queue/mod.rs b/crates/zkevm_circuits/src/demux_log_queue/mod.rs new file mode 100644 index 00000000..438bf117 --- /dev/null +++ b/crates/zkevm_circuits/src/demux_log_queue/mod.rs @@ -0,0 +1,922 @@ +use super::*; + +pub mod input; + +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + vm_state::*, +}; +use crate::fsm_input_output::ClosedFormInputCompactForm; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::{gates::*, traits::cs::ConstraintSystem}; +use boojum::field::SmallField; +use boojum::gadgets::queue::queue_optimizer::SpongeOptimizer; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{ + allocatable::CSAllocatableExt, encodable::CircuitEncodableExt, selectable::Selectable, + }, + u160::*, +}; + +use zkevm_opcode_defs::system_params::*; + +use crate::{ + demux_log_queue::input::*, + fsm_input_output::{circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH, *}, +}; + +pub type StorageLogQueue = CircuitQueue, 8, 12, 4, 4, 20, R>; +pub type StorageLogQueueWitness = + CircuitQueueWitness, QUEUE_STATE_WIDTH, LOG_QUERY_PACKED_WIDTH>; + +pub fn demultiplex_storage_logs_enty_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: LogDemuxerCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let LogDemuxerCircuitInstanceWitness { + closed_form_input, + initial_queue_witness, + } = witness; + + let mut structured_input = + LogDemuxerInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + + // passthrough must be trivial + structured_input + .observable_input + .initial_log_queue_state + .enforce_trivial_head(cs); + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &structured_input.observable_input.initial_log_queue_state, + &structured_input.hidden_fsm_input.initial_log_queue_state, + ); + let mut initial_queue = StorageLogQueue::::from_state(cs, state); + use std::sync::Arc; + let initial_queue_witness = CircuitQueueWitness::from_inner_witness(initial_queue_witness); + initial_queue.witness = Arc::new(initial_queue_witness); + + // for the rest it's just select between empty or from FSM + let queue_states_from_fsm = [ + &structured_input.hidden_fsm_input.storage_access_queue_state, + &structured_input.hidden_fsm_input.events_access_queue_state, + &structured_input + .hidden_fsm_input + .l1messages_access_queue_state, + &structured_input + .hidden_fsm_input + .keccak256_access_queue_state, + &structured_input.hidden_fsm_input.sha256_access_queue_state, + &structured_input + .hidden_fsm_input + .ecrecover_access_queue_state, + ]; + + let empty_state = QueueState::empty(cs); + let [mut storage_access_queue, mut events_access_queue, mut l1messages_access_queue, mut keccak256_access_queue, mut sha256_access_queue, mut ecrecover_access_queue] = + queue_states_from_fsm.map(|el| { + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &empty_state, + &el, + ); + StorageLogQueue::::from_state(cs, state) + }); + + let input_queues = [ + &mut storage_access_queue, + &mut events_access_queue, + &mut l1messages_access_queue, + &mut keccak256_access_queue, + &mut sha256_access_queue, + &mut ecrecover_access_queue, + ]; + + demultiplex_storage_logs_inner(cs, &mut initial_queue, input_queues, limit); + + use boojum::gadgets::traits::allocatable::CSPlaceholder; + // form the final state + structured_input.observable_output = LogDemuxerOutputData::placeholder(cs); + + let completed = initial_queue.is_empty(cs); + structured_input.completion_flag = completed; + + structured_input.hidden_fsm_output.initial_log_queue_state = initial_queue.into_state(); + + structured_input + .hidden_fsm_output + .storage_access_queue_state = storage_access_queue.into_state(); + + structured_input.hidden_fsm_output.events_access_queue_state = events_access_queue.into_state(); + + structured_input + .hidden_fsm_output + .l1messages_access_queue_state = l1messages_access_queue.into_state(); + + structured_input + .hidden_fsm_output + .keccak256_access_queue_state = keccak256_access_queue.into_state(); + + structured_input.hidden_fsm_output.sha256_access_queue_state = sha256_access_queue.into_state(); + + structured_input + .hidden_fsm_output + .ecrecover_access_queue_state = ecrecover_access_queue.into_state(); + + // copy into observable output + structured_input + .observable_output + .storage_access_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input + .hidden_fsm_output + .storage_access_queue_state, + &structured_input + .observable_output + .storage_access_queue_state, + ); + structured_input.observable_output.events_access_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input.hidden_fsm_output.events_access_queue_state, + &structured_input.observable_output.events_access_queue_state, + ); + structured_input + .observable_output + .l1messages_access_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input + .hidden_fsm_output + .l1messages_access_queue_state, + &structured_input + .observable_output + .l1messages_access_queue_state, + ); + structured_input + .observable_output + .keccak256_access_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input + .hidden_fsm_output + .keccak256_access_queue_state, + &structured_input + .observable_output + .keccak256_access_queue_state, + ); + structured_input.observable_output.sha256_access_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input.hidden_fsm_output.sha256_access_queue_state, + &structured_input.observable_output.sha256_access_queue_state, + ); + structured_input + .observable_output + .ecrecover_access_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input + .hidden_fsm_output + .ecrecover_access_queue_state, + &structured_input + .observable_output + .ecrecover_access_queue_state, + ); + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +pub const NUM_SEPARATE_QUEUES: usize = 6; + +#[repr(u64)] +pub enum LogType { + RollupStorage = 0, + Events = 1, + L1Messages = 2, + KeccakCalls = 3, + Sha256Calls = 4, + ECRecoverCalls = 5, + PorterStorage = 1024, // force unreachable +} + +pub fn demultiplex_storage_logs_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + storage_log_queue: &mut StorageLogQueue, + output_queues: [&mut StorageLogQueue; NUM_SEPARATE_QUEUES], + limit: usize, +) where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + assert!(limit <= u32::MAX as usize); + + let [rollup_storage_queue, events_queue, l1_messages_queue, keccak_calls_queue, sha256_calls_queue, ecdsa_calls_queue] = + output_queues; + + let keccak_precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::KECCAK256_ROUND_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + let sha256_precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::SHA256_ROUND_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + let ecrecover_precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::ECRECOVER_INNER_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + + // we have 6 queues to demux into, and up to 3 sponges per any push + // use crate::base_structures::log_query::LOG_QUERY_ABSORBTION_ROUNDS; + // let mut optimizer = SpongeOptimizer::::new(LOG_QUERY_ABSORBTION_ROUNDS * limit); + + for _ in 0..limit { + // debug_assert!(optimizer.is_fresh()); + + let queue_is_empty = storage_log_queue.is_empty(cs); + let execute = queue_is_empty.negated(cs); + let popped = storage_log_queue.pop_front(cs, execute); + + let [aux_byte_for_storage, aux_byte_for_event, aux_byte_for_l1_message, aux_byte_for_precompile_call] = + [ + STORAGE_AUX_BYTE, + EVENT_AUX_BYTE, + L1_MESSAGE_AUX_BYTE, + PRECOMPILE_AUX_BYTE, + ] + .map(|byte| UInt8::allocated_constant(cs, byte)); + + let is_storage_aux_byte = UInt8::equals(cs, &aux_byte_for_storage, &popped.0.aux_byte); + let is_event_aux_byte = UInt8::equals(cs, &aux_byte_for_event, &popped.0.aux_byte); + let is_l1_message_aux_byte = + UInt8::equals(cs, &aux_byte_for_l1_message, &popped.0.aux_byte); + let is_precompile_aux_byte = + UInt8::equals(cs, &aux_byte_for_precompile_call, &popped.0.aux_byte); + + let is_keccak_address = UInt160::equals(cs, &keccak_precompile_address, &popped.0.address); + let is_sha256_address = UInt160::equals(cs, &sha256_precompile_address, &popped.0.address); + let is_ecrecover_address = + UInt160::equals(cs, &ecrecover_precompile_address, &popped.0.address); + + let is_rollup_shard = popped.0.shard_id.is_zero(cs); + let execute_rollup_storage = + Boolean::multi_and(cs, &[is_storage_aux_byte, is_rollup_shard, execute]); + let is_porter_shard = is_rollup_shard.negated(cs); + let execute_porter_storage = + Boolean::multi_and(cs, &[is_storage_aux_byte, is_porter_shard, execute]); + + let boolean_false = Boolean::allocated_constant(cs, false); + Boolean::enforce_equal(cs, &execute_porter_storage, &boolean_false); + + let execute_event = Boolean::multi_and(cs, &[is_event_aux_byte, execute]); + let execute_l1_message = Boolean::multi_and(cs, &[is_l1_message_aux_byte, execute]); + let execute_keccak_call = + Boolean::multi_and(cs, &[is_precompile_aux_byte, is_keccak_address, execute]); + let execute_sha256_call = + Boolean::multi_and(cs, &[is_precompile_aux_byte, is_sha256_address, execute]); + let execute_ecrecover_call = + Boolean::multi_and(cs, &[is_precompile_aux_byte, is_ecrecover_address, execute]); + + // rollup_storage_queue.push_encoding_with_optimizer_without_changing_witness( + // cs, + // popped.1, + // execute_rollup_storage, + // LogType::RollupStorage as usize, + // &mut optimizer + // ); + // events_queue.push_encoding_with_optimizer_without_changing_witness( + // cs, + // popped.1, + // execute_event, + // LogType::Events as usize, + // &mut optimizer + // ); + // l1_messages_queue.push_encoding_with_optimizer_without_changing_witness( + // cs, + // popped.1, + // execute_l1_message, + // LogType::L1Messages as usize, + // &mut optimizer + // ); + // keccak_calls_queue.push_encoding_with_optimizer_without_changing_witness( + // cs, + // popped.1, + // execute_keccak_call, + // LogType::KeccakCalls as usize, + // &mut optimizer + // ); + // sha256_calls_queue.push_encoding_with_optimizer_without_changing_witness( + // cs, + // popped.1, + // execute_sha256_call, + // LogType::Sha256Calls as usize, + // &mut optimizer + // ); + // ecdsa_calls_queue.push_encoding_with_optimizer_without_changing_witness( + // cs, + // popped.1, + // execute_ecrecover_call, + // LogType::ECRecoverCalls as usize, + // &mut optimizer + // ); + + let bitmask = [ + execute_rollup_storage, + execute_event, + execute_l1_message, + execute_keccak_call, + execute_sha256_call, + execute_ecrecover_call, + ]; + + push_with_optimize( + cs, + [ + rollup_storage_queue, + events_queue, + l1_messages_queue, + keccak_calls_queue, + sha256_calls_queue, + ecdsa_calls_queue, + ], + bitmask, + popped.0, + ); + + let expected_bitmask_bits = [ + is_storage_aux_byte, + is_event_aux_byte, + is_l1_message_aux_byte, + is_precompile_aux_byte, + ]; + + let is_bitmask = check_if_bitmask_and_if_empty(cs, expected_bitmask_bits); + is_bitmask.conditionally_enforce_true(cs, execute); + + // // we enforce optimizer in this round, and it clears it up + // optimizer.enforce(cs); + } + + storage_log_queue.enforce_consistency(cs); + + // checks in "Drop" interact badly with some tools, so we check it during testing instead + // debug_assert!(optimizer.is_fresh()); +} + +pub fn push_with_optimize< + F: SmallField, + CS: ConstraintSystem, + EL: CircuitEncodableExt, + const AW: usize, + const SW: usize, + const CW: usize, + const T: usize, + const N: usize, + R: CircuitRoundFunction, + const NUM_QUEUE: usize, +>( + cs: &mut CS, + mut queues: [&mut CircuitQueue; NUM_QUEUE], + bitmask: [Boolean; NUM_QUEUE], + value_encoding: EL, +) where + [(); >::INTERNAL_STRUCT_LEN]:, +{ + let mut states = queues.iter().map(|x| x.into_state()); + let mut state = states.next().unwrap(); + + for (bit, next_state) in bitmask.iter().skip(1).zip(states) { + state = QueueState::conditionally_select(cs, *bit, &next_state, &state); + } + + let mut exec_queue = CircuitQueue::::from_raw_parts( + cs, + state.head, + state.tail.tail, + state.tail.length, + ); + + let boolean_true = Boolean::allocated_constant(cs, true); + + exec_queue.push(cs, value_encoding, boolean_true); + + for (bit, queue) in bitmask.into_iter().zip(queues.iter_mut()) { + // We don't need to update head + // queue.head = <[Num; T]>::conditionally_select(cs, bit, &exec_queue.head, &queue.head); + queue.tail = <[Num; T]>::conditionally_select(cs, bit, &exec_queue.tail, &queue.tail); + queue.length = UInt32::conditionally_select(cs, bit, &exec_queue.length, &queue.length); + } +} + +pub fn check_if_bitmask_and_if_empty, const N: usize>( + cs: &mut CS, + mask: [Boolean; N], +) -> Boolean { + let lc: [_; N] = mask.map(|el| (el.get_variable(), F::ONE)); + + let lc = Num::linear_combination(cs, &lc); + + let one = Num::from_variable(cs.allocate_constant(F::ONE)); + let is_boolean = Num::equals(cs, &lc, &one); + + is_boolean +} + +#[cfg(test)] +mod tests { + use super::*; + use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::tables::*; + use boojum::gadgets::u160::UInt160; + use boojum::gadgets::u256::UInt256; + use boojum::gadgets::u32::UInt32; + use boojum::gadgets::u8::UInt8; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::worker::Worker; + use ethereum_types::{Address, U256}; + type F = GoldilocksField; + type P = GoldilocksField; + + #[test] + fn test_demultiplex_storage_logs_inner() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + use boojum::cs::cs_builder::*; + + 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 = 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 = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 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(()); + + // add tables + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let cs = &mut owned_cs; + + // start test + let execute = Boolean::allocated_constant(cs, true); + + let mut storage_log_queue = StorageLogQueue::::empty(cs); + let unsorted_input = witness_input_unsorted(cs); + for el in unsorted_input { + storage_log_queue.push(cs, el, execute); + } + let mut output_queue = StorageLogQueue::empty(cs); + let mut output_queue1 = StorageLogQueue::empty(cs); + let mut output_queue2 = StorageLogQueue::empty(cs); + let mut output_queue3 = StorageLogQueue::empty(cs); + let mut output_queue4 = StorageLogQueue::empty(cs); + let mut output_queue5 = StorageLogQueue::empty(cs); + + let output = [ + &mut output_queue, + &mut output_queue1, + &mut output_queue2, + &mut output_queue3, + &mut output_queue4, + &mut output_queue5, + ]; + let limit = 16; + demultiplex_storage_logs_inner(cs, &mut storage_log_queue, output, limit); + + cs.pad_and_shrink(); + let worker = Worker::new(); + let mut owned_cs = owned_cs.into_assembly(); + owned_cs.print_gate_stats(); + assert!(owned_cs.check_if_satisfied(&worker)); + } + + fn witness_input_unsorted>(cs: &mut CS) -> Vec> { + let mut unsorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + let zero_8 = UInt8::allocated_constant(cs, 0); + let zero_32 = UInt32::allocated_constant(cs, 0); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1205), + }; + + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("1").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1425), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1609), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("7").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1777), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1969), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("5").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2253), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("10").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2357), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2429), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("4").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2681), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("9").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2797), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("9").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2829), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2901), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("3").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3089), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("8").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3193), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3265), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("2").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3421), + }; + unsorted_querie.push(q); + + unsorted_querie + } +} diff --git a/crates/zkevm_circuits/src/ecrecover/baseline.rs b/crates/zkevm_circuits/src/ecrecover/baseline.rs new file mode 100644 index 00000000..1e016070 --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/baseline.rs @@ -0,0 +1,937 @@ +use super::*; +use crate::base_structures::log_query::*; +use crate::base_structures::memory_query::*; +use crate::base_structures::precompile_input_outputs::PrecompileFunctionOutputData; +use crate::demux_log_queue::StorageLogQueue; +use crate::ethereum_types::U256; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::fsm_input_output::*; +use arrayvec::ArrayVec; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::crypto_bigint::{Zero, U1024}; +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::curves::sw_projective::SWProjectivePoint; +use boojum::gadgets::keccak256::keccak256; +use boojum::gadgets::non_native_field::implementations::*; +use boojum::gadgets::num::Num; +use boojum::gadgets::queue::CircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::allocatable::{CSAllocatableExt, CSPlaceholder}; +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::u160::UInt160; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use cs_derive::*; +use std::collections::VecDeque; +use std::sync::{Arc, RwLock}; +use zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE; + +pub const MEMORY_QUERIES_PER_CALL: usize = 4; + +use super::input::*; + +#[derive(Derivative, CSSelectable)] +#[derivative(Clone, Debug)] +pub struct EcrecoverPrecompileCallParams { + pub input_page: UInt32, + pub input_offset: UInt32, + pub output_page: UInt32, + pub output_offset: UInt32, +} + +impl EcrecoverPrecompileCallParams { + // pub fn empty() -> Self { + // Self { + // input_page: UInt32::::zero(), + // input_offset: UInt32::::zero(), + // output_page: UInt32::::zero(), + // output_offset: UInt32::::zero(), + // } + // } + + pub fn from_encoding>(_cs: &mut CS, encoding: UInt256) -> Self { + let input_offset = encoding.inner[0]; + let output_offset = encoding.inner[2]; + let input_page = encoding.inner[4]; + let output_page = encoding.inner[5]; + + let new = Self { + input_page, + input_offset, + output_page, + output_offset, + }; + + new + } +} + +const NUM_WORDS: usize = 17; +const SECP_B_COEF: u64 = 7; +const EXCEPTION_FLAGS_ARR_LEN: usize = 8; +const NUM_MEMORY_READS_PER_CYCLE: usize = 4; +const X_POWERS_ARR_LEN: usize = 256; +const VALID_Y_IN_EXTERNAL_FIELD: u64 = 4; +const VALID_X_CUBED_IN_EXTERNAL_FIELD: u64 = 9; + +// 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< + F: SmallField, + CS: ConstraintSystem, + P: boojum::pairing::ff::PrimeField, + const N: usize, +>( + cs: &mut CS, + elem: &UInt256, + params: &Arc>, +) -> (NonNativeFieldOverU16, Boolean) +where + [(); N + 1]:, +{ + let is_zero = elem.is_zero(cs); + let one_nn = NonNativeFieldOverU16::::allocated_constant(cs, P::one(), params); + // we still have to decompose it into u16 words + let zero_var = cs.allocate_constant(F::ZERO); + let mut limbs = [zero_var; N]; + assert!(N >= 16); + for (dst, src) in limbs.array_chunks_mut::<2>().zip(elem.inner.iter()) { + let [b0, b1, b2, b3] = src.to_le_bytes(cs); + let low = UInt16::from_le_bytes(cs, [b0, b1]); + let high = UInt16::from_le_bytes(cs, [b2, b3]); + + *dst = [low.get_variable(), high.get_variable()]; + } + + let mut max_value = U1024::from_word(1u64); + max_value = max_value.shl_vartime(256); + max_value = max_value.saturating_sub(&U1024::from_word(1u64)); + + let (overflows, rem) = max_value.div_rem(¶ms.modulus_u1024); + + let mut max_moduluses = overflows.as_words()[0] as u32; + if rem.is_zero().unwrap_u8() != 1 { + max_moduluses += 1; + } + + let element = NonNativeFieldOverU16 { + limbs: limbs, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses }, + form: RepresentationForm::Normalized, + params: params.clone(), + _marker: std::marker::PhantomData, + }; + + let selected = Selectable::conditionally_select(cs, is_zero, &one_nn, &element); + + (selected, is_zero) +} + +fn convert_uint256_to_field_element< + F: SmallField, + CS: ConstraintSystem, + P: boojum::pairing::ff::PrimeField, + const N: usize, +>( + cs: &mut CS, + elem: &UInt256, + params: &Arc>, +) -> NonNativeFieldOverU16 { + // we still have to decompose it into u16 words + let zero_var = cs.allocate_constant(F::ZERO); + let mut limbs = [zero_var; N]; + assert!(N >= 16); + for (dst, src) in limbs.array_chunks_mut::<2>().zip(elem.inner.iter()) { + let [b0, b1, b2, b3] = src.to_le_bytes(cs); + let low = UInt16::from_le_bytes(cs, [b0, b1]); + let high = UInt16::from_le_bytes(cs, [b2, b3]); + + *dst = [low.get_variable(), high.get_variable()]; + } + + let mut max_value = U1024::from_word(1u64); + max_value = max_value.shl_vartime(256); + max_value = max_value.saturating_sub(&U1024::from_word(1u64)); + + let (overflows, rem) = max_value.div_rem(¶ms.modulus_u1024); + let mut max_moduluses = overflows.as_words()[0] as u32; + if rem.is_zero().unwrap_u8() != 1 { + max_moduluses += 1; + } + + let element = NonNativeFieldOverU16 { + limbs: limbs, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses }, + form: RepresentationForm::Normalized, + params: params.clone(), + _marker: std::marker::PhantomData, + }; + + element +} + +fn ecrecover_precompile_inner_routine>( + cs: &mut CS, + recid: &UInt8, + r: &UInt256, + s: &UInt256, + message_hash: &UInt256, + valid_x_in_external_field: Secp256BaseNNField, + valid_y_in_external_field: Secp256BaseNNField, + valid_t_in_external_field: Secp256BaseNNField, + base_field_params: &Arc, + scalar_field_params: &Arc, +) -> (Boolean, UInt256) { + use boojum::pairing::ff::Field; + let curve_b = Secp256Affine::b_coeff(); + + let mut minus_one = Secp256Fq::one(); + minus_one.negate(); + + let mut curve_b_nn = + Secp256BaseNNField::::allocated_constant(cs, curve_b, &base_field_params); + let mut minus_one_nn = + Secp256BaseNNField::::allocated_constant(cs, minus_one, &base_field_params); + + let secp_n_u256 = U256([ + scalar_field_params.modulus_u1024.as_ref().as_words()[0], + scalar_field_params.modulus_u1024.as_ref().as_words()[1], + scalar_field_params.modulus_u1024.as_ref().as_words()[2], + scalar_field_params.modulus_u1024.as_ref().as_words()[3], + ]); + let secp_n_u256 = UInt256::allocated_constant(cs, secp_n_u256); + + let secp_p_u256 = U256([ + base_field_params.modulus_u1024.as_ref().as_words()[0], + base_field_params.modulus_u1024.as_ref().as_words()[1], + base_field_params.modulus_u1024.as_ref().as_words()[2], + base_field_params.modulus_u1024.as_ref().as_words()[3], + ]); + let secp_p_u256 = UInt256::allocated_constant(cs, secp_p_u256); + + let mut exception_flags = ArrayVec::<_, EXCEPTION_FLAGS_ARR_LEN>::new(); + + // recid = (x_overflow ? 2 : 0) | (secp256k1_fe_is_odd(&r.y) ? 1 : 0) + // The point X = (x, y) we are going to recover is not known at the start, but it is strongly related to r. + // This is because x = r + kn for some integer k, where x is an element of the field F_q . In other words, x < q. + // (here n is the order of group of points on elleptic curve) + // For secp256k1 curve values of q and n are relatively close, that is, + // the probability of a random element of Fq being greater than n is about 1/{2^128}. + // This in turn means that the overwhelming majority of r determine a unique x, however some of them determine + // two: x = r and x = r + n. If x_overflow flag is set than x = r + n + + let [y_is_odd, x_overflow, ..] = + Num::::from_variable(recid.get_variable()).spread_into_bits::<_, 8>(cs); + + 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]); + exception_flags.push(error); + + // we handle x separately as it is the only element of base field of a curve (not a scalar field element!) + // check that x < q - order of base point on Secp256 curve + // if it is not actually the case - mask x to be zero + let (_res, is_in_range) = x_as_u256.overflowing_sub(cs, &secp_p_u256); + x_as_u256 = x_as_u256.mask(cs, is_in_range); + let x_is_not_in_range = is_in_range.negated(cs); + exception_flags.push(x_is_not_in_range); + + let mut x_fe = convert_uint256_to_field_element(cs, &x_as_u256, &base_field_params); + + let (mut r_fe, r_is_zero) = + convert_uint256_to_field_element_masked(cs, &r, &scalar_field_params); + exception_flags.push(r_is_zero); + let (mut s_fe, s_is_zero) = + convert_uint256_to_field_element_masked(cs, &s, &scalar_field_params); + exception_flags.push(s_is_zero); + + // NB: although it is not strictly an exception we also assume that hash is never zero as field element + let (mut message_hash_fe, message_hash_is_zero) = + convert_uint256_to_field_element_masked(cs, &message_hash, &scalar_field_params); + exception_flags.push(message_hash_is_zero); + + // curve equation is y^2 = x^3 + b + // we compute t = r^3 + b and check if t is a quadratic residue or not. + // we do this by computing Legendre symbol (t, p) = t^[(p-1)/2] (mod p) + // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + // n = (p-1)/2 = 2^255 - 2^31 - 2^8 - 2^7 - 2^6 - 2^5 - 2^3 - 1 + // we have to compute t^b = t^{2^255} / ( t^{2^31} * t^{2^8} * t^{2^7} * t^{2^6} * t^{2^5} * t^{2^3} * t) + // if t is not a quadratic residue we return error and replace x by another value that will make + // t = x^3 + b a quadratic residue + + let mut t = x_fe.square(cs); + t = t.mul(cs, &mut x_fe); + t = t.add(cs, &mut curve_b_nn); + + let t_is_zero = t.is_zero(cs); + exception_flags.push(t_is_zero); + + // if t is zero then just mask + let t = Selectable::conditionally_select(cs, t_is_zero, &valid_t_in_external_field, &t); + + // array of powers of t of the form t^{2^i} starting from i = 0 to 255 + let mut t_powers = Vec::with_capacity(X_POWERS_ARR_LEN); + t_powers.push(t); + + for _ in 1..X_POWERS_ARR_LEN { + let prev = t_powers.last_mut().unwrap(); + let next = prev.square(cs); + t_powers.push(next); + } + + let mut acc = t_powers[0].clone(); + for idx in [3, 5, 6, 7, 8, 31].into_iter() { + let other = &mut t_powers[idx]; + acc = acc.mul(cs, other); + } + let mut legendre_symbol = t_powers[255].div_unchecked(cs, &mut acc); + + // we can also reuse the same values to compute square root in case of p = 3 mod 4 + // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + // n = (p+1)/4 = 2^254 - 2^30 - 2^7 - 2^6 - 2^5 - 2^4 - 2^2 + + let mut acc_2 = t_powers[2].clone(); + for idx in [4, 5, 6, 7, 30].into_iter() { + let other = &mut t_powers[idx]; + acc_2 = acc_2.mul(cs, other); + } + + let mut may_be_recovered_y = t_powers[254].div_unchecked(cs, &mut acc_2); + may_be_recovered_y.normalize(cs); + let mut may_be_recovered_y_negated = may_be_recovered_y.negated(cs); + may_be_recovered_y_negated.normalize(cs); + + let [lowest_bit, ..] = + Num::::from_variable(may_be_recovered_y.limbs[0]).spread_into_bits::<_, 16>(cs); + + // if lowest bit != parity bit, then we need conditionally select + let should_swap = lowest_bit.xor(cs, y_is_odd); + let may_be_recovered_y = Selectable::conditionally_select( + cs, + should_swap, + &may_be_recovered_y_negated, + &may_be_recovered_y, + ); + + let t_is_nonresidue = + Secp256BaseNNField::::equals(cs, &mut legendre_symbol, &mut minus_one_nn); + exception_flags.push(t_is_nonresidue); + // unfortunately, if t is found to be a quadratic nonresidue, we can't simply let x to be zero, + // because then t_new = 7 is again a quadratic nonresidue. So, in this case we let x to be 9, then + // t = 16 is a quadratic residue + let x = + Selectable::conditionally_select(cs, t_is_nonresidue, &valid_x_in_external_field, &x_fe); + let y = Selectable::conditionally_select( + cs, + t_is_nonresidue, + &valid_y_in_external_field, + &may_be_recovered_y, + ); + + // we recovered (x, y) using curve equation, so it's on curve (or was masked) + let mut r_fe_inversed = r_fe.inverse_unchecked(cs); + let mut s_by_r_inv = s_fe.mul(cs, &mut r_fe_inversed); + 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 gen_negated = Secp256Affine::one(); + gen_negated.negate(); + let (gen_negated_x, gen_negated_y) = gen_negated.into_xy_unchecked(); + let gen_negated_x = + Secp256BaseNNField::allocated_constant(cs, gen_negated_x, base_field_params); + let gen_negated_y = + Secp256BaseNNField::allocated_constant(cs, gen_negated_y, base_field_params); + + let s_by_r_inv_normalized_lsb_bits: Vec<_> = s_by_r_inv + .limbs + .iter() + .map(|el| Num::::from_variable(*el).spread_into_bits::<_, 16>(cs)) + .flatten() + .collect(); + let message_hash_by_r_inv_lsb_bits: Vec<_> = message_hash_by_r_inv + .limbs + .iter() + .map(|el| Num::::from_variable(*el).spread_into_bits::<_, 16>(cs)) + .flatten() + .collect(); + + // 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 + // current implementation of point by scalar multiplications doesn't support multiplication by zero + // so we check that all s, r, hash are not zero (as FieldElements): + // if any of them is zero we reject the signature and in circuit itself replace all zero variables by ones + + let mut recovered_point = (x, y); + let mut generator_point = (gen_negated_x, gen_negated_y); + // now we do multiexponentiation + let mut q_acc = + SWProjectivePoint::>::zero(cs, base_field_params); + + // we should start from MSB, double the accumulator, then conditionally add + for (cycle, (x_bit, hash_bit)) in s_by_r_inv_normalized_lsb_bits + .into_iter() + .rev() + .zip(message_hash_by_r_inv_lsb_bits.into_iter().rev()) + .enumerate() + { + if cycle != 0 { + q_acc = q_acc.double(cs); + } + let q_plus_x = q_acc.add_mixed(cs, &mut recovered_point); + let mut q_0: SWProjectivePoint> = + Selectable::conditionally_select(cs, x_bit, &q_plus_x, &q_acc); + + let q_plux_gen = q_0.add_mixed(cs, &mut generator_point); + let q_1 = Selectable::conditionally_select(cs, hash_bit, &q_plux_gen, &q_0); + + q_acc = q_1; + } + + use boojum::pairing::GenericCurveAffine; + let ((mut q_x, mut q_y), is_infinity) = + q_acc.convert_to_affine_or_default(cs, Secp256Affine::one()); + exception_flags.push(is_infinity); + let any_exception = Boolean::multi_or(cs, &exception_flags[..]); + + q_x.normalize(cs); + q_y.normalize(cs); + + let zero_u8 = UInt8::zero(cs); + + let mut bytes_to_hash = [zero_u8; 64]; + let it = q_x.limbs[..16] + .iter() + .rev() + .chain(q_y.limbs[..16].iter().rev()); + + for (dst, src) in bytes_to_hash.array_chunks_mut::<2>().zip(it) { + let limb = unsafe { UInt16::from_variable_unchecked(*src) }; + *dst = limb.to_be_bytes(cs); + } + + let mut digest_bytes = keccak256(cs, &bytes_to_hash); + // digest is 32 bytes, but we need only 20 to recover address + digest_bytes[0..12].copy_from_slice(&[zero_u8; 12]); // empty out top bytes + digest_bytes.reverse(); + let written_value_unmasked = UInt256::from_le_bytes(cs, digest_bytes); + + let written_value = written_value_unmasked.mask_negated(cs, any_exception); + let all_ok = any_exception.negated(cs); + + (all_ok, written_value) +} + +pub fn ecrecover_function_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: EcrecoverCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + assert!(limit <= u32::MAX as usize); + + let EcrecoverCircuitInstanceWitness { + closed_form_input, + requests_queue_witness, + memory_reads_witness, + } = witness; + + let memory_reads_witness: VecDeque<_> = memory_reads_witness.into_iter().flatten().collect(); + + let precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::ECRECOVER_INNER_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + let aux_byte_for_precompile = UInt8::allocated_constant(cs, PRECOMPILE_AUX_BYTE); + + let scalar_params = Arc::new(secp256k1_scalar_field_params()); + let base_params = Arc::new(secp256k1_base_field_params()); + + use boojum::pairing::ff::PrimeField; + + let valid_x_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str(&VALID_X_CUBED_IN_EXTERNAL_FIELD.to_string()).unwrap(), + &base_params, + ); + let valid_t_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str(&(VALID_X_CUBED_IN_EXTERNAL_FIELD + SECP_B_COEF).to_string()).unwrap(), + &base_params, + ); + let valid_y_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str(&VALID_Y_IN_EXTERNAL_FIELD.to_string()).unwrap(), + &base_params, + ); + + let mut structured_input = + EcrecoverCircuitInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + let start_flag = structured_input.start_flag; + + let requests_queue_state_from_input = structured_input.observable_input.initial_log_queue_state; + + // it must be trivial + requests_queue_state_from_input.enforce_trivial_head(cs); + + let requests_queue_state_from_fsm = structured_input.hidden_fsm_input.log_queue_state; + + let requests_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &requests_queue_state_from_input, + &requests_queue_state_from_fsm, + ); + + let memory_queue_state_from_input = + structured_input.observable_input.initial_memory_queue_state; + + // it must be trivial + memory_queue_state_from_input.enforce_trivial_head(cs); + + let memory_queue_state_from_fsm = structured_input.hidden_fsm_input.memory_queue_state; + + let memory_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &memory_queue_state_from_input, + &memory_queue_state_from_fsm, + ); + + let mut requests_queue = StorageLogQueue::::from_state(cs, requests_queue_state); + let queue_witness = CircuitQueueWitness::from_inner_witness(requests_queue_witness); + requests_queue.witness = Arc::new(queue_witness); + + let mut memory_queue = MemoryQueue::::from_state(cs, memory_queue_state); + + let one_u32 = UInt32::allocated_constant(cs, 1u32); + let zero_u256 = UInt256::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + + use crate::storage_application::ConditionalWitnessAllocator; + let read_queries_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(memory_reads_witness)), + }; + + for _cycle in 0..limit { + let is_empty = requests_queue.is_empty(cs); + let should_process = is_empty.negated(cs); + let (request, _) = requests_queue.pop_front(cs, should_process); + + let mut precompile_call_params = + EcrecoverPrecompileCallParams::from_encoding(cs, request.key); + + let timestamp_to_use_for_read = request.timestamp; + let timestamp_to_use_for_write = timestamp_to_use_for_read.add_no_overflow(cs, one_u32); + + Num::conditionally_enforce_equal( + cs, + should_process, + &Num::from_variable(request.aux_byte.get_variable()), + &Num::from_variable(aux_byte_for_precompile.get_variable()), + ); + for (a, b) in request + .address + .inner + .iter() + .zip(precompile_address.inner.iter()) + { + Num::conditionally_enforce_equal( + cs, + should_process, + &Num::from_variable(a.get_variable()), + &Num::from_variable(b.get_variable()), + ); + } + + let mut read_values = [zero_u256; NUM_MEMORY_READS_PER_CYCLE]; + let mut bias_variable = should_process.get_variable(); + for dst in read_values.iter_mut() { + let read_query_value: UInt256 = read_queries_allocator + .conditionally_allocate_biased(cs, should_process, bias_variable); + bias_variable = read_query_value.inner[0].get_variable(); + + *dst = read_query_value; + + let read_query = MemoryQuery { + timestamp: timestamp_to_use_for_read, + memory_page: precompile_call_params.input_page, + index: precompile_call_params.input_offset, + rw_flag: boolean_false, + is_ptr: boolean_false, + value: read_query_value, + }; + + let _ = memory_queue.push(cs, read_query, should_process); + + precompile_call_params.input_offset = precompile_call_params + .input_offset + .add_no_overflow(cs, one_u32); + } + + 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]; + + let (success, written_value) = ecrecover_precompile_inner_routine( + cs, + &rec_id, + &r_as_u256, + &s_as_u256, + &message_hash_as_u256, + valid_x_in_external_field.clone(), + valid_y_in_external_field.clone(), + valid_t_in_external_field.clone(), + &base_params, + &scalar_params, + ); + + let success_as_u32 = unsafe { UInt32::from_variable_unchecked(success.get_variable()) }; + let mut success_as_u256 = zero_u256; + success_as_u256.inner[0] = success_as_u32; + + let success_query = MemoryQuery { + timestamp: timestamp_to_use_for_write, + memory_page: precompile_call_params.output_page, + index: precompile_call_params.output_offset, + rw_flag: boolean_true, + value: success_as_u256, + is_ptr: boolean_false, + }; + + precompile_call_params.output_offset = precompile_call_params + .output_offset + .add_no_overflow(cs, one_u32); + + let _ = memory_queue.push(cs, success_query, should_process); + + let value_query = MemoryQuery { + timestamp: timestamp_to_use_for_write, + memory_page: precompile_call_params.output_page, + index: precompile_call_params.output_offset, + rw_flag: boolean_true, + value: written_value, + is_ptr: boolean_false, + }; + + let _ = memory_queue.push(cs, value_query, should_process); + } + + requests_queue.enforce_consistency(cs); + + // form the final state + let done = requests_queue.is_empty(cs); + structured_input.completion_flag = done; + structured_input.observable_output = PrecompileFunctionOutputData::placeholder(cs); + + let final_memory_state = memory_queue.into_state(); + let final_requets_state = requests_queue.into_state(); + + structured_input.observable_output.final_memory_state = QueueState::conditionally_select( + cs, + structured_input.completion_flag, + &final_memory_state, + &structured_input.observable_output.final_memory_state, + ); + + structured_input.hidden_fsm_output.log_queue_state = final_requets_state; + structured_input.hidden_fsm_output.memory_queue_state = final_memory_state; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +#[cfg(test)] +mod test { + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::traits::allocatable::CSAllocatable; + use boojum::pairing::ff::{Field, PrimeField, SqrtField}; + use boojum::worker::Worker; + + use super::*; + + type F = GoldilocksField; + type P = GoldilocksField; + + use boojum::config::DevCSConfig; + + use boojum::pairing::ff::PrimeFieldRepr; + use boojum::pairing::{GenericCurveAffine, GenericCurveProjective}; + use rand::Rng; + use rand::SeedableRng; + use rand::XorShiftRng; + + pub fn deterministic_rng() -> XorShiftRng { + XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]) + } + + fn simulate_signature() -> (Secp256Fr, Secp256Fr, Secp256Affine, Secp256Fr) { + let mut rng = deterministic_rng(); + let sk: Secp256Fr = rng.gen(); + + simulate_signature_for_sk(sk) + } + + fn transmute_representation(repr: T) -> U { + assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + + unsafe { std::mem::transmute_copy::(&repr) } + } + + fn simulate_signature_for_sk( + sk: Secp256Fr, + ) -> (Secp256Fr, Secp256Fr, Secp256Affine, Secp256Fr) { + let mut rng = deterministic_rng(); + let pk = Secp256Affine::one().mul(sk.into_repr()).into_affine(); + let digest: Secp256Fr = rng.gen(); + let k: Secp256Fr = rng.gen(); + let r_point = Secp256Affine::one().mul(k.into_repr()).into_affine(); + + let r_x = r_point.into_xy_unchecked().0; + let r = transmute_representation::<_, ::Repr>(r_x.into_repr()); + let r = Secp256Fr::from_repr(r).unwrap(); + + let k_inv = k.inverse().unwrap(); + let mut s = r; + s.mul_assign(&sk); + s.add_assign(&digest); + s.mul_assign(&k_inv); + + { + let mut mul_by_generator = digest; + mul_by_generator.mul_assign(&r.inverse().unwrap()); + mul_by_generator.negate(); + + let mut mul_by_r = s; + mul_by_r.mul_assign(&r.inverse().unwrap()); + + let res_1 = Secp256Affine::one().mul(mul_by_generator.into_repr()); + let res_2 = r_point.mul(mul_by_r.into_repr()); + + let mut tmp = res_1; + tmp.add_assign(&res_2); + + let tmp = tmp.into_affine(); + + let x = tmp.into_xy_unchecked().0; + assert_eq!(x, pk.into_xy_unchecked().0); + } + + (r, s, pk, digest) + } + + fn repr_into_u256(repr: T) -> U256 { + let mut u256 = U256::zero(); + u256.0.copy_from_slice(&repr.as_ref()[..4]); + + u256 + } + + 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::gadgets::tables::byte_split::ByteSplitTable; + use boojum::gadgets::tables::*; + + #[test] + fn test_signature_for_address_verification() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + 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: 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 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 = ZeroCheckGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + false, + ); + 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_variables, + max_trace_len, + ); + let builder = new_builder::<_, F>(builder_impl); + + let builder = configure(builder); + let mut owned_cs = builder.build(()); + + // 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 sk = crate::ff::from_hex::( + "b5b1870957d373ef0eeffecc6e4812c0fd08f554b37b233526acc331bf1544f7", + ) + .unwrap(); + let eth_address = hex::decode("12890d2cce102216644c59dae5baed380d84830c").unwrap(); + let (r, s, _pk, digest) = simulate_signature_for_sk(sk); + + let scalar_params = secp256k1_scalar_field_params(); + let base_params = secp256k1_base_field_params(); + + let digest_u256 = repr_into_u256(digest.into_repr()); + let r_u256 = repr_into_u256(r.into_repr()); + let s_u256 = repr_into_u256(s.into_repr()); + + let rec_id = UInt8::allocate_checked(cs, 0); + 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, + ); + + let (no_error, digest) = ecrecover_precompile_inner_routine( + 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)); + } +} diff --git a/crates/zkevm_circuits/src/ecrecover/decomp_table.rs b/crates/zkevm_circuits/src/ecrecover/decomp_table.rs new file mode 100644 index 00000000..636b64ee --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/decomp_table.rs @@ -0,0 +1,76 @@ +use super::*; +use crate::ecrecover::{secp256k1::fr::Fr, Secp256Affine}; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; +use boojum::pairing::ff::PrimeField; +use derivative::*; + +const TABLE_NAME: &'static str = "WNAFDECOMP table"; + +#[derive(Derivative)] +#[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; + +// Lookups for wNAF decomposition of scalars. +pub fn create_wnaf_decomp_table() -> LookupTable { + 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); + } + LookupTable::new_from_keys_and_generation_function( + &all_keys, + TABLE_NAME.to_string(), + 1, + |keys| { + let mut a = keys[0].as_u64_reduced() as u8; + let mut v = Vec::with_capacity(4); + let mut carry_bit = false; + for _ in 0..2 { + if a % 2 == 1 { + let mut naf = (a & MASK_FOR_MOD_TABLE_SIZE) as i8; + if naf >= HALF_TABLE_SIZE { + naf -= TABLE_SIZE + }; + + let naf_abs = naf.abs() as u8; + if naf < 0 { + if carry_bit { + a += naf_abs; + } else { + (a, carry_bit) = a.overflowing_add(naf_abs); + } + } else { + a -= naf_abs; + } + v.push(naf); + } else { + v.push(0i8); + } + + a >>= 1; + } + + let concat = { + let mut res = 0u32; + res |= (v[0] as u8) as u32; + let shifted_v1 = ((v[1] as u8) as u32) << 8; + res |= shifted_v1; + // Add carry bit if we overflowed + if carry_bit { + res |= 1 << 16; + } + res + }; + smallvec::smallvec![ + F::from_u64_unchecked(concat as u64), + F::from_u64_unchecked(a as u64) + ] + }, + ) +} diff --git a/crates/zkevm_circuits/src/ecrecover/input.rs b/crates/zkevm_circuits/src/ecrecover/input.rs new file mode 100644 index 00000000..2581b39f --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/input.rs @@ -0,0 +1,51 @@ +use std::collections::VecDeque; + +use super::*; +use crate::base_structures::precompile_input_outputs::*; +use crate::base_structures::vm_state::*; +use boojum::cs::Variable; +use boojum::gadgets::queue::*; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; + +use boojum::gadgets::traits::auxiliary::PrettyComparison; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct EcrecoverCircuitFSMInputOutput { + pub log_queue_state: QueueState, + pub memory_queue_state: QueueState, +} + +impl CSPlaceholder for EcrecoverCircuitFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + log_queue_state: QueueState::::placeholder(cs), + memory_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type EcrecoverCircuitInputOutput = ClosedFormInput< + F, + EcrecoverCircuitFSMInputOutput, + PrecompileFunctionInputData, + PrecompileFunctionOutputData, +>; +pub type EcrecoverCircuitInputOutputWitness = ClosedFormInputWitness< + F, + EcrecoverCircuitFSMInputOutput, + PrecompileFunctionInputData, + PrecompileFunctionOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct EcrecoverCircuitInstanceWitness { + pub closed_form_input: EcrecoverCircuitInputOutputWitness, + pub requests_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, + pub memory_reads_witness: VecDeque<[U256; MEMORY_QUERIES_PER_CALL]>, +} diff --git a/crates/zkevm_circuits/src/ecrecover/mod.rs b/crates/zkevm_circuits/src/ecrecover/mod.rs new file mode 100644 index 00000000..7316c0b4 --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/mod.rs @@ -0,0 +1,73 @@ +use super::*; +use crate::base_structures::log_query::*; +use crate::base_structures::memory_query::*; +use crate::base_structures::precompile_input_outputs::PrecompileFunctionOutputData; +use crate::demux_log_queue::StorageLogQueue; +use crate::ethereum_types::U256; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::fsm_input_output::*; +use arrayvec::ArrayVec; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::crypto_bigint::{Zero, U1024}; +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::curves::sw_projective::SWProjectivePoint; +use boojum::gadgets::keccak256::keccak256; +use boojum::gadgets::non_native_field::implementations::*; +use boojum::gadgets::num::Num; +use boojum::gadgets::queue::CircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::allocatable::{CSAllocatableExt, CSPlaceholder}; +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::u160::UInt160; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use cs_derive::*; +use std::collections::VecDeque; +use std::sync::{Arc, RwLock}; +use zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE; + +pub mod input; +pub use self::input::*; + +pub mod secp256k1; + +pub const MEMORY_QUERIES_PER_CALL: usize = 4; + +pub mod naf_abs_div2_table; +use naf_abs_div2_table::*; +pub mod decomp_table; +use decomp_table::*; + +pub mod baseline; +pub mod new_optimized; + +// characteristics of the base field for secp curve +use self::secp256k1::fq::Fq as Secp256Fq; +// order of group of points for secp curve +use self::secp256k1::fr::Fr as Secp256Fr; +// some affine point +use self::secp256k1::PointAffine as Secp256Affine; + +type Secp256BaseNNFieldParams = NonNativeFieldOverU16Params; +type Secp256ScalarNNFieldParams = NonNativeFieldOverU16Params; + +type Secp256BaseNNField = NonNativeFieldOverU16; +type Secp256ScalarNNField = NonNativeFieldOverU16; + +fn secp256k1_base_field_params() -> Secp256BaseNNFieldParams { + NonNativeFieldOverU16Params::create() +} + +fn secp256k1_scalar_field_params() -> Secp256ScalarNNFieldParams { + NonNativeFieldOverU16Params::create() +} + +// re-exports for integration +pub use self::baseline::{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 new file mode 100644 index 00000000..e83dd9a8 --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/naf_abs_div2_table.rs @@ -0,0 +1,33 @@ +use super::*; +use crate::ecrecover::{secp256k1::fr::Fr, Secp256Affine}; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; +use boojum::pairing::ff::PrimeField; +use derivative::*; + +const TABLE_NAME: &'static str = "NAFABSDIV2 table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NafAbsDiv2Table; + +// Quick table lookups in wNAF circuit +pub fn create_naf_abs_div2_table() -> LookupTable { + 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); + } + LookupTable::new_from_keys_and_generation_function( + &all_keys, + TABLE_NAME.to_string(), + 1, + |keys| { + let a = keys[0].as_u64_reduced() as i8; + // 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)] + }, + ) +} diff --git a/crates/zkevm_circuits/src/ecrecover/new_optimized.rs b/crates/zkevm_circuits/src/ecrecover/new_optimized.rs new file mode 100644 index 00000000..6d8d5488 --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/new_optimized.rs @@ -0,0 +1,1551 @@ +use super::*; +use crate::base_structures::log_query::*; +use crate::base_structures::memory_query::*; +use crate::base_structures::precompile_input_outputs::PrecompileFunctionOutputData; +use crate::demux_log_queue::StorageLogQueue; +use crate::ecrecover::secp256k1::fixed_base_mul_table::FixedBaseMulTable; +use crate::ethereum_types::U256; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::fsm_input_output::*; +use arrayvec::ArrayVec; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::crypto_bigint::{Zero, U1024}; +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::Variable; +use boojum::field::SmallField; +use boojum::gadgets::blake2s::mixing_function::merge_byte_using_table; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::curves::sw_projective::SWProjectivePoint; +use boojum::gadgets::curves::zeroable_affine::ZeroableAffinePoint; +use boojum::gadgets::keccak256::keccak256; +use boojum::gadgets::non_native_field::implementations::*; +use boojum::gadgets::non_native_field::traits::NonNativeField; +use boojum::gadgets::num::Num; +use boojum::gadgets::queue::CircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::tables::And8Table; +use boojum::gadgets::tables::ByteSplitTable; +use boojum::gadgets::traits::allocatable::{CSAllocatableExt, CSPlaceholder}; +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::u160::UInt160; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u512::UInt512; +use boojum::gadgets::u8::UInt8; +use boojum::pairing::ff::PrimeField; +use boojum::pairing::GenericCurveAffine; +use boojum::pairing::{CurveAffine, GenericCurveProjective}; +use boojum::sha3::digest::typenum::private::IsGreaterPrivate; +use cs_derive::*; +use std::collections::VecDeque; +use std::str::FromStr; +use std::sync::{Arc, RwLock}; +use zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE; + +pub use self::input::*; +use super::input::*; + +pub const MEMORY_QUERIES_PER_CALL: usize = 4; +pub const ALLOW_ZERO_MESSAGE: bool = false; + +#[derive(Derivative, CSSelectable)] +#[derivative(Clone, Debug)] +pub struct EcrecoverPrecompileCallParams { + pub input_page: UInt32, + pub input_offset: UInt32, + pub output_page: UInt32, + pub output_offset: UInt32, +} + +impl EcrecoverPrecompileCallParams { + pub fn from_encoding>(_cs: &mut CS, encoding: UInt256) -> Self { + let input_offset = encoding.inner[0]; + let output_offset = encoding.inner[2]; + let input_page = encoding.inner[4]; + let output_page = encoding.inner[5]; + + let new = Self { + input_page, + input_offset, + output_page, + output_offset, + }; + + new + } +} + +const NUM_WORDS: usize = 17; +const SECP_B_COEF: u64 = 7; +const EXCEPTION_FLAGS_ARR_LEN: usize = 9; +const NUM_MEMORY_READS_PER_CYCLE: usize = 4; +const X_POWERS_ARR_LEN: usize = 256; +const VALID_Y_IN_EXTERNAL_FIELD: u64 = 4; +const VALID_X_CUBED_IN_EXTERNAL_FIELD: u64 = 9; + +// GLV consts + +// 2**128 +const TWO_POW_128: &'static str = "340282366920938463463374607431768211456"; +// BETA s.t. for any curve point Q = (x,y): +// lambda * Q = (beta*x mod p, y) +const BETA: &'static str = + "55594575648329892869085402983802832744385952214688224221778511981742606582254"; +// Secp256k1.p - 1 / 2 +// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc2f - 0x1 / 0x2 +const MODULUS_MINUS_ONE_DIV_TWO: &'static str = + "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"; +// Decomposition constants +// Derived through algorithm 3.74 http://tomlr.free.fr/Math%E9matiques/Math%20Complete/Cryptography/Guide%20to%20Elliptic%20Curve%20Cryptography%20-%20D.%20Hankerson,%20A.%20Menezes,%20S.%20Vanstone.pdf +// NOTE: B2 == A1 +const A1: &'static str = "0x3086d221a7d46bcde86c90e49284eb15"; +const B1: &'static str = "0xe4437ed6010e88286f547fa90abfe4c3"; +const A2: &'static str = "0x114ca50f7a8e2f3f657c1108d9d44cfd8"; + +// 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< + F: SmallField, + CS: ConstraintSystem, + P: boojum::pairing::ff::PrimeField, + const N: usize, +>( + cs: &mut CS, + elem: &UInt256, + params: &Arc>, +) -> (NonNativeFieldOverU16, Boolean) +where + [(); N + 1]:, +{ + let is_zero = elem.is_zero(cs); + let one_nn = NonNativeFieldOverU16::::allocated_constant(cs, P::one(), params); + // we still have to decompose it into u16 words + let zero_var = cs.allocate_constant(F::ZERO); + let mut limbs = [zero_var; N]; + assert!(N >= 16); + for (dst, src) in limbs.array_chunks_mut::<2>().zip(elem.inner.iter()) { + let [b0, b1, b2, b3] = src.to_le_bytes(cs); + let low = UInt16::from_le_bytes(cs, [b0, b1]); + let high = UInt16::from_le_bytes(cs, [b2, b3]); + + *dst = [low.get_variable(), high.get_variable()]; + } + + let mut max_value = U1024::from_word(1u64); + max_value = max_value.shl_vartime(256); + max_value = max_value.saturating_sub(&U1024::from_word(1u64)); + + let (overflows, rem) = max_value.div_rem(¶ms.modulus_u1024); + + let mut max_moduluses = overflows.as_words()[0] as u32; + if rem.is_zero().unwrap_u8() != 1 { + max_moduluses += 1; + } + + let element = NonNativeFieldOverU16 { + limbs: limbs, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses }, + form: RepresentationForm::Normalized, + params: params.clone(), + _marker: std::marker::PhantomData, + }; + + let selected = Selectable::conditionally_select(cs, is_zero, &one_nn, &element); + + (selected, is_zero) +} + +fn convert_uint256_to_field_element< + F: SmallField, + CS: ConstraintSystem, + P: boojum::pairing::ff::PrimeField, + const N: usize, +>( + cs: &mut CS, + elem: &UInt256, + params: &Arc>, +) -> NonNativeFieldOverU16 { + // we still have to decompose it into u16 words + let zero_var = cs.allocate_constant(F::ZERO); + let mut limbs = [zero_var; N]; + assert!(N >= 16); + for (dst, src) in limbs.array_chunks_mut::<2>().zip(elem.inner.iter()) { + let [b0, b1, b2, b3] = src.to_le_bytes(cs); + let low = UInt16::from_le_bytes(cs, [b0, b1]); + let high = UInt16::from_le_bytes(cs, [b2, b3]); + + *dst = [low.get_variable(), high.get_variable()]; + } + + let mut max_value = U1024::from_word(1u64); + max_value = max_value.shl_vartime(256); + max_value = max_value.saturating_sub(&U1024::from_word(1u64)); + + let (overflows, rem) = max_value.div_rem(¶ms.modulus_u1024); + let mut max_moduluses = overflows.as_words()[0] as u32; + if rem.is_zero().unwrap_u8() != 1 { + max_moduluses += 1; + } + + let element = NonNativeFieldOverU16 { + limbs: limbs, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses }, + form: RepresentationForm::Normalized, + params: params.clone(), + _marker: std::marker::PhantomData, + }; + + element +} + +// NOTE: caller must ensure that the field element is normalized, otherwise this will fail. +fn convert_field_element_to_uint256< + F: SmallField, + CS: ConstraintSystem, + P: boojum::pairing::ff::PrimeField, + const N: usize, +>( + cs: &mut CS, + mut elem: NonNativeFieldOverU16, +) -> UInt256 { + assert_eq!(elem.form, RepresentationForm::Normalized); + assert_eq!(elem.tracker.max_moduluses, 1); + + let mut limbs = [UInt32::::zero(cs); 8]; + let two_pow_16 = Num::allocated_constant(cs, F::from_u64_unchecked(2u32.pow(16) as u64)); + for (dst, src) in limbs.iter_mut().zip(elem.limbs.array_chunks_mut::<2>()) { + let low = Num::from_variable(src[0]); + let high = Num::from_variable(src[1]); + *dst = unsafe { + UInt32::from_variable_unchecked( + Num::fma(cs, &high, &two_pow_16, &F::ONE, &low, &F::ONE).get_variable(), + ) + }; + } + + UInt256 { inner: limbs } +} + +fn to_wnaf>( + cs: &mut CS, + e: Secp256ScalarNNField, + neg: Boolean, + decomp_id: u32, + byte_split_id: u32, +) -> Vec> { + let mut naf = vec![]; + let mut bytes = e + .limbs + .iter() + .flat_map(|el| unsafe { UInt16::from_variable_unchecked(*el).to_le_bytes(cs) }) + .collect::>>(); + + // Loop for max amount of bits in e to ensure homogenous circuit + for i in 0..33 { + // Since each lookup consumes 2 bits of information, and we need at least 3 bits for + // figuring out the NAF number, we can do two lookups before needing to propagated the + // changes up the bytestring. + let mut naf_overflow = None; + for j in 0..2 { + let res = cs.perform_lookup::<1, 2>(decomp_id, &[bytes[0].get_variable()]); + let wnaf_and_carry_bits = unsafe { UInt32::from_variable_unchecked(res[0]) }; + let wnaf_bytes = wnaf_and_carry_bits.to_le_bytes(cs); + bytes[0] = unsafe { UInt8::from_variable_unchecked(res[1]) }; + wnaf_bytes[..2].iter().for_each(|byte| { + let byte_neg = byte.negate(cs); + let byte = Selectable::conditionally_select(cs, neg, byte, &byte_neg); + naf.push(byte); + }); + // Save carry bit. + // It only ever matters to save it on the first iteration as it is not possible + // to overflow for the second. + if j == 0 { + naf_overflow = Some(wnaf_bytes[2]); + } + } + + // Break up the first byte into a lower chunk and a (potential) carry bit. + let res = cs.perform_lookup::<1, 2>(byte_split_id, &[bytes[0].get_variable()]); + let mut low = res[0]; + let carry_bit = unsafe { UInt8::from_variable_unchecked(res[1]) }; + let mut of = Boolean::allocated_constant(cs, true); + + // Shift e and propagate carry. + // Because a GLV decomposed scalar is at most 129 bits, we only care to shift + // the lower 17 bytes of the number initially, and as we progress, more zero bytes + // will appear, lowering the amount of iterations needed to shift our number. + let num_iter = 16 - (i / 2); + bytes + .iter_mut() + .skip(1) + .take(num_iter) + .enumerate() + .for_each(|(i, b)| { + // Propagate carry + let (added_b, new_of) = b.overflowing_add(cs, &carry_bit); + *b = Selectable::conditionally_select(cs, of, &added_b, b); + of = new_of; + // If this is the first byte, we also add the NAF carry bit. + if i == 0 { + let naf_of; + (*b, naf_of) = b.overflowing_add(cs, &naf_overflow.unwrap()); + // If this overflows, we should remember to add a carry bit to the next element. + of = Selectable::conditionally_select(cs, naf_of, &naf_of, &of); + } + + // Glue bytes + let res = cs.perform_lookup::<1, 2>(byte_split_id, &[b.get_variable()]); + *b = unsafe { + UInt8::from_variable_unchecked(merge_byte_using_table::<_, _, 4>( + cs, low, res[0], + )) + }; + low = res[1]; + }); + + // Shift up by one to align. + bytes = bytes[1..].to_vec(); + } + + naf +} + +fn wnaf_scalar_mul>( + cs: &mut CS, + mut point: SWProjectivePoint>, + mut scalar: Secp256ScalarNNField, + base_field_params: &Arc, + scalar_field_params: &Arc, +) -> 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 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); + + // 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 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 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(); + + // 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(); + + 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 low_pow_2_128 = pow_2_128.to_low(); + let (_res, k1_neg) = k1_u256.overflowing_sub(cs, &low_pow_2_128); + let k1_negated = k1.negated(cs); + let k1 = as NonNativeField>::conditionally_select( + cs, + k1_neg, + &k1, + &k1_negated, + ); + let (_res, k2_neg) = k2_u256.overflowing_sub(cs, &low_pow_2_128); + let k2_negated = k2.negated(cs); + let k2 = as NonNativeField>::conditionally_select( + cs, + k2_neg, + &k2, + &k2_negated, + ); + + (k1_neg, k1, k2_neg, k2) + }; + + // WNAF + // The scalar multiplication window size. + const GLV_WINDOW_SIZE: usize = 2; + + // The GLV table length. + const L: usize = 1 << (GLV_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 + // we know that the point is not infinity. + let (mut double, _) = point + .double(cs) + .convert_to_affine_or_default(cs, Secp256Affine::one()); + t1.push(point.clone()); + for i in 1..L { + let next = t1[i - 1].add_mixed(cs, &mut double); + t1.push(next); + } + + let t1 = t1 + .iter_mut() + .map(|el| el.convert_to_affine_or_default(cs, Secp256Affine::one()).0) + .collect::>(); + + 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 decomp_id = cs + .get_table_id_for_marker::() + .expect("table should exist"); + let byte_split_id = cs + .get_table_id_for_marker::>() + .expect("table should exist"); + + let naf_abs_div2_table_id = cs + .get_table_id_for_marker::() + .expect("table must exist"); + let naf_add = + |cs: &mut CS, + table: &[(Secp256BaseNNField, Secp256BaseNNField)], + naf: UInt8, + acc: &mut SWProjectivePoint>| { + let is_zero = naf.is_zero(cs); + let index = unsafe { + UInt8::from_variable_unchecked( + cs.perform_lookup::<1, 2>(naf_abs_div2_table_id, &[naf.get_variable()])[0], + ) + }; + let coords = &table[index.witness_hook(cs)().unwrap() as usize]; + let mut p_1 = + SWProjectivePoint::>::from_xy_unchecked( + cs, + coords.0.clone(), + coords.1.clone(), + ); + let (_, naf_is_positive) = naf.overflowing_sub(cs, &overflow_checker); + let p_1_neg = p_1.negated(cs); + p_1 = Selectable::conditionally_select(cs, naf_is_positive, &p_1, &p_1_neg); + let acc_added = acc.add_mixed(cs, &mut (p_1.x, p_1.y)); + *acc = Selectable::conditionally_select(cs, is_zero, &acc, &acc_added); + }; + + let naf1 = to_wnaf(cs, k1, k1_neg, decomp_id, byte_split_id); + let naf2 = to_wnaf(cs, k2, k2_neg, decomp_id, byte_split_id); + let mut acc = + SWProjectivePoint::>::zero(cs, &base_field_params); + for i in (0..129).rev() { + naf_add(cs, &t1, naf1[i], &mut acc); + naf_add(cs, &t2, naf2[i], &mut acc); + if i != 0 { + acc = acc.double(cs); + } + } + + acc +} + +fn fixed_base_mul, F: SmallField>( + cs: &mut CS, + mut message_hash_by_r_inv: Secp256ScalarNNField, + base_field_params: &Arc, +) -> SWProjectivePoint> { + message_hash_by_r_inv.enforce_reduced(cs); + let is_zero = message_hash_by_r_inv.is_zero(cs); + let bytes = message_hash_by_r_inv + .limbs + .iter() + .take(16) + .flat_map(|el| unsafe { UInt16::from_variable_unchecked(*el).to_le_bytes(cs) }) + .collect::>>(); + + let zero_point = + SWProjectivePoint::>::zero(cs, base_field_params); + let mut acc = + SWProjectivePoint::>::zero(cs, base_field_params); + let mut full_table_ids = vec![]; + seq_macro::seq!(C in 0..32 { + let ids = vec![ + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + cs.get_table_id_for_marker::>() + .expect("table must exist"), + ]; + full_table_ids.push(ids); + }); + + full_table_ids + .into_iter() + .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| { + let [x_v, y_v] = cs.perform_lookup::<1, 2>(*id, &[byte.get_variable()]); + let x_v = unsafe { UInt32::from_variable_unchecked(x_v) }; + let y_v = unsafe { UInt32::from_variable_unchecked(y_v) }; + let x_v = x_v.to_le_bytes(cs); + let y_v = y_v.to_le_bytes(cs); + let x_1 = UInt16::from_le_bytes(cs, x_v[..2].try_into().unwrap()); + let x_2 = UInt16::from_le_bytes(cs, x_v[2..].try_into().unwrap()); + let y_1 = UInt16::from_le_bytes(cs, y_v[..2].try_into().unwrap()); + let y_2 = UInt16::from_le_bytes(cs, y_v[2..].try_into().unwrap()); + [ + (x_1.get_variable(), y_1.get_variable()), + (x_2.get_variable(), y_2.get_variable()), + ] + }) + .collect::>() + .into_iter() + .unzip(); + let zero_var = cs.allocate_constant(F::ZERO); + let mut x_arr = [zero_var; 17]; + x_arr[..16].copy_from_slice(&x[..16]); + let mut y_arr = [zero_var; 17]; + y_arr[..16].copy_from_slice(&y[..16]); + let x = NonNativeFieldOverU16 { + limbs: x_arr, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses: 1 }, + form: RepresentationForm::Normalized, + params: base_field_params.clone(), + _marker: std::marker::PhantomData, + }; + let y = NonNativeFieldOverU16 { + limbs: y_arr, + non_zero_limbs: 16, + tracker: OverflowTracker { max_moduluses: 1 }, + form: RepresentationForm::Normalized, + params: base_field_params.clone(), + _marker: std::marker::PhantomData, + }; + acc = acc.add_mixed(cs, &mut (x, y)); + }); + acc = Selectable::conditionally_select(cs, is_zero, &zero_point, &acc); + acc +} + +fn ecrecover_precompile_inner_routine< + F: SmallField, + CS: ConstraintSystem, + const MESSAGE_HASH_CAN_BE_ZERO: bool, +>( + cs: &mut CS, + recid: &UInt8, + r: &UInt256, + s: &UInt256, + message_hash: &UInt256, + valid_x_in_external_field: Secp256BaseNNField, + valid_y_in_external_field: Secp256BaseNNField, + valid_t_in_external_field: Secp256BaseNNField, + base_field_params: &Arc, + scalar_field_params: &Arc, +) -> (Boolean, UInt256) { + use boojum::pairing::ff::Field; + let curve_b = Secp256Affine::b_coeff(); + + let mut minus_one = Secp256Fq::one(); + minus_one.negate(); + + let mut curve_b_nn = + Secp256BaseNNField::::allocated_constant(cs, curve_b, &base_field_params); + let mut minus_one_nn = + Secp256BaseNNField::::allocated_constant(cs, minus_one, &base_field_params); + + let secp_n_u256 = U256([ + scalar_field_params.modulus_u1024.as_ref().as_words()[0], + scalar_field_params.modulus_u1024.as_ref().as_words()[1], + scalar_field_params.modulus_u1024.as_ref().as_words()[2], + scalar_field_params.modulus_u1024.as_ref().as_words()[3], + ]); + let secp_n_u256 = UInt256::allocated_constant(cs, secp_n_u256); + + let secp_p_u256 = U256([ + base_field_params.modulus_u1024.as_ref().as_words()[0], + base_field_params.modulus_u1024.as_ref().as_words()[1], + base_field_params.modulus_u1024.as_ref().as_words()[2], + base_field_params.modulus_u1024.as_ref().as_words()[3], + ]); + let secp_p_u256 = UInt256::allocated_constant(cs, secp_p_u256); + + let mut exception_flags = ArrayVec::<_, EXCEPTION_FLAGS_ARR_LEN>::new(); + + // recid = (x_overflow ? 2 : 0) | (secp256k1_fe_is_odd(&r.y) ? 1 : 0) + // The point X = (x, y) we are going to recover is not known at the start, but it is strongly related to r. + // This is because x = r + kn for some integer k, where x is an element of the field F_q . In other words, x < q. + // (here n is the order of group of points on elleptic curve) + // For secp256k1 curve values of q and n are relatively close, that is, + // the probability of a random element of Fq being greater than n is about 1/{2^128}. + // This in turn means that the overwhelming majority of r determine a unique x, however some of them determine + // two: x = r and x = r + n. If x_overflow flag is set than x = r + n + + let [y_is_odd, x_overflow, ..] = + Num::::from_variable(recid.get_variable()).spread_into_bits::<_, 8>(cs); + + 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]); + exception_flags.push(error); + + // we handle x separately as it is the only element of base field of a curve (not a scalar field element!) + // check that x < q - order of base point on Secp256 curve + // if it is not actually the case - mask x to be zero + let (_res, is_in_range) = x_as_u256.overflowing_sub(cs, &secp_p_u256); + x_as_u256 = x_as_u256.mask(cs, is_in_range); + let x_is_not_in_range = is_in_range.negated(cs); + exception_flags.push(x_is_not_in_range); + + let mut x_fe = convert_uint256_to_field_element(cs, &x_as_u256, &base_field_params); + + let (mut r_fe, r_is_zero) = + convert_uint256_to_field_element_masked(cs, &r, &scalar_field_params); + exception_flags.push(r_is_zero); + let (mut s_fe, s_is_zero) = + convert_uint256_to_field_element_masked(cs, &s, &scalar_field_params); + exception_flags.push(s_is_zero); + + let (mut message_hash_fe, message_hash_is_zero) = if MESSAGE_HASH_CAN_BE_ZERO { + ( + convert_uint256_to_field_element(cs, &message_hash, scalar_field_params), + Boolean::allocated_constant(cs, false), + ) + } else { + convert_uint256_to_field_element_masked(cs, &message_hash, scalar_field_params) + }; + exception_flags.push(message_hash_is_zero); + + // curve equation is y^2 = x^3 + b + // we compute t = r^3 + b and check if t is a quadratic residue or not. + // we do this by computing Legendre symbol (t, p) = t^[(p-1)/2] (mod p) + // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + // n = (p-1)/2 = 2^255 - 2^31 - 2^8 - 2^7 - 2^6 - 2^5 - 2^3 - 1 + // we have to compute t^b = t^{2^255} / ( t^{2^31} * t^{2^8} * t^{2^7} * t^{2^6} * t^{2^5} * t^{2^3} * t) + // if t is not a quadratic residue we return error and replace x by another value that will make + // t = x^3 + b a quadratic residue + + let mut t = x_fe.square(cs); + t = t.mul(cs, &mut x_fe); + t = t.add(cs, &mut curve_b_nn); + + let t_is_zero = t.is_zero(cs); + exception_flags.push(t_is_zero); + + // if t is zero then just mask + let t = Selectable::conditionally_select(cs, t_is_zero, &valid_t_in_external_field, &t); + + // array of powers of t of the form t^{2^i} starting from i = 0 to 255 + let mut t_powers = Vec::with_capacity(X_POWERS_ARR_LEN); + t_powers.push(t); + + for _ in 1..X_POWERS_ARR_LEN { + let prev = t_powers.last_mut().unwrap(); + let next = prev.square(cs); + t_powers.push(next); + } + + let mut acc = t_powers[0].clone(); + for idx in [3, 5, 6, 7, 8, 31].into_iter() { + let other = &mut t_powers[idx]; + acc = acc.mul(cs, other); + } + let mut legendre_symbol = t_powers[255].div_unchecked(cs, &mut acc); + + // we can also reuse the same values to compute square root in case of p = 3 mod 4 + // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + // n = (p+1)/4 = 2^254 - 2^30 - 2^7 - 2^6 - 2^5 - 2^4 - 2^2 + + let mut acc_2 = t_powers[2].clone(); + for idx in [4, 5, 6, 7, 30].into_iter() { + let other = &mut t_powers[idx]; + acc_2 = acc_2.mul(cs, other); + } + + let mut may_be_recovered_y = t_powers[254].div_unchecked(cs, &mut acc_2); + may_be_recovered_y.normalize(cs); + let may_be_recovered_y_negated = may_be_recovered_y.negated(cs); + + let [lowest_bit, ..] = + Num::::from_variable(may_be_recovered_y.limbs[0]).spread_into_bits::<_, 16>(cs); + + // if lowest bit != parity bit, then we need conditionally select + let should_swap = lowest_bit.xor(cs, y_is_odd); + let may_be_recovered_y = Selectable::conditionally_select( + cs, + should_swap, + &may_be_recovered_y_negated, + &may_be_recovered_y, + ); + + let t_is_nonresidue = + Secp256BaseNNField::::equals(cs, &mut legendre_symbol, &mut minus_one_nn); + exception_flags.push(t_is_nonresidue); + // unfortunately, if t is found to be a quadratic nonresidue, we can't simply let x to be zero, + // because then t_new = 7 is again a quadratic nonresidue. So, in this case we let x to be 9, then + // t = 16 is a quadratic residue + let x = + Selectable::conditionally_select(cs, t_is_nonresidue, &valid_x_in_external_field, &x_fe); + let y = Selectable::conditionally_select( + cs, + t_is_nonresidue, + &valid_y_in_external_field, + &may_be_recovered_y, + ); + + // we recovered (x, y) using curve equation, so it's on curve (or was masked) + let mut r_fe_inversed = r_fe.inverse_unchecked(cs); + let mut s_by_r_inv = s_fe.mul(cs, &mut r_fe_inversed); + 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); + + // 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 + + let recovered_point = + SWProjectivePoint::>::from_xy_unchecked(cs, x, y); + // now we do multiplication + 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, &base_field_params); + + let (mut q_acc, is_infinity) = + hash_times_g.convert_to_affine_or_default(cs, Secp256Affine::one()); + let q_acc_added = s_times_x.add_mixed(cs, &mut q_acc); + let mut q_acc = Selectable::conditionally_select(cs, is_infinity, &s_times_x, &q_acc_added); + + let ((q_x, q_y), is_infinity) = q_acc.convert_to_affine_or_default(cs, Secp256Affine::one()); + exception_flags.push(is_infinity); + let any_exception = Boolean::multi_or(cs, &exception_flags[..]); + + let zero_u8 = UInt8::zero(cs); + + let mut bytes_to_hash = [zero_u8; 64]; + let it = q_x.limbs[..16] + .iter() + .rev() + .chain(q_y.limbs[..16].iter().rev()); + + for (dst, src) in bytes_to_hash.array_chunks_mut::<2>().zip(it) { + let limb = unsafe { UInt16::from_variable_unchecked(*src) }; + *dst = limb.to_be_bytes(cs); + } + + let mut digest_bytes = keccak256(cs, &bytes_to_hash); + // digest is 32 bytes, but we need only 20 to recover address + digest_bytes[0..12].copy_from_slice(&[zero_u8; 12]); // empty out top bytes + digest_bytes.reverse(); + let written_value_unmasked = UInt256::from_le_bytes(cs, digest_bytes); + + let written_value = written_value_unmasked.mask_negated(cs, any_exception); + let all_ok = any_exception.negated(cs); + + (all_ok, written_value) +} + +pub fn ecrecover_function_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: EcrecoverCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + assert!(limit <= u32::MAX as usize); + + let EcrecoverCircuitInstanceWitness { + closed_form_input, + requests_queue_witness, + memory_reads_witness, + } = witness; + + let memory_reads_witness: VecDeque<_> = memory_reads_witness.into_iter().flatten().collect(); + + let precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::ECRECOVER_INNER_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + let aux_byte_for_precompile = UInt8::allocated_constant(cs, PRECOMPILE_AUX_BYTE); + + let scalar_params = Arc::new(secp256k1_scalar_field_params()); + let base_params = Arc::new(secp256k1_base_field_params()); + + use boojum::pairing::ff::PrimeField; + + let valid_x_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str(&VALID_X_CUBED_IN_EXTERNAL_FIELD.to_string()).unwrap(), + &base_params, + ); + let valid_t_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str(&(VALID_X_CUBED_IN_EXTERNAL_FIELD + SECP_B_COEF).to_string()).unwrap(), + &base_params, + ); + let valid_y_in_external_field = Secp256BaseNNField::allocated_constant( + cs, + Secp256Fq::from_str(&VALID_Y_IN_EXTERNAL_FIELD.to_string()).unwrap(), + &base_params, + ); + + let mut structured_input = + EcrecoverCircuitInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + let start_flag = structured_input.start_flag; + + let requests_queue_state_from_input = structured_input.observable_input.initial_log_queue_state; + + // it must be trivial + requests_queue_state_from_input.enforce_trivial_head(cs); + + let requests_queue_state_from_fsm = structured_input.hidden_fsm_input.log_queue_state; + + let requests_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &requests_queue_state_from_input, + &requests_queue_state_from_fsm, + ); + + let memory_queue_state_from_input = + structured_input.observable_input.initial_memory_queue_state; + + // it must be trivial + memory_queue_state_from_input.enforce_trivial_head(cs); + + let memory_queue_state_from_fsm = structured_input.hidden_fsm_input.memory_queue_state; + + let memory_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &memory_queue_state_from_input, + &memory_queue_state_from_fsm, + ); + + let mut requests_queue = StorageLogQueue::::from_state(cs, requests_queue_state); + let queue_witness = CircuitQueueWitness::from_inner_witness(requests_queue_witness); + requests_queue.witness = Arc::new(queue_witness); + + let mut memory_queue = MemoryQueue::::from_state(cs, memory_queue_state); + + let one_u32 = UInt32::allocated_constant(cs, 1u32); + let zero_u256 = UInt256::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + + use crate::storage_application::ConditionalWitnessAllocator; + let read_queries_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(memory_reads_witness)), + }; + + for _cycle in 0..limit { + let is_empty = requests_queue.is_empty(cs); + let should_process = is_empty.negated(cs); + let (request, _) = requests_queue.pop_front(cs, should_process); + + let mut precompile_call_params = + EcrecoverPrecompileCallParams::from_encoding(cs, request.key); + + let timestamp_to_use_for_read = request.timestamp; + let timestamp_to_use_for_write = timestamp_to_use_for_read.add_no_overflow(cs, one_u32); + + Num::conditionally_enforce_equal( + cs, + should_process, + &Num::from_variable(request.aux_byte.get_variable()), + &Num::from_variable(aux_byte_for_precompile.get_variable()), + ); + for (a, b) in request + .address + .inner + .iter() + .zip(precompile_address.inner.iter()) + { + Num::conditionally_enforce_equal( + cs, + should_process, + &Num::from_variable(a.get_variable()), + &Num::from_variable(b.get_variable()), + ); + } + + let mut read_values = [zero_u256; NUM_MEMORY_READS_PER_CYCLE]; + let mut bias_variable = should_process.get_variable(); + for dst in read_values.iter_mut() { + let read_query_value: UInt256 = read_queries_allocator + .conditionally_allocate_biased(cs, should_process, bias_variable); + bias_variable = read_query_value.inner[0].get_variable(); + + *dst = read_query_value; + + let read_query = MemoryQuery { + timestamp: timestamp_to_use_for_read, + memory_page: precompile_call_params.input_page, + index: precompile_call_params.input_offset, + rw_flag: boolean_false, + is_ptr: boolean_false, + value: read_query_value, + }; + + let _ = memory_queue.push(cs, read_query, should_process); + + precompile_call_params.input_offset = precompile_call_params + .input_offset + .add_no_overflow(cs, one_u32); + } + + 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]; + + let (success, written_value) = ecrecover_precompile_inner_routine::<_, _, ALLOW_ZERO_MESSAGE>( + cs, + &rec_id, + &r_as_u256, + &s_as_u256, + &message_hash_as_u256, + valid_x_in_external_field.clone(), + valid_y_in_external_field.clone(), + valid_t_in_external_field.clone(), + &base_params, + &scalar_params, + ); + + let success_as_u32 = unsafe { UInt32::from_variable_unchecked(success.get_variable()) }; + let mut success_as_u256 = zero_u256; + success_as_u256.inner[0] = success_as_u32; + + let success_query = MemoryQuery { + timestamp: timestamp_to_use_for_write, + memory_page: precompile_call_params.output_page, + index: precompile_call_params.output_offset, + rw_flag: boolean_true, + value: success_as_u256, + is_ptr: boolean_false, + }; + + precompile_call_params.output_offset = precompile_call_params + .output_offset + .add_no_overflow(cs, one_u32); + + let _ = memory_queue.push(cs, success_query, should_process); + + let value_query = MemoryQuery { + timestamp: timestamp_to_use_for_write, + memory_page: precompile_call_params.output_page, + index: precompile_call_params.output_offset, + rw_flag: boolean_true, + value: written_value, + is_ptr: boolean_false, + }; + + let _ = memory_queue.push(cs, value_query, should_process); + } + + requests_queue.enforce_consistency(cs); + + // form the final state + let done = requests_queue.is_empty(cs); + structured_input.completion_flag = done; + structured_input.observable_output = PrecompileFunctionOutputData::placeholder(cs); + + let final_memory_state = memory_queue.into_state(); + let final_requets_state = requests_queue.into_state(); + + structured_input.observable_output.final_memory_state = QueueState::conditionally_select( + cs, + structured_input.completion_flag, + &final_memory_state, + &structured_input.observable_output.final_memory_state, + ); + + structured_input.hidden_fsm_output.log_queue_state = final_requets_state; + structured_input.hidden_fsm_output.memory_queue_state = final_memory_state; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +#[cfg(test)] +mod test { + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::traits::allocatable::CSAllocatable; + use boojum::pairing::ff::{Field, PrimeField, SqrtField}; + use boojum::worker::Worker; + + use super::*; + + type F = GoldilocksField; + type P = GoldilocksField; + + use boojum::config::DevCSConfig; + + use boojum::pairing::ff::PrimeFieldRepr; + use boojum::pairing::{GenericCurveAffine, GenericCurveProjective}; + use rand::Rng; + use rand::SeedableRng; + use rand::XorShiftRng; + + pub fn deterministic_rng() -> XorShiftRng { + XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]) + } + + fn simulate_signature() -> (Secp256Fr, Secp256Fr, Secp256Affine, Secp256Fr) { + let mut rng = deterministic_rng(); + let sk: Secp256Fr = rng.gen(); + + simulate_signature_for_sk(sk) + } + + fn transmute_representation(repr: T) -> U { + assert_eq!(std::mem::size_of::(), std::mem::size_of::()); + + unsafe { std::mem::transmute_copy::(&repr) } + } + + fn simulate_signature_for_sk( + sk: Secp256Fr, + ) -> (Secp256Fr, Secp256Fr, Secp256Affine, Secp256Fr) { + let mut rng = deterministic_rng(); + let pk = Secp256Affine::one().mul(sk.into_repr()).into_affine(); + let digest: Secp256Fr = rng.gen(); + let k: Secp256Fr = rng.gen(); + let r_point = Secp256Affine::one().mul(k.into_repr()).into_affine(); + + let r_x = r_point.into_xy_unchecked().0; + let r = transmute_representation::<_, ::Repr>(r_x.into_repr()); + let r = Secp256Fr::from_repr(r).unwrap(); + + let k_inv = k.inverse().unwrap(); + let mut s = r; + s.mul_assign(&sk); + s.add_assign(&digest); + s.mul_assign(&k_inv); + + { + let mut mul_by_generator = digest; + mul_by_generator.mul_assign(&r.inverse().unwrap()); + mul_by_generator.negate(); + + let mut mul_by_r = s; + mul_by_r.mul_assign(&r.inverse().unwrap()); + + let res_1 = Secp256Affine::one().mul(mul_by_generator.into_repr()); + let res_2 = r_point.mul(mul_by_r.into_repr()); + + let mut tmp = res_1; + tmp.add_assign(&res_2); + + let tmp = tmp.into_affine(); + + let x = tmp.into_xy_unchecked().0; + assert_eq!(x, pk.into_xy_unchecked().0); + } + + (r, s, pk, digest) + } + + fn repr_into_u256(repr: T) -> U256 { + let mut u256 = U256::zero(); + u256.0.copy_from_slice(&repr.as_ref()[..4]); + + u256 + } + + use crate::ecrecover::secp256k1::fixed_base_mul_table::{ + create_fixed_base_mul_table, FixedBaseMulTable, + }; + use boojum::cs::cs_builder::*; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + use boojum::cs::gates::*; + use boojum::cs::implementations::reference_cs::CSReferenceImplementation; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::gadgets::tables::byte_split::ByteSplitTable; + use boojum::gadgets::tables::*; + + fn create_cs( + max_trace_len: usize, + ) -> CSReferenceImplementation< + F, + P, + 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, + }; + let max_variables = 1 << 26; + + 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: 8, + share_table_id: true, + }, + ); + let builder = U8x4FMAGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + 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 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 = 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 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_variables, + max_trace_len, + ); + let builder = new_builder::<_, F>(builder_impl); + + let builder = configure(builder); + let mut owned_cs = builder.build(()); + + // 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_naf_abs_div2_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::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_table::(); + owned_cs.add_lookup_table::, 3>(table); + let table = create_fixed_base_mul_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 table = create_byte_split_table::(); + owned_cs.add_lookup_table::, 3>(table); + + owned_cs + } + + #[test] + fn test_signature_for_address_verification() { + let mut owned_cs = create_cs(1 << 20); + let cs = &mut owned_cs; + + let sk = crate::ff::from_hex::( + "b5b1870957d373ef0eeffecc6e4812c0fd08f554b37b233526acc331bf1544f7", + ) + .unwrap(); + let eth_address = hex::decode("12890d2cce102216644c59dae5baed380d84830c").unwrap(); + let (r, s, _pk, digest) = simulate_signature_for_sk(sk); + + let scalar_params = secp256k1_scalar_field_params(); + let base_params = secp256k1_base_field_params(); + + let digest_u256 = repr_into_u256(digest.into_repr()); + let r_u256 = repr_into_u256(r.into_repr()); + let s_u256 = repr_into_u256(s.into_repr()); + + let rec_id = UInt8::allocate_checked(cs, 0); + 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..5 { + let (no_error, digest) = ecrecover_precompile_inner_routine::<_, _, false>( + 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_ecrecover_zero_elements() { + let mut owned_cs = create_cs(1 << 21); + let cs = &mut owned_cs; + + let sk = crate::ff::from_hex::( + "b5b1870957d373ef0eeffecc6e4812c0fd08f554b37b233526acc331bf1544f7", + ) + .unwrap(); + let (r, s, _pk, digest) = simulate_signature_for_sk(sk); + + let scalar_params = secp256k1_scalar_field_params(); + let base_params = secp256k1_base_field_params(); + + let zero_digest = Secp256Fr::zero(); + let zero_r = Secp256Fr::zero(); + let zero_s = Secp256Fr::zero(); + + let digest_u256 = repr_into_u256(digest.into_repr()); + let r_u256 = repr_into_u256(r.into_repr()); + let s_u256 = repr_into_u256(s.into_repr()); + + let zero_digest_u256 = repr_into_u256(zero_digest.into_repr()); + let zero_r_u256 = repr_into_u256(zero_r.into_repr()); + let zero_s_u256 = repr_into_u256(zero_s.into_repr()); + + let rec_id = UInt8::allocate_checked(cs, 0); + let r = UInt256::allocate(cs, r_u256); + let s = UInt256::allocate(cs, s_u256); + let digest = UInt256::allocate(cs, digest_u256); + + let zero_r = UInt256::allocate(cs, zero_r_u256); + let zero_s = UInt256::allocate(cs, zero_s_u256); + let zero_digest = UInt256::allocate(cs, zero_digest_u256); + + // Create an r that is unrecoverable. + let r_unrecoverable = + UInt256::allocate(cs, U256::from(0u64).overflowing_sub(U256::from(1u64)).0); + + 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, + ); + + // Construct a table of all combinations of correct and incorrect values + // for r, s, and digest. + let r_values = vec![r, zero_r, r_unrecoverable]; + let s_values = vec![s, zero_s]; + let digest_values = vec![digest, zero_digest]; + + // We ensure that there are no combinations where all correct items are chosen, so that we + // can consistently check for errors. + let mut first = true; + let mut all_combinations = vec![]; + for r in r_values.iter() { + for s in s_values.iter() { + for digest in digest_values.iter() { + if first { + first = false; + continue; + } + all_combinations.push((r.clone(), s.clone(), digest.clone())); + } + } + } + + for (r, s, digest) in all_combinations.into_iter() { + let (no_error, _digest) = ecrecover_precompile_inner_routine::<_, _, false>( + 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() == false); + } + } + + // As discussed on ethresearch forums, a caller may 'abuse' ecrecover in order to compute a + // secp256k1 ecmul in the EVM. This test compares the result of an ecrecover scalar mul with + // the output of a previously tested ecmul in the EVM. + // + // It works as follows: given a point x coordinate `r`, we set `s` to be `r * k` for some `k`. + // This then works out in the secp256k1 recover equation to create the equation + // `res = (r, y) * r * k * inv(r, P)` which is equal to `res = (r, y) * k`, effectively + // performing a scalar multiplication. + // + // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384 + #[test] + fn test_ecrecover_scalar_mul_trick() { + use rand::Rand; + let mut owned_cs = create_cs(1 << 20); + let cs = &mut owned_cs; + + // NOTE: This is essentially reducing a base field to a scalar field element. Due to the + // nature of the recovery equation turning into `(r, y) * r * k * inv(r, P)`, reducing r to + // a scalar value would yield the same result regardless. + let r = crate::ff::from_hex::( + "00000000000000009b37e91445e92b1423354825aa33d841d83cacfdd895d316ae88dabc31736996", + ) + .unwrap(); + let k = crate::ff::from_hex::( + "0000000000000000005aa98b08426f9dea29001fc925f3f35a10c9927082fe4d026cc485d1ebb430", + ) + .unwrap(); + let mut s = r.clone(); + s.mul_assign(&k); + let evm_tested_digest = hex::decode("eDc01060fdD6592f54A63EAE6C89436675C4d70D").unwrap(); + + let scalar_params = secp256k1_scalar_field_params(); + let base_params = secp256k1_base_field_params(); + + let r_u256 = repr_into_u256(r.into_repr()); + let s_u256 = repr_into_u256(s.into_repr()); + + let rec_id = UInt8::allocate_checked(cs, 0); + let r = UInt256::allocate(cs, r_u256); + let s = UInt256::allocate(cs, s_u256); + let digest = UInt256::allocate(cs, U256::zero()); + + 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..5 { + 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, + ); + + // Zero digest shouldn't give us an error + 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..], &evm_tested_digest[..]); + } + + 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)); + } +} 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 new file mode 100644 index 00000000..8aec309a --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/secp256k1/fixed_base_mul_table.rs @@ -0,0 +1,55 @@ +use super::*; +use crate::ecrecover::{secp256k1::fr::Fr, Secp256Affine}; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; +use boojum::pairing::ff::PrimeField; +use derivative::*; + +const TABLE_NAME: &'static str = "FIXEDBASEMUL table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +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); + } + 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]] + }, + ) +} diff --git a/crates/zkevm_circuits/src/ecrecover/secp256k1/fq.rs b/crates/zkevm_circuits/src/ecrecover/secp256k1/fq.rs new file mode 100644 index 00000000..ac08cfd5 --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/secp256k1/fq.rs @@ -0,0 +1,7 @@ +use boojum::pairing::ff::*; + +// base field, Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F +#[derive(PrimeField)] +#[PrimeFieldModulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663"] +#[PrimeFieldGenerator = "2"] +pub struct Fq(FqRepr); diff --git a/crates/zkevm_circuits/src/ecrecover/secp256k1/fr.rs b/crates/zkevm_circuits/src/ecrecover/secp256k1/fr.rs new file mode 100644 index 00000000..72409872 --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/secp256k1/fr.rs @@ -0,0 +1,7 @@ +use boojum::pairing::ff::*; + +// scalar field, R = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +#[derive(PrimeField)] +#[PrimeFieldModulus = "115792089237316195423570985008687907852837564279074904382605163141518161494337"] +#[PrimeFieldGenerator = "2"] +pub struct Fr(FrRepr); diff --git a/crates/zkevm_circuits/src/ecrecover/secp256k1/mod.rs b/crates/zkevm_circuits/src/ecrecover/secp256k1/mod.rs new file mode 100644 index 00000000..83e13fae --- /dev/null +++ b/crates/zkevm_circuits/src/ecrecover/secp256k1/mod.rs @@ -0,0 +1,762 @@ +use boojum::pairing::ff::BitIterator; +use boojum::pairing::ff::*; +use boojum::pairing::{ + EncodingBytes, GenericCompressedEncodable, GenericCurveAffine, GenericCurveProjective, + GenericUncompressedEncodable, GroupDecodingError, +}; + +pub mod fixed_base_mul_table; +pub mod fq; +pub mod fr; + +use fixed_base_mul_table::*; +use fq::*; +use fr::*; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct PointAffine { + pub(crate) x: Fq, + pub(crate) y: Fq, + pub(crate) infinity: bool, +} + +static NAME_STR: &'static str = "Secp256k1"; + +impl ::std::fmt::Display for PointAffine { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + if self.infinity { + write!(f, "{}(Infinity)", NAME_STR) + } else { + write!(f, "{}(x={}, y={})", NAME_STR, self.x, self.y) + } + } +} + +#[derive(Copy, Clone, Debug, Eq)] +pub struct PointProjective { + pub(crate) x: Fq, + pub(crate) y: Fq, + pub(crate) z: Fq, +} + +impl ::std::fmt::Display for PointProjective { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}", self.into_affine()) + } +} + +impl PartialEq for PointProjective { + fn eq(&self, other: &PointProjective) -> bool { + if self.is_zero() { + return other.is_zero(); + } + + if other.is_zero() { + return false; + } + + // The points (X, Y, Z) and (X', Y', Z') + // are equal when (X * Z^2) = (X' * Z'^2) + // and (Y * Z^3) = (Y' * Z'^3). + + let mut z1 = self.z; + z1.square(); + let mut z2 = other.z; + z2.square(); + + let mut tmp1 = self.x; + tmp1.mul_assign(&z2); + + let mut tmp2 = other.x; + tmp2.mul_assign(&z1); + + if tmp1 != tmp2 { + return false; + } + + z1.mul_assign(&self.z); + z2.mul_assign(&other.z); + z2.mul_assign(&self.y); + z1.mul_assign(&other.y); + + if z1 != z2 { + return false; + } + + true + } +} + +impl PointAffine { + fn mul_bits>(&self, bits: BitIterator) -> PointProjective { + let mut res = PointProjective::zero(); + for i in bits { + res.double(); + if i { + res.add_assign_mixed(self) + } + } + res + } + + /// Attempts to construct an affine point given an x-coordinate. The + /// point is not guaranteed to be in the prime order subgroup. + /// + /// If and only if `greatest` is set will the lexicographically + /// largest y-coordinate be selected. + fn get_point_from_x(x: Fq, greatest: bool) -> Option { + // Compute x^3 + b + let mut x3b = x; + x3b.square(); + x3b.mul_assign(&x); + x3b.add_assign(&PointAffine::get_coeff_b()); + + x3b.sqrt().map(|y| { + let mut negy = y; + negy.negate(); + + PointAffine { + x: x, + y: if (y < negy) ^ greatest { y } else { negy }, + infinity: false, + } + }) + } + + fn is_on_curve(&self) -> bool { + if self.is_zero() { + true + } else { + // Check that the point is on the curve + let mut y2 = self.y; + y2.square(); + + let mut x3b = self.x; + x3b.square(); + x3b.mul_assign(&self.x); + x3b.add_assign(&Self::get_coeff_b()); + + y2 == x3b + } + } +} + +impl GenericCurveAffine for PointAffine { + type Scalar = Fr; + type Base = Fq; + type Projective = PointProjective; + + fn zero() -> Self { + PointAffine { + x: Fq::zero(), + y: Fq::one(), + infinity: true, + } + } + + fn one() -> Self { + Self::get_generator() + } + + fn is_zero(&self) -> bool { + self.infinity + } + + fn mul::Repr>>(&self, by: S) -> PointProjective { + let bits = BitIterator::new(by.into()); + self.mul_bits(bits) + } + + fn negate(&mut self) { + if !self.is_zero() { + self.y.negate(); + } + } + + fn into_projective(&self) -> PointProjective { + (*self).into() + } + + #[inline(always)] + fn as_xy(&self) -> (&Self::Base, &Self::Base) { + (&self.x, &self.y) + } + + #[inline(always)] + fn into_xy_unchecked(self) -> (Self::Base, Self::Base) { + (self.x, self.y) + } + + #[inline(always)] + fn from_xy_unchecked(x: Self::Base, y: Self::Base) -> Self { + let infinity = x.is_zero() && y.is_zero(); + Self { + x: x, + y: y, + infinity, + } + } + + fn from_xy_checked(x: Self::Base, y: Self::Base) -> Result { + let infinity = x.is_zero() && y.is_zero(); + let affine = Self { + x: x, + y: y, + infinity, + }; + + if !affine.is_on_curve() { + Err(GroupDecodingError::NotOnCurve) + } else { + Ok(affine) + } + } + + fn a_coeff() -> Self::Base { + Self::Base::zero() + } + + fn b_coeff() -> Self::Base { + Self::get_coeff_b() + } +} + +impl GenericCurveProjective for PointProjective { + type Scalar = Fr; + type Base = Fq; + type Affine = PointAffine; + + // The point at infinity is always represented by + // Z = 0. + fn zero() -> Self { + PointProjective { + x: Fq::zero(), + y: Fq::one(), + z: Fq::zero(), + } + } + + fn one() -> Self { + PointAffine::one().into() + } + + // The point at infinity is always represented by + // Z = 0. + fn is_zero(&self) -> bool { + self.z.is_zero() + } + + fn is_normalized(&self) -> bool { + self.is_zero() || self.z == Fq::one() + } + + fn batch_normalization(v: &mut [Self]) { + // Montgomery’s Trick and Fast Implementation of Masked AES + // Genelle, Prouff and Quisquater + // Section 3.2 + + // First pass: compute [a, ab, abc, ...] + let mut prod = Vec::with_capacity(v.len()); + let mut tmp = Fq::one(); + for g in v + .iter_mut() + // Ignore normalized elements + .filter(|g| !g.is_normalized()) + { + tmp.mul_assign(&g.z); + prod.push(tmp); + } + + // Invert `tmp`. + tmp = tmp.inverse().unwrap(); // Guaranteed to be nonzero. + + // Second pass: iterate backwards to compute inverses + for (g, s) in v + .iter_mut() + // Backwards + .rev() + // Ignore normalized elements + .filter(|g| !g.is_normalized()) + // Backwards, skip last element, fill in one for last term. + .zip(prod.into_iter().rev().skip(1).chain(Some(Fq::one()))) + { + // tmp := tmp * g.z; g.z := tmp * s = 1/z + let mut newtmp = tmp; + newtmp.mul_assign(&g.z); + g.z = tmp; + g.z.mul_assign(&s); + tmp = newtmp; + } + + // Perform affine transformations + for g in v.iter_mut().filter(|g| !g.is_normalized()) { + let mut z = g.z; // 1/z + z.square(); // 1/z^2 + g.x.mul_assign(&z); // x/z^2 + z.mul_assign(&g.z); // 1/z^3 + g.y.mul_assign(&z); // y/z^3 + g.z = Fq::one(); // z = 1 + } + } + + fn double(&mut self) { + if self.is_zero() { + return; + } + + // Other than the point at infinity, no points on E or E' + // can double to equal the point at infinity, as y=0 is + // never true for points on the curve. + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + + // A = X1^2 + let mut a = self.x; + a.square(); + + // B = Y1^2 + let mut b = self.y; + b.square(); + + // C = B^2 + let mut c = b; + c.square(); + + // D = 2*((X1+B)2-A-C) + let mut d = self.x; + d.add_assign(&b); + d.square(); + d.sub_assign(&a); + d.sub_assign(&c); + d.double(); + + // E = 3*A + let mut e = a; + e.double(); + e.add_assign(&a); + + // F = E^2 + let mut f = e; + f.square(); + + // Z3 = 2*Y1*Z1 + self.z.mul_assign(&self.y); + self.z.double(); + + // X3 = F-2*D + self.x = f; + self.x.sub_assign(&d); + self.x.sub_assign(&d); + + // Y3 = E*(D-X3)-8*C + self.y = d; + self.y.sub_assign(&self.x); + self.y.mul_assign(&e); + c.double(); + c.double(); + c.double(); + self.y.sub_assign(&c); + } + + fn add_assign(&mut self, other: &Self) { + if self.is_zero() { + *self = *other; + return; + } + + if other.is_zero() { + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square(); + + // Z2Z2 = Z2^2 + let mut z2z2 = other.z; + z2z2.square(); + + // U1 = X1*Z2Z2 + let mut u1 = self.x; + u1.mul_assign(&z2z2); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S1 = Y1*Z2*Z2Z2 + let mut s1 = self.y; + s1.mul_assign(&other.z); + s1.mul_assign(&z2z2); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if u1 == u2 && s1 == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + if u1 == u2 { + // The two points are equal, so we double. + (*self) = Self::zero(); + return; + } + + // H = U2-U1 + let mut h = u2; + h.sub_assign(&u1); + + // I = (2*H)^2 + let mut i = h; + i.double(); + i.square(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-S1) + let mut r = s2; + r.sub_assign(&s1); + r.double(); + + // V = U1*I + let mut v = u1; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r; + self.x.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V - X3) - 2*S1*J + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + s1.mul_assign(&j); // S1 = S1 * J * 2 + s1.double(); + self.y.sub_assign(&s1); + + // Z3 = ((Z1+Z2)^2 - Z1Z1 - Z2Z2)*H + self.z.add_assign(&other.z); + self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&z2z2); + self.z.mul_assign(&h); + } + } + + fn add_assign_mixed(&mut self, other: &Self::Affine) { + if other.is_zero() { + return; + } + + if self.is_zero() { + self.x = other.x; + self.y = other.y; + self.z = Fq::one(); + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square(); + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2.mul_assign(&z1z1); + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2.mul_assign(&self.z); + s2.mul_assign(&z1z1); + + if self.x == u2 && self.y == s2 { + // The two points are equal, so we double. + self.double(); + } else { + // If we're adding -a and a together, self.z becomes zero as H becomes zero. + + // H = U2-X1 + let mut h = u2; + h.sub_assign(&self.x); + + // HH = H^2 + let mut hh = h; + hh.square(); + + // I = 4*HH + let mut i = hh; + i.double(); + i.double(); + + // J = H*I + let mut j = h; + j.mul_assign(&i); + + // r = 2*(S2-Y1) + let mut r = s2; + r.sub_assign(&self.y); + r.double(); + + // V = X1*I + let mut v = self.x; + v.mul_assign(&i); + + // X3 = r^2 - J - 2*V + self.x = r; + self.x.square(); + self.x.sub_assign(&j); + self.x.sub_assign(&v); + self.x.sub_assign(&v); + + // Y3 = r*(V-X3)-2*Y1*J + j.mul_assign(&self.y); // J = 2*Y1*J + j.double(); + self.y = v; + self.y.sub_assign(&self.x); + self.y.mul_assign(&r); + self.y.sub_assign(&j); + + // Z3 = (Z1+H)^2-Z1Z1-HH + self.z.add_assign(&h); + self.z.square(); + self.z.sub_assign(&z1z1); + self.z.sub_assign(&hh); + } + } + + fn negate(&mut self) { + if !self.is_zero() { + self.y.negate() + } + } + + fn mul_assign::Repr>>(&mut self, other: S) { + let mut res = Self::zero(); + + let mut found_one = false; + + for i in BitIterator::new(other.into()) { + if found_one { + res.double(); + } else { + found_one = i; + } + + if i { + res.add_assign(self); + } + } + + *self = res; + } + + fn into_affine(&self) -> PointAffine { + (*self).into() + } + + fn recommended_wnaf_for_scalar(scalar: ::Repr) -> usize { + Self::empirical_recommended_wnaf_for_scalar(scalar) + } + + fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + Self::empirical_recommended_wnaf_for_num_scalars(num_scalars) + } + + fn as_xyz(&self) -> (&Self::Base, &Self::Base, &Self::Base) { + (&self.x, &self.y, &self.z) + } + + fn into_xyz_unchecked(self) -> (Self::Base, Self::Base, Self::Base) { + (self.x, self.y, self.z) + } + + fn from_xyz_unchecked(x: Self::Base, y: Self::Base, z: Self::Base) -> Self { + Self { x, y, z } + } + + fn from_xyz_checked( + _x: Self::Base, + _y: Self::Base, + _z: Self::Base, + ) -> Result { + unimplemented!() + } +} + +// The affine point X, Y is represented in the jacobian +// coordinates with Z = 1. +impl From for PointProjective { + fn from(p: PointAffine) -> PointProjective { + if p.is_zero() { + PointProjective::zero() + } else { + PointProjective { + x: p.x, + y: p.y, + z: Fq::one(), + } + } + } +} + +// The projective point X, Y, Z is represented in the affine +// coordinates as X/Z^2, Y/Z^3. +impl From for PointAffine { + fn from(p: PointProjective) -> PointAffine { + if p.is_zero() { + PointAffine::zero() + } else if p.z == Fq::one() { + // If Z is one, the point is already normalized. + PointAffine { + x: p.x, + y: p.y, + infinity: false, + } + } else { + // Z is nonzero, so it must have an inverse in a field. + let zinv = p.z.inverse().unwrap(); + let mut zinv_powered = zinv; + zinv_powered.square(); + + // X/Z^2 + let mut x = p.x; + x.mul_assign(&zinv_powered); + + // Y/Z^3 + let mut y = p.y; + zinv_powered.mul_assign(&zinv); + y.mul_assign(&zinv_powered); + + PointAffine { + x: x, + y: y, + infinity: false, + } + } + } +} + +impl rand::Rand for PointProjective { + fn rand(rng: &mut R) -> Self { + loop { + let x = rng.gen(); + let greatest = rng.gen(); + + if let Some(p) = PointAffine::get_point_from_x(x, greatest) { + if !p.is_zero() { + if p.is_on_curve() { + return p.into_projective(); + } + } + } + } + } +} + +impl rand::Rand for PointAffine { + fn rand(rng: &mut R) -> Self { + loop { + let x = rng.gen(); + let greatest = rng.gen(); + + if let Some(p) = PointAffine::get_point_from_x(x, greatest) { + if !p.is_zero() { + if p.is_on_curve() { + return p; + } + } + } + } + } +} + +impl PointAffine { + fn get_coeff_b() -> ::Base { + Fq::from_str("7").unwrap() + } + + fn get_generator() -> Self { + Self { + x: crate::ff::from_hex::( + "0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + ) + .unwrap(), + y: crate::ff::from_hex::( + "0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", + ) + .unwrap(), + infinity: false, + } + } +} + +impl PointProjective { + fn empirical_recommended_wnaf_for_scalar(scalar: FrRepr) -> usize { + let num_bits = scalar.num_bits() as usize; + + if num_bits >= 130 { + 4 + } else if num_bits >= 34 { + 3 + } else { + 2 + } + } + + fn empirical_recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { + const RECOMMENDATIONS: [usize; 12] = + [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569]; + + let mut ret = 4; + for r in &RECOMMENDATIONS { + if num_scalars > *r { + ret += 1; + } else { + break; + } + } + + ret + } +} + +impl GenericUncompressedEncodable<64> for PointAffine { + /// Converts this element into its uncompressed encoding, so long as it's not + /// the point at infinity. + fn into_uncompressed(&self) -> EncodingBytes { + todo!() + } + + /// Converts an uncompressed encoding into the curve point + fn from_uncompressed(_encoding: EncodingBytes) -> Result { + todo!() + } +} + +impl GenericCompressedEncodable<32> for PointAffine { + /// Converts this element into its uncompressed encoding, so long as it's not + /// the point at infinity. + fn into_compressed(&self) -> (EncodingBytes, bool) { + todo!(); + } + + /// Converts an uncompressed encoding into the curve point + fn from_compressed( + _encoding: EncodingBytes, + _parity: bool, + ) -> Result { + todo!() + } +} diff --git a/crates/zkevm_circuits/src/fsm_input_output/circuit_inputs/main_vm.rs b/crates/zkevm_circuits/src/fsm_input_output/circuit_inputs/main_vm.rs new file mode 100644 index 00000000..4a2c639b --- /dev/null +++ b/crates/zkevm_circuits/src/fsm_input_output/circuit_inputs/main_vm.rs @@ -0,0 +1,71 @@ +use super::*; + +use crate::base_structures::vm_state::*; +use boojum::gadgets::queue::*; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Debug)] +pub struct VmInputData { + pub rollback_queue_tail_for_block: [Num; QUEUE_STATE_WIDTH], + pub memory_queue_initial_state: QueueTailState, + pub decommitment_queue_initial_state: QueueTailState, + pub per_block_context: GlobalContext, +} + +impl CSPlaceholder for VmInputData { + fn placeholder>(cs: &mut CS) -> Self { + let zero_num = Num::zero(cs); + let empty_tail = QueueTailState::placeholder(cs); + let placeholder_ctx = GlobalContext::::placeholder(cs); + Self { + rollback_queue_tail_for_block: [zero_num; QUEUE_STATE_WIDTH], + memory_queue_initial_state: empty_tail, + decommitment_queue_initial_state: empty_tail, + per_block_context: placeholder_ctx, + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Debug)] +#[DerivePrettyComparison("true")] +pub struct VmOutputData { + pub log_queue_final_state: QueueState, + pub memory_queue_final_state: QueueState, + pub decommitment_queue_final_state: QueueState, +} + +impl CSPlaceholder for VmOutputData { + fn placeholder>(cs: &mut CS) -> Self { + let empty_small = QueueState::placeholder(cs); + let empty_large = QueueState::placeholder(cs); + Self { + log_queue_final_state: empty_small, + memory_queue_final_state: empty_large, + decommitment_queue_final_state: empty_large, + } + } +} + +use crate::base_structures::vm_state::VmLocalState; + +pub type VmCircuitInputOutput = + crate::fsm_input_output::ClosedFormInput, VmInputData, VmOutputData>; +pub type VmCircuitInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + VmLocalState, + VmInputData, + VmOutputData, +>; + +use crate::main_vm::witness_oracle::WitnessOracle; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug(bound = ""), Default)] +#[serde(bound = "")] +pub struct VmCircuitWitness> { + pub closed_form_input: VmCircuitInputOutputWitness, + #[derivative(Debug = "ignore")] + pub witness_oracle: W, +} diff --git a/crates/zkevm_circuits/src/fsm_input_output/circuit_inputs/mod.rs b/crates/zkevm_circuits/src/fsm_input_output/circuit_inputs/mod.rs new file mode 100644 index 00000000..4befa825 --- /dev/null +++ b/crates/zkevm_circuits/src/fsm_input_output/circuit_inputs/mod.rs @@ -0,0 +1,6 @@ +use super::*; +use boojum::cs::Variable; + +pub const INPUT_OUTPUT_COMMITMENT_LENGTH: usize = 4; + +pub mod main_vm; diff --git a/crates/zkevm_circuits/src/fsm_input_output/mod.rs b/crates/zkevm_circuits/src/fsm_input_output/mod.rs new file mode 100644 index 00000000..c6205f5e --- /dev/null +++ b/crates/zkevm_circuits/src/fsm_input_output/mod.rs @@ -0,0 +1,326 @@ +use super::*; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use cs_derive::*; + +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::Variable; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u32::UInt32; +use boojum::serde_utils::BigArraySerde; + +pub mod circuit_inputs; + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[WitnessHookBound( + " +where + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, +" +)] +#[derivative(Clone, Debug)] +pub struct ClosedFormInput< + F: SmallField, + T: Clone + std::fmt::Debug + CSAllocatable + CircuitVarLengthEncodable + WitnessHookable, + IN: Clone + std::fmt::Debug + CSAllocatable + CircuitVarLengthEncodable + WitnessHookable, + OUT: Clone + std::fmt::Debug + CSAllocatable + CircuitVarLengthEncodable + WitnessHookable, +> where + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, +{ + pub start_flag: Boolean, + pub completion_flag: Boolean, + pub observable_input: IN, + pub observable_output: OUT, + pub hidden_fsm_input: T, + pub hidden_fsm_output: T, +} + +impl< + F: SmallField, + T: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + IN: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + OUT: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + > ClosedFormInput +where + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, +{ + pub fn alloc_ignoring_outputs>( + cs: &mut CS, + witness: ClosedFormInputWitness, + ) -> Self + where + T: CSPlaceholder, + OUT: CSPlaceholder, + { + let start_flag = Boolean::allocate(cs, witness.start_flag); + let observable_input = IN::allocate(cs, witness.observable_input.clone()); + let hidden_fsm_input = T::allocate(cs, witness.hidden_fsm_input.clone()); + let boolean_false = Boolean::allocated_constant(cs, false); + + let observable_output = OUT::placeholder(cs); + let hidden_fsm_output = T::placeholder(cs); + + let new = Self { + start_flag, + completion_flag: boolean_false, + observable_input, + observable_output, + hidden_fsm_input, + hidden_fsm_output, + }; + + new + } + + #[track_caller] + pub fn hook_compare_witness>( + &self, + cs: &CS, + expected: & as CSAllocatable>::Witness, + ) where + T: PrettyComparison, + OUT: PrettyComparison, + { + if let Some(circuit_result) = (self.witness_hook(&*cs))() { + let comparison_lines = >::find_diffs( + &circuit_result.hidden_fsm_output, + &expected.hidden_fsm_output, + ); + if comparison_lines.is_empty() == false { + panic!( + "Difference in FSM. Left is circuit, right is expected:\n{}", + comparison_lines.join("\n") + ); + } + let comparison_lines = >::find_diffs( + &circuit_result.observable_output, + &expected.observable_output, + ); + if comparison_lines.is_empty() == false { + panic!( + "Difference in observable output. Left is circuit, right is expected:\n{}", + comparison_lines.join("\n") + ); + } + assert_eq!(&circuit_result, expected); + } + } +} + +pub const CLOSED_FORM_COMMITTMENT_LENGTH: usize = 4; + +impl< + F: SmallField, + T: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + IN: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + OUT: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + > std::default::Default for ClosedFormInputWitness +where + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, +{ + fn default() -> Self { + ClosedFormInput::::placeholder_witness() + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Debug)] +pub struct ClosedFormInputCompactForm { + pub start_flag: Boolean, + pub completion_flag: Boolean, + pub observable_input_committment: [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + pub observable_output_committment: [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + pub hidden_fsm_input_committment: [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + pub hidden_fsm_output_committment: [Num; CLOSED_FORM_COMMITTMENT_LENGTH], +} + +impl ClosedFormInputCompactForm { + pub fn from_full_form< + CS: ConstraintSystem, + T: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + IN: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + OUT: Clone + + std::fmt::Debug + + CSAllocatable + + CircuitVarLengthEncodable + + WitnessHookable, + R: CircuitRoundFunction, + >( + cs: &mut CS, + full_form: &ClosedFormInput, + round_function: &R, + ) -> Self + where + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + >::Witness: serde::Serialize + serde::de::DeserializeOwned + Eq, + { + let observable_input_committment = + commit_variable_length_encodable_item(cs, &full_form.observable_input, round_function); + let observable_output_committment = + commit_variable_length_encodable_item(cs, &full_form.observable_output, round_function); + + let hidden_fsm_input_committment = + commit_variable_length_encodable_item(cs, &full_form.hidden_fsm_input, round_function); + let hidden_fsm_output_committment = + commit_variable_length_encodable_item(cs, &full_form.hidden_fsm_output, round_function); + + // mask FSM part. Observable part is NEVER masked + + let zero_num = Num::zero(cs); + let empty_committment = [zero_num; CLOSED_FORM_COMMITTMENT_LENGTH]; + + let hidden_fsm_input_committment = Num::parallel_select( + cs, + full_form.start_flag, + &empty_committment, + &hidden_fsm_input_committment, + ); + + // mask output. Observable output is zero is not the last indeed + let observable_output_committment = Num::parallel_select( + cs, + full_form.completion_flag, + &observable_output_committment, + &empty_committment, + ); + + // and vice versa for FSM + let hidden_fsm_output_committment = Num::parallel_select( + cs, + full_form.completion_flag, + &empty_committment, + &hidden_fsm_output_committment, + ); + + let new = Self { + start_flag: full_form.start_flag, + completion_flag: full_form.completion_flag, + observable_input_committment, + observable_output_committment, + hidden_fsm_input_committment, + hidden_fsm_output_committment, + }; + + new + } +} + +pub fn commit_variable_length_encodable_item< + F: SmallField, + CS: ConstraintSystem, + T: CircuitVarLengthEncodable, + const AW: usize, + const SW: usize, + const CW: usize, + const N: usize, + R: CircuitRoundFunction, +>( + cs: &mut CS, + item: &T, + _round_function: &R, +) -> [Num; N] { + let expected_length = item.encoding_length(); + + let mut buffer = Vec::with_capacity(expected_length); + item.encode_to_buffer(cs, &mut buffer); + + assert_eq!(buffer.len(), expected_length); + + commit_encoding::(cs, &buffer, _round_function) +} + +pub fn commit_encoding< + F: SmallField, + CS: ConstraintSystem, + const AW: usize, + const SW: usize, + const CW: usize, + const N: usize, + R: CircuitRoundFunction, +>( + cs: &mut CS, + input: &[Variable], + _round_function: &R, +) -> [Num; N] { + // we use length specialization here + let expected_length = input.len(); + + let mut state = R::create_empty_state(cs); + let length = UInt32::allocated_constant(cs, expected_length as u32); + R::apply_length_specialization(cs, &mut state, length.get_variable()); + + // pad with zeroes + + let mut buffer_length = expected_length / AW; + if expected_length % AW != 0 { + buffer_length += 1; + } + + buffer_length *= AW; + + let mut buffer = Vec::with_capacity(buffer_length); + buffer.extend_from_slice(input); + + let zero_var = cs.allocate_constant(F::ZERO); + buffer.resize(buffer_length, zero_var); + + for chunk in buffer.array_chunks::() { + let capacity_els = R::split_capacity_elements(&state); + + state = R::absorb_with_replacement(cs, *chunk, capacity_els); + state = R::compute_round_function(cs, state); + } + + let output = R::state_into_commitment::(&state); + + output.map(|el| Num::from_variable(el)) +} diff --git a/crates/zkevm_circuits/src/keccak256_round_function/input.rs b/crates/zkevm_circuits/src/keccak256_round_function/input.rs new file mode 100644 index 00000000..d521b41a --- /dev/null +++ b/crates/zkevm_circuits/src/keccak256_round_function/input.rs @@ -0,0 +1,95 @@ +use std::collections::VecDeque; + +use super::*; + +use crate::base_structures::precompile_input_outputs::*; +use crate::base_structures::vm_state::*; +use boojum::cs::Variable; +use boojum::gadgets::queue::*; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::keccak256::{BYTES_PER_WORD, LANE_WIDTH}; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::serde_utils::BigArraySerde; + +#[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 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], +} + +impl CSPlaceholder for Keccak256RoundFunctionFSM { + fn placeholder>(cs: &mut CS) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + let zero_u8 = UInt8::zero(cs); + let zero_u32 = UInt32::zero(cs); + Self { + read_precompile_call: boolean_false, + read_unaligned_words_for_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], + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct Keccak256RoundFunctionFSMInputOutput { + pub internal_fsm: Keccak256RoundFunctionFSM, + pub log_queue_state: QueueState, + pub memory_queue_state: QueueState, +} + +impl CSPlaceholder for Keccak256RoundFunctionFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + internal_fsm: Keccak256RoundFunctionFSM::placeholder(cs), + log_queue_state: QueueState::::placeholder(cs), + memory_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type Keccak256RoundFunctionCircuitInputOutput = ClosedFormInput< + F, + Keccak256RoundFunctionFSMInputOutput, + PrecompileFunctionInputData, + PrecompileFunctionOutputData, +>; +pub type Keccak256RoundFunctionCircuitInputOutputWitness = ClosedFormInputWitness< + F, + Keccak256RoundFunctionFSMInputOutput, + PrecompileFunctionInputData, + PrecompileFunctionOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct Keccak256RoundFunctionCircuitInstanceWitness { + pub closed_form_input: Keccak256RoundFunctionCircuitInputOutputWitness, + pub requests_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, + pub memory_reads_witness: VecDeque, +} diff --git a/crates/zkevm_circuits/src/keccak256_round_function/mod.rs b/crates/zkevm_circuits/src/keccak256_round_function/mod.rs new file mode 100644 index 00000000..b7264881 --- /dev/null +++ b/crates/zkevm_circuits/src/keccak256_round_function/mod.rs @@ -0,0 +1,573 @@ +use super::*; + +use boojum::field::SmallField; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::gadgets::boolean::Boolean; +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 cs_derive::*; + +use crate::ethereum_types::U256; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use boojum::gadgets::num::Num; +use zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE; + +use crate::base_structures::log_query::*; +use crate::base_structures::memory_query::*; +use crate::base_structures::precompile_input_outputs::PrecompileFunctionOutputData; +use crate::demux_log_queue::StorageLogQueue; +use crate::fsm_input_output::*; +use crate::storage_application::ConditionalWitnessAllocator; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::Variable; +use boojum::gadgets::keccak256::{self}; +use boojum::gadgets::queue::CircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::{CSAllocatableExt, CSPlaceholder}; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::u160::UInt160; +use boojum::gadgets::u8::UInt8; +use std::sync::{Arc, RwLock}; + +pub mod input; +use self::input::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +// #[DerivePrettyComparison("true")] +pub struct Keccak256PrecompileCallParams { + pub input_page: UInt32, + pub input_offset: UInt32, + pub output_page: UInt32, + pub output_offset: UInt32, + pub num_rounds: UInt32, +} + +impl CSPlaceholder for Keccak256PrecompileCallParams { + fn placeholder>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + Self { + input_page: zero_u32, + input_offset: zero_u32, + output_page: zero_u32, + output_offset: zero_u32, + num_rounds: zero_u32, + } + } +} + +impl Keccak256PrecompileCallParams { + pub fn from_encoding>(_cs: &mut CS, encoding: UInt256) -> Self { + let input_offset = encoding.inner[0]; + let output_offset = encoding.inner[2]; + let input_page = encoding.inner[4]; + let output_page = encoding.inner[5]; + + let num_rounds = encoding.inner[6]; + + let new = Self { + input_page, + input_offset, + output_page, + output_offset, + num_rounds, + }; + + new + } +} + +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 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 fn keccak256_precompile_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + memory_queue: &mut MemoryQueue, + precompile_calls_queue: &mut StorageLogQueue, + memory_read_witness: ConditionalWitnessAllocator>, + mut state: Keccak256RoundFunctionFSM, + _round_function: &R, + limit: usize, +) -> Keccak256RoundFunctionFSM +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + assert!(limit <= u32::MAX as usize); + + let precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::KECCAK256_ROUND_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + let aux_byte_for_precompile = UInt8::allocated_constant(cs, PRECOMPILE_AUX_BYTE); + + 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, + ); + + // 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; + let input_queue_is_empty = precompile_calls_queue.is_empty(cs); + // we can only skip the full circuit if we are not in any form of progress + let can_finish_immediatelly = + Boolean::multi_and(cs, &[state.read_precompile_call, input_queue_is_empty]); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(can_finish_immediatelly.witness_hook(cs)()); + dbg!(state.witness_hook(cs)()); + } + + state.read_precompile_call = state + .read_precompile_call + .mask_negated(cs, can_finish_immediatelly); + state.read_unaligned_words_for_round = state + .read_unaligned_words_for_round + .mask_negated(cs, can_finish_immediatelly); + state.completed = Boolean::multi_or(cs, &[state.completed, can_finish_immediatelly]); + + // main work cycle + for _cycle in 0..limit { + // 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); + + Num::conditionally_enforce_equal( + cs, + state.read_precompile_call, + &Num::from_variable(precompile_call.aux_byte.get_variable()), + &Num::from_variable(aux_byte_for_precompile.get_variable()), + ); + for (a, b) in precompile_call + .address + .inner + .iter() + .zip(precompile_address.inner.iter()) + { + Num::conditionally_enforce_equal( + cs, + state.read_precompile_call, + &Num::from_variable(a.get_variable()), + &Num::from_variable(b.get_variable()), + ); + } + + // now compute some parameters that describe the call itself + + let params_encoding = precompile_call.key; + let call_params = Keccak256PrecompileCallParams::from_encoding(cs, params_encoding); + + state.precompile_call_params = Keccak256PrecompileCallParams::conditionally_select( + cs, + state.read_precompile_call, + &call_params, + &state.precompile_call_params, + ); + // also set timestamps + state.timestamp_to_use_for_read = UInt32::conditionally_select( + cs, + state.read_precompile_call, + &precompile_call.timestamp, + &state.timestamp_to_use_for_read, + ); + + // timestamps have large space, so this can be expected + let timestamp_to_use_for_write = + unsafe { state.timestamp_to_use_for_read.increment_unchecked(cs) }; + state.timestamp_to_use_for_write = UInt32::conditionally_select( + cs, + state.read_precompile_call, + ×tamp_to_use_for_write, + &state.timestamp_to_use_for_write, + ); + + // and do some work! keccak256 is expensive + let reset_buffer = Boolean::multi_or(cs, &[state.read_precompile_call, state.completed]); + state.read_unaligned_words_for_round = Boolean::multi_or( + cs, + &[ + state.read_precompile_call, + state.read_unaligned_words_for_round, + ], + ); + state.read_precompile_call = boolean_false; + + // --------------------------------- + // 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); + } + + // 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); + } + + 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); + + unsafe { UInt16::from_variable_unchecked(lc.get_variable()) } + }; + + // 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 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(); + + 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, + 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( + cs, + should_read, + &may_be_new_offset, + &state.precompile_call_params.input_offset, + ); + + // perform read + memory_queue.push(cs, read_query, should_read); + + // we need to change endianess. Memory is BE, and each of 4 byte chunks should be interpreted as BE u32 for sha256 + let be_bytes = read_query_value.to_be_bytes(cs); + *dst = be_bytes; + } + + // 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; + + 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]); + } + + 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( + cs, + state.read_unaligned_words_for_round, + &may_be_new_num_rounds, + &state.precompile_call_params.num_rounds, + ); + + // 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..]); + + // update buffers + state.u8_words_buffer = tmp_buffer; + state.u64_words_buffer_markers = tmp_buffer_markers; + + // conditionally reset state. Keccak256 empty state is just all 0s + + 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); + } + } + } + + // 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 result = UInt256::from_be_bytes(cs, squeezed); + + 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, + rw_flag: boolean_true, + is_ptr: boolean_false, + value: result, + }; + + // perform write + memory_queue.push(cs, write_query, write_result); + + // --------------------------------- + + // update call props + 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]); + let process_next = Boolean::multi_and(cs, &[write_result, input_is_not_empty]); + + 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]); + state.read_unaligned_words_for_round = t.negated(cs); + } + + precompile_calls_queue.enforce_consistency(cs); + + state +} + +#[track_caller] +pub fn keccak256_round_function_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: Keccak256RoundFunctionCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + let Keccak256RoundFunctionCircuitInstanceWitness { + closed_form_input, + requests_queue_witness, + memory_reads_witness, + } = witness; + + let mut structured_input = Keccak256RoundFunctionCircuitInputOutput::alloc_ignoring_outputs( + cs, + closed_form_input.clone(), + ); + + let start_flag = structured_input.start_flag; + + let requests_queue_state_from_input = structured_input.observable_input.initial_log_queue_state; + + // it must be trivial + requests_queue_state_from_input.enforce_trivial_head(cs); + + let requests_queue_state_from_fsm = structured_input.hidden_fsm_input.log_queue_state; + + let requests_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &requests_queue_state_from_input, + &requests_queue_state_from_fsm, + ); + + let memory_queue_state_from_input = + structured_input.observable_input.initial_memory_queue_state; + + // it must be trivial + memory_queue_state_from_input.enforce_trivial_head(cs); + + let memory_queue_state_from_fsm = structured_input.hidden_fsm_input.memory_queue_state; + + let memory_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &memory_queue_state_from_input, + &memory_queue_state_from_fsm, + ); + + let mut requests_queue = StorageLogQueue::::from_state(cs, requests_queue_state); + let queue_witness = CircuitQueueWitness::from_inner_witness(requests_queue_witness); + requests_queue.witness = Arc::new(queue_witness); + + let mut memory_queue = MemoryQueue::::from_state(cs, memory_queue_state); + + let read_queries_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(memory_reads_witness)), + }; + + let mut starting_fsm_state = Keccak256RoundFunctionFSM::placeholder(cs); + starting_fsm_state.read_precompile_call = Boolean::allocated_constant(cs, true); + + let initial_state = Keccak256RoundFunctionFSM::conditionally_select( + cs, + start_flag, + &starting_fsm_state, + &structured_input.hidden_fsm_input.internal_fsm, + ); + + let final_state = keccak256_precompile_inner::( + cs, + &mut memory_queue, + &mut requests_queue, + read_queries_allocator, + initial_state, + round_function, + limit, + ); + + let final_memory_state = memory_queue.into_state(); + let final_requets_state = requests_queue.into_state(); + + // form the final state + let done = final_state.completed; + structured_input.completion_flag = done; + structured_input.observable_output = PrecompileFunctionOutputData::placeholder(cs); + + structured_input.observable_output.final_memory_state = QueueState::conditionally_select( + cs, + structured_input.completion_flag, + &final_memory_state, + &structured_input.observable_output.final_memory_state, + ); + + structured_input.hidden_fsm_output.internal_fsm = final_state; + structured_input.hidden_fsm_output.log_queue_state = final_requets_state; + structured_input.hidden_fsm_output.memory_queue_state = final_memory_state; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +pub(crate) fn keccak256_absorb_and_run_permutation>( + cs: &mut CS, + state: &mut [[[UInt8; keccak256::BYTES_PER_WORD]; keccak256::LANE_WIDTH]; + keccak256::LANE_WIDTH], + block: &[UInt8; keccak256::KECCAK_RATE_BYTES], +) -> [UInt8; keccak256::KECCAK256_DIGEST_SIZE] { + let mut state_as_variables = state.map(|el| el.map(|el| el.map(|el| el.get_variable()))); + for i in 0..keccak256::LANE_WIDTH { + for j in 0..keccak256::LANE_WIDTH { + if i + keccak256::LANE_WIDTH * j + < (keccak256::KECCAK_RATE_BYTES / keccak256::BYTES_PER_WORD) + { + let tmp = block + .array_chunks::<{ keccak256::BYTES_PER_WORD }>() + .skip(i + keccak256::LANE_WIDTH * j) + .next() + .unwrap(); + use boojum::gadgets::blake2s::mixing_function::xor_many; + let tmp = tmp.map(|el| el.get_variable()); + state_as_variables[i][j] = xor_many(cs, &state_as_variables[i][j], &tmp); + } + } + } + use boojum::gadgets::keccak256::round_function::keccak_256_round_function; + keccak_256_round_function(cs, &mut state_as_variables); + + let new_state = unsafe { + state_as_variables.map(|el| el.map(|el| el.map(|el| UInt8::from_variable_unchecked(el)))) + }; + + *state = new_state; + + // copy back + let mut result = + [std::mem::MaybeUninit::>::uninit(); keccak256::KECCAK256_DIGEST_SIZE]; + for (i, dst) in result.array_chunks_mut::<8>().enumerate() { + for (dst, src) in dst.iter_mut().zip(state[i][0].iter()) { + dst.write(*src); + } + } + + unsafe { result.map(|el| el.assume_init()) } +} diff --git a/crates/zkevm_circuits/src/lib.rs b/crates/zkevm_circuits/src/lib.rs new file mode 100644 index 00000000..ddc2dcf2 --- /dev/null +++ b/crates/zkevm_circuits/src/lib.rs @@ -0,0 +1,41 @@ +#![allow(clippy::drop_ref)] +#![allow(dead_code)] +#![allow(dropping_references)] +#![allow(unused_imports)] +#![feature(generic_const_exprs)] +#![feature(array_chunks)] +#![feature(more_qualified_paths)] + +use derivative::*; + +pub use boojum; +pub use boojum::ethereum_types; + +pub mod config; + +pub mod base_structures; +pub mod code_unpacker_sha256; +pub mod demux_log_queue; +pub mod ecrecover; +pub mod fsm_input_output; +pub mod keccak256_round_function; +pub mod linear_hasher; +pub mod log_sorter; +pub mod main_vm; +pub mod ram_permutation; +pub mod recursion; +pub mod scheduler; +pub mod sha256_round_function; +pub mod sort_decommittment_requests; +pub mod storage_application; +pub mod storage_validity_by_grand_product; +pub mod tables; +pub mod utils; + +use boojum::pairing::ff; + +pub const DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS: usize = 2; + +pub const fn bit_width_to_bitmask(width: usize) -> u64 { + (1u64 << width) - 1 +} diff --git a/crates/zkevm_circuits/src/linear_hasher/input.rs b/crates/zkevm_circuits/src/linear_hasher/input.rs new file mode 100644 index 00000000..9ed28174 --- /dev/null +++ b/crates/zkevm_circuits/src/linear_hasher/input.rs @@ -0,0 +1,80 @@ +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + 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::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; +use std::collections::VecDeque; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct LinearHasherInputData { + pub queue_state: QueueState, +} + +impl CSPlaceholder for LinearHasherInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct LinearHasherOutputData { + pub keccak256_hash: [UInt8; 32], +} + +impl CSPlaceholder for LinearHasherOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + keccak256_hash: [UInt8::::placeholder(cs); 32], + } + } +} + +pub type LinearHasherInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + (), + LinearHasherInputData, + LinearHasherOutputData, +>; + +pub type LinearHasherInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + (), + LinearHasherInputData, + LinearHasherOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct LinearHasherCircuitInstanceWitness { + pub closed_form_input: LinearHasherInputOutputWitness, + // #[serde(bound( + // serialize = "CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>: serde::Serialize" + // ))] + // #[serde(bound( + // deserialize = "CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>: serde::de::DeserializeOwned" + // ))] + pub queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, +} diff --git a/crates/zkevm_circuits/src/linear_hasher/mod.rs b/crates/zkevm_circuits/src/linear_hasher/mod.rs new file mode 100644 index 00000000..2409b3c8 --- /dev/null +++ b/crates/zkevm_circuits/src/linear_hasher/mod.rs @@ -0,0 +1,214 @@ +use std::collections::VecDeque; +use std::mem::MaybeUninit; + +use crate::base_structures::log_query::LogQuery; +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::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::num::Num; +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::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use std::sync::{Arc, RwLock}; +use zkevm_opcode_defs::system_params::STORAGE_AUX_BYTE; + +use super::*; + +pub mod input; +use self::input::*; + +pub fn linear_hasher_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: LinearHasherCircuitInstanceWitness, + round_function: &R, + params: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + let limit = params; + + assert!(limit <= u32::MAX as usize); + + let LinearHasherCircuitInstanceWitness { + closed_form_input, + queue_witness, + } = witness; + + let mut structured_input = + LinearHasherInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + let start_flag = structured_input.start_flag; + + let zero_u8: UInt8 = UInt8::zero(cs); + let boolean_true = Boolean::allocated_constant(cs, true); + + // only 1 instance of the circuit here for now + Boolean::enforce_equal(cs, &start_flag, &boolean_true); + + let queue_state_from_input = structured_input.observable_input.queue_state; + + // it must be trivial + queue_state_from_input.enforce_trivial_head(cs); + + let mut queue = StorageLogQueue::::from_state(cs, queue_state_from_input); + let queue_witness = CircuitQueueWitness::from_inner_witness(queue_witness); + queue.witness = Arc::new(queue_witness); + + let keccak_accumulator_state = + [[[zero_u8; keccak256::BYTES_PER_WORD]; keccak256::LANE_WIDTH]; keccak256::LANE_WIDTH]; + + let mut keccak_accumulator_state = + keccak_accumulator_state.map(|el| el.map(|el| el.map(|el| el.get_variable()))); + + use crate::base_structures::log_query::L2_TO_L1_MESSAGE_BYTE_LENGTH; + // we do not serialize length because it's recalculatable in L1 + + let empty_hash = { + use zkevm_opcode_defs::sha3::*; + + let mut result = [0u8; 32]; + let digest = Keccak256::digest(&[]); + result.copy_from_slice(digest.as_slice()); + + result.map(|el| UInt8::allocated_constant(cs, el)) + }; + + let mut buffer = vec![]; + + let mut done = queue.is_empty(cs); + let no_work = done; + + use crate::storage_application::keccak256_conditionally_absorb_and_run_permutation; + use boojum::gadgets::keccak256::KECCAK_RATE_BYTES; + + for _cycle in 0..limit { + let queue_is_empty = queue.is_empty(cs); + let should_pop = queue_is_empty.negated(cs); + + let (storage_log, _) = queue.pop_front(cs, should_pop); + + let now_empty = queue.is_empty(cs); + let is_last_serialization = Boolean::multi_and(cs, &[should_pop, now_empty]); + use crate::base_structures::ByteSerializable; + let as_bytes = storage_log.into_bytes(cs); + + assert!(buffer.len() < 136); + + buffer.extend(as_bytes); + + let continue_to_absorb = done.negated(cs); + + if buffer.len() >= 136 { + let buffer_for_round: [UInt8; KECCAK_RATE_BYTES] = buffer[..136].try_into().unwrap(); + let buffer_for_round = buffer_for_round.map(|el| el.get_variable()); + let carry_on = buffer[136..].to_vec(); + + buffer = carry_on; + + // absorb if we are not done yet + keccak256_conditionally_absorb_and_run_permutation( + cs, + continue_to_absorb, + &mut keccak_accumulator_state, + &buffer_for_round, + ); + } + + assert!(buffer.len() < 136); + + // in case if we do last round + { + let absorb_as_last_round = + Boolean::multi_and(cs, &[continue_to_absorb, is_last_serialization]); + let mut last_round_buffer = [zero_u8; KECCAK_RATE_BYTES]; + let tail_len = buffer.len(); + last_round_buffer[..tail_len].copy_from_slice(&buffer); + + if tail_len == KECCAK_RATE_BYTES - 1 { + // unreachable, but we set it for completeness + last_round_buffer[tail_len] = UInt8::allocated_constant(cs, 0x81); + } else { + last_round_buffer[tail_len] = UInt8::allocated_constant(cs, 0x01); + last_round_buffer[KECCAK_RATE_BYTES - 1] = UInt8::allocated_constant(cs, 0x80); + } + + let last_round_buffer = last_round_buffer.map(|el| el.get_variable()); + + // absorb if it's the last round + keccak256_conditionally_absorb_and_run_permutation( + cs, + absorb_as_last_round, + &mut keccak_accumulator_state, + &last_round_buffer, + ); + } + + done = Boolean::multi_or(cs, &[done, is_last_serialization]); + } + + queue.enforce_consistency(cs); + let completed = queue.is_empty(cs); + + Boolean::enforce_equal(cs, &completed, &boolean_true); + + structured_input.completion_flag = completed.clone(); + + let fsm_output = (); + structured_input.hidden_fsm_output = fsm_output; + + // squeeze + let mut keccak256_hash = [MaybeUninit::>::uninit(); keccak256::KECCAK256_DIGEST_SIZE]; + for (i, dst) in keccak256_hash.array_chunks_mut::<8>().enumerate() { + for (dst, src) in dst.iter_mut().zip(keccak_accumulator_state[i][0].iter()) { + let tmp = unsafe { UInt8::from_variable_unchecked(*src) }; + dst.write(tmp); + } + } + + let keccak256_hash = unsafe { keccak256_hash.map(|el| el.assume_init()) }; + + let keccak256_hash = + <[UInt8; 32]>::conditionally_select(cs, no_work, &empty_hash, &keccak256_hash); + + let mut observable_output = LinearHasherOutputData::placeholder(cs); + observable_output.keccak256_hash = keccak256_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 +} diff --git a/crates/zkevm_circuits/src/log_sorter/input.rs b/crates/zkevm_circuits/src/log_sorter/input.rs new file mode 100644 index 00000000..ee2d1de6 --- /dev/null +++ b/crates/zkevm_circuits/src/log_sorter/input.rs @@ -0,0 +1,106 @@ +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + vm_state::*, +}; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; + +use crate::DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS; + +#[derive(Derivative, CSAllocatable, CSVarLengthEncodable, CSSelectable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct EventsDeduplicatorFSMInputOutput { + pub lhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub rhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub initial_unsorted_queue_state: QueueState, + pub intermediate_sorted_queue_state: QueueState, + pub final_result_queue_state: QueueState, + pub previous_key: UInt32, + pub previous_item: LogQuery, +} + +impl CSPlaceholder for EventsDeduplicatorFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + let zero_num = Num::zero(cs); + let zero_u32 = UInt32::zero(cs); + Self { + lhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + rhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + initial_unsorted_queue_state: QueueState::::placeholder(cs), + intermediate_sorted_queue_state: QueueState::::placeholder(cs), + final_result_queue_state: QueueState::::placeholder(cs), + previous_key: zero_u32, + previous_item: LogQuery::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct EventsDeduplicatorInputData { + pub initial_log_queue_state: QueueState, + pub intermediate_sorted_queue_state: QueueState, +} + +impl CSPlaceholder for EventsDeduplicatorInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + initial_log_queue_state: QueueState::::placeholder(cs), + intermediate_sorted_queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct EventsDeduplicatorOutputData { + pub final_queue_state: QueueState, +} + +impl CSPlaceholder for EventsDeduplicatorOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + final_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type EventsDeduplicatorInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + EventsDeduplicatorFSMInputOutput, + EventsDeduplicatorInputData, + EventsDeduplicatorOutputData, +>; +pub type EventsDeduplicatorInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + EventsDeduplicatorFSMInputOutput, + EventsDeduplicatorInputData, + EventsDeduplicatorOutputData, +>; +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct EventsDeduplicatorInstanceWitness { + pub closed_form_input: EventsDeduplicatorInputOutputWitness, + pub initial_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, + pub intermediate_sorted_queue_witness: + CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, +} diff --git a/crates/zkevm_circuits/src/log_sorter/mod.rs b/crates/zkevm_circuits/src/log_sorter/mod.rs new file mode 100644 index 00000000..b29ccc97 --- /dev/null +++ b/crates/zkevm_circuits/src/log_sorter/mod.rs @@ -0,0 +1,814 @@ +pub mod input; + +use super::*; +use crate::base_structures::log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}; +use crate::base_structures::vm_state::*; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::fsm_input_output::{commit_variable_length_encodable_item, ClosedFormInputCompactForm}; +use crate::storage_validity_by_grand_product::unpacked_long_comparison; +use crate::utils::accumulate_grand_products; +use boojum::cs::{gates::*, traits::cs::ConstraintSystem}; +use boojum::field::SmallField; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{allocatable::CSAllocatableExt, selectable::Selectable}, + u256::UInt256, + u32::UInt32, + u8::UInt8, +}; + +use crate::demux_log_queue::StorageLogQueue; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +// This is a sorter of logs that are kind-of "pure", e.g. event emission or L2 -> L1 messages. +// Those logs do not affect a global state and may either be rolled back in full or not. +// We identify equality of logs using "timestamp" field that is a monotonic unique counter +// across the block + +pub const NUM_PERMUTATION_ARG_CHALLENGES: usize = LOG_QUERY_PACKED_WIDTH + 1; + +use crate::log_sorter::input::*; + +pub fn sort_and_deduplicate_events_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: EventsDeduplicatorInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + //use table + let EventsDeduplicatorInstanceWitness { + closed_form_input, + initial_queue_witness, + intermediate_sorted_queue_witness, + } = witness; + + let mut structured_input = + EventsDeduplicatorInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + + let unsorted_queue_from_passthrough_state = + structured_input.observable_input.initial_log_queue_state; + + // passthrough must be trivial + unsorted_queue_from_passthrough_state.enforce_trivial_head(cs); + + let unsorted_queue_from_fsm_input_state = structured_input + .hidden_fsm_input + .initial_unsorted_queue_state; + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &unsorted_queue_from_passthrough_state, + &unsorted_queue_from_fsm_input_state, + ); + + let mut unsorted_queue = StorageLogQueue::::from_state(cs, state); + + use std::sync::Arc; + let initial_queue_witness = CircuitQueueWitness::from_inner_witness(initial_queue_witness); + unsorted_queue.witness = Arc::new(initial_queue_witness); + + let intermediate_sorted_queue_from_passthrough_state = structured_input + .observable_input + .intermediate_sorted_queue_state; + + // passthrough must be trivial + intermediate_sorted_queue_from_passthrough_state.enforce_trivial_head(cs); + + let intermediate_sorted_queue_from_fsm_state = structured_input + .hidden_fsm_input + .intermediate_sorted_queue_state; + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &intermediate_sorted_queue_from_passthrough_state, + &intermediate_sorted_queue_from_fsm_state, + ); + let mut intermediate_sorted_queue = StorageLogQueue::::from_state(cs, state); + let intermediate_sorted_queue_witness = + CircuitQueueWitness::from_inner_witness(intermediate_sorted_queue_witness); + intermediate_sorted_queue.witness = Arc::new(intermediate_sorted_queue_witness); + + let final_sorted_queue_from_fsm = structured_input.hidden_fsm_input.final_result_queue_state; + let empty_state = QueueState::empty(cs); + + let final_sorted_state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &empty_state, + &final_sorted_queue_from_fsm, + ); + let mut final_sorted_queue = StorageLogQueue::::from_state(cs, final_sorted_state); + + // get challenges for permutation argument + let challenges = crate::utils::produce_fs_challenges::< + F, + CS, + R, + QUEUE_STATE_WIDTH, + { LOG_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + structured_input + .observable_input + .initial_log_queue_state + .tail, + structured_input + .observable_input + .intermediate_sorted_queue_state + .tail, + round_function, + ); + + let one = Num::allocated_constant(cs, F::ONE); + let initial_lhs = Num::parallel_select( + cs, + structured_input.start_flag, + &[one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &structured_input.hidden_fsm_input.lhs_accumulator, + ); + + let initial_rhs = Num::parallel_select( + cs, + structured_input.start_flag, + &[one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &structured_input.hidden_fsm_input.rhs_accumulator, + ); + + // there is no code at address 0 in our case, so we can formally use it for all the purposes + let zero_u32 = UInt32::zero(cs); + let previous_key = UInt32::conditionally_select( + cs, + structured_input.start_flag, + &zero_u32, + &structured_input.hidden_fsm_input.previous_key, + ); + + // there is no code at address 0 in our case, so we can formally use it for all the purposes + use boojum::gadgets::traits::allocatable::CSPlaceholder; + let empty_storage = LogQuery::placeholder(cs); + let previous_item = LogQuery::conditionally_select( + cs, + structured_input.start_flag, + &empty_storage, + &structured_input.hidden_fsm_input.previous_item, + ); + + let (new_lhs, new_rhs, previous_key, previous_item) = + repack_and_prove_events_rollbacks_inner::<_, _, R>( + cs, + initial_lhs, + initial_rhs, + &mut unsorted_queue, + &mut intermediate_sorted_queue, + &mut final_sorted_queue, + structured_input.start_flag, + challenges, + previous_key, + previous_item, + limit, + ); + + let unsorted_is_empty = unsorted_queue.is_empty(cs); + let sorted_is_empty = intermediate_sorted_queue.is_empty(cs); + + Boolean::enforce_equal(cs, &unsorted_is_empty, &sorted_is_empty); + + let completed = unsorted_queue.length.is_zero(cs); + for (lhs, rhs) in new_lhs.iter().zip(new_rhs.iter()) { + Num::conditionally_enforce_equal(cs, completed, lhs, rhs); + } + // form the final state + structured_input.hidden_fsm_output.previous_key = previous_key; + structured_input.hidden_fsm_output.previous_item = previous_item; + structured_input.hidden_fsm_output.lhs_accumulator = new_lhs; + structured_input.hidden_fsm_output.rhs_accumulator = new_rhs; + + structured_input + .hidden_fsm_output + .initial_unsorted_queue_state = unsorted_queue.into_state(); + structured_input + .hidden_fsm_output + .intermediate_sorted_queue_state = intermediate_sorted_queue.into_state(); + + structured_input.completion_flag = completed; + + let empty_state = QueueState::empty(cs); + let final_queue_for_observable_output = QueueState::conditionally_select( + cs, + completed, + &final_sorted_queue.into_state(), + &empty_state, + ); + + structured_input.observable_output.final_queue_state = final_queue_for_observable_output; + + structured_input.hidden_fsm_output.final_result_queue_state = final_sorted_queue.into_state(); + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +pub fn repack_and_prove_events_rollbacks_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + mut lhs: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + mut rhs: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + unsorted_queue: &mut StorageLogQueue, + intermediate_sorted_queue: &mut StorageLogQueue, + result_queue: &mut StorageLogQueue, + is_start: Boolean, + fs_challenges: [[Num; LOG_QUERY_PACKED_WIDTH + 1]; + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + mut previous_key: UInt32, + mut previous_item: LogQuery, + limit: usize, +) -> ( + [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + UInt32, + LogQuery, +) +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + assert!(limit <= u32::MAX as usize); + + // we can recreate it here, there are two cases: + // - we are 100% empty, but it's the only circuit in this case + // - otherwise we continue, and then it's not trivial + + // NOTE: scheduler guarantees that only 1 - the first - circuit will have "is_start", + // so to take a shortcut we can only need to test if there is nothing in the queue + let no_work = unsorted_queue.is_empty(cs); + let mut previous_is_trivial = Boolean::multi_or(cs, &[no_work, is_start]); + + let unsorted_queue_lenght = Num::from_variable(unsorted_queue.length.get_variable()); + let intermediate_sorted_queue_lenght = + Num::from_variable(intermediate_sorted_queue.length.get_variable()); + + Num::enforce_equal( + cs, + &unsorted_queue_lenght, + &intermediate_sorted_queue_lenght, + ); + + // reallocate and simultaneously collapse rollbacks + + for _cycle in 0..limit { + let original_is_empty = unsorted_queue.is_empty(cs); + let sorted_is_empty = intermediate_sorted_queue.is_empty(cs); + Boolean::enforce_equal(cs, &original_is_empty, &sorted_is_empty); + + let should_pop = original_is_empty.negated(cs); + let is_trivial = original_is_empty; + + let (unsorted_item, original_encoding) = unsorted_queue.pop_front(cs, should_pop); + let (sorted_item, sorted_encoding) = intermediate_sorted_queue.pop_front(cs, should_pop); + + // we also ensure that original items are "write" unless it's a padding + unsorted_item + .rw_flag + .conditionally_enforce_true(cs, should_pop); + + accumulate_grand_products::< + F, + CS, + LOG_QUERY_PACKED_WIDTH, + { LOG_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + &mut lhs, + &mut rhs, + &fs_challenges, + &original_encoding, + &sorted_encoding, + should_pop, + ); + + // now ensure sorting + { + // sanity check - all such logs are "write into the sky" + sorted_item + .rw_flag + .conditionally_enforce_true(cs, should_pop); + + // check if keys are equal and check a value + + // We compare timestamps, and then resolve logic over rollbacks, so the only way when + // keys are equal can be when we do rollback + let sorting_key = sorted_item.timestamp; + + // ensure sorting for uniqueness timestamp and rollback flag + // We know that timestamps are unique accross logs, and are also the same between write and rollback + let (keys_are_equal, new_key_is_smaller) = + unpacked_long_comparison(cs, &[previous_key], &[sorting_key]); + + // keys are always ordered as >= unless is padding + new_key_is_smaller.conditionally_enforce_false(cs, should_pop); + + let same_log = keys_are_equal; + let same_nontrivial_log = Boolean::multi_and(cs, &[should_pop, same_log]); + let may_be_different_log = same_log.negated(cs); + let different_nontrivial_log = + Boolean::multi_and(cs, &[should_pop, may_be_different_log]); + + // if we pop an item and it's not trivial with different log, then it MUST be non-rollback + let this_item_is_not_rollback = sorted_item.rollback.negated(cs); + this_item_is_not_rollback.conditionally_enforce_true(cs, different_nontrivial_log); + + // if it's same non-trivial log, then previous one is always guaranteed to be not-rollback by line above, + // and so this one should be rollback + sorted_item + .rollback + .conditionally_enforce_true(cs, same_nontrivial_log); + + // we self-check ourselves over the content of the log, even though by the construction + // of the queue it's a guaranteed permutation + let keys_are_equal = UInt256::equals(cs, &sorted_item.key, &previous_item.key); + let values_are_equal = + UInt256::equals(cs, &sorted_item.written_value, &previous_item.written_value); + let same_body = Boolean::multi_and(cs, &[keys_are_equal, values_are_equal]); + + // if previous is not trivial then we always have equal content + let previous_is_non_trivial = previous_is_trivial.negated(cs); + let should_enforce = Boolean::multi_and(cs, &[same_log, previous_is_non_trivial]); + + same_body.conditionally_enforce_true(cs, should_enforce); + + let previous_item_is_not_rollback = previous_item.rollback.negated(cs); + + // decide if we should add the PREVIOUS into the queue + // We add only if previous one is not trivial, and current one doesn't rollback it due to different timestamp, + // OR if current one is trivial + + let maybe_add_to_queue = may_be_different_log.or(cs, is_trivial); + + let add_to_the_queue = Boolean::multi_and( + cs, + &[ + previous_is_non_trivial, + maybe_add_to_queue, + previous_item_is_not_rollback, + ], + ); + let boolean_false = Boolean::allocated_constant(cs, false); + // cleanup some fields that are not useful + let query_to_add = LogQuery { + address: previous_item.address, + key: previous_item.key, + read_value: UInt256::zero(cs), + written_value: previous_item.written_value, + rw_flag: boolean_false, + aux_byte: UInt8::zero(cs), + rollback: boolean_false, + is_service: previous_item.is_service, + shard_id: previous_item.shard_id, + tx_number_in_block: previous_item.tx_number_in_block, + timestamp: UInt32::zero(cs), + }; + + result_queue.push(cs, query_to_add, add_to_the_queue); + + previous_is_trivial = is_trivial; + previous_item = sorted_item; + previous_key = sorting_key; + } + } + + // finalization step - same way, check if last item is not a rollback + { + let now_empty = unsorted_queue.is_empty(cs); + + let previous_is_non_trivial = previous_is_trivial.negated(cs); + let previous_item_is_not_rollback = previous_item.rollback.negated(cs); + let add_to_the_queue = Boolean::multi_and( + cs, + &[ + previous_is_non_trivial, + previous_item_is_not_rollback, + now_empty, + ], + ); + let boolean_false = Boolean::allocated_constant(cs, false); + let query_to_add = LogQuery { + address: previous_item.address, + key: previous_item.key, + read_value: UInt256::zero(cs), + written_value: previous_item.written_value, + rw_flag: boolean_false, + aux_byte: UInt8::zero(cs), + rollback: boolean_false, + is_service: previous_item.is_service, + shard_id: previous_item.shard_id, + tx_number_in_block: previous_item.tx_number_in_block, + timestamp: UInt32::zero(cs), + }; + + result_queue.push(cs, query_to_add, add_to_the_queue); + } + + unsorted_queue.enforce_consistency(cs); + intermediate_sorted_queue.enforce_consistency(cs); + + (lhs, rhs, previous_key, previous_item) +} + +/// Check that a == b and a > b by performing a long subtraction b - a with borrow. +/// Both a and b are considered as least significant word first +#[track_caller] +pub fn prepacked_long_comparison>( + cs: &mut CS, + a: &[Num], + b: &[Num], + width_data: &[usize], +) -> (Boolean, Boolean) { + assert_eq!(a.len(), b.len()); + assert_eq!(a.len(), width_data.len()); + + let mut previous_borrow = Boolean::allocated_constant(cs, false); + let mut limbs_are_equal = vec![]; + for (a, b) in a.iter().zip(b.iter()) { + let a_uint32 = unsafe { UInt32::from_variable_unchecked(a.get_variable()) }; + let b_uint32 = unsafe { UInt32::from_variable_unchecked(b.get_variable()) }; + let (diff, borrow) = a_uint32.overflowing_sub_with_borrow_in(cs, b_uint32, previous_borrow); + let equal = diff.is_zero(cs); + limbs_are_equal.push(equal); + previous_borrow = borrow; + } + let final_borrow = previous_borrow; + let eq = Boolean::multi_and(cs, &limbs_are_equal); + + (eq, final_borrow) +} + +#[cfg(test)] +mod tests { + use super::*; + use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; + use boojum::cs::implementations::reference_cs::CSDevelopmentAssembly; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::tables::*; + use boojum::gadgets::traits::allocatable::CSPlaceholder; + use boojum::gadgets::u160::UInt160; + use boojum::gadgets::u256::UInt256; + use boojum::gadgets::u8::UInt8; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::worker::Worker; + use ethereum_types::{Address, U256}; + type F = GoldilocksField; + type P = GoldilocksField; + + #[test] + fn test_repack_and_prove_events_rollbacks_inner() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + use boojum::cs::cs_builder::*; + + 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 = 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 = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 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(()); + + // add tables + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let cs = &mut owned_cs; + + let execute = Boolean::allocated_constant(cs, true); + let mut original_queue = StorageLogQueue::::empty(cs); + let unsorted_input = witness_input_unsorted(cs); + for el in unsorted_input { + original_queue.push(cs, el, execute); + } + let mut sorted_queue = StorageLogQueue::::empty(cs); + let sorted_input = witness_input_sorted(cs); + for el in sorted_input { + sorted_queue.push(cs, el, execute); + } + + let mut result_queue = StorageLogQueue::empty(cs); + + let lhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let rhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let is_start = Boolean::allocated_constant(cs, true); + let round_function = Poseidon2Goldilocks; + let fs_challenges = crate::utils::produce_fs_challenges::< + F, + _, + Poseidon2Goldilocks, + QUEUE_STATE_WIDTH, + { LOG_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + original_queue.into_state().tail, + sorted_queue.into_state().tail, + &round_function, + ); + let limit = 16; + let previous_key = UInt32::allocated_constant(cs, 0); + let previous_item = LogQuery::placeholder(cs); + repack_and_prove_events_rollbacks_inner( + cs, + lhs, + rhs, + &mut original_queue, + &mut sorted_queue, + &mut result_queue, + is_start, + fs_challenges, + previous_key, + previous_item, + limit, + ); + + cs.pad_and_shrink(); + let worker = Worker::new(); + let mut owned_cs = owned_cs.into_assembly(); + owned_cs.print_gate_stats(); + assert!(owned_cs.check_if_satisfied(&worker)); + } + + fn witness_input_unsorted>(cs: &mut CS) -> Vec> { + let mut unsorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + let zero_8 = UInt8::allocated_constant(cs, 0); + let one_8 = UInt8::allocated_constant(cs, 1); + let zero_32 = UInt32::allocated_constant(cs, 0); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("962072674308").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("32776").unwrap()), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_true, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9441), + }; + + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "26331131646299181274004581916076390273434308111684230560370784413089286382145", + ) + .unwrap(), + ), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("32769").unwrap()), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9597), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "39698723498166066574330386068075452510013183019908537087846976369872031173837", + ) + .unwrap(), + ), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("32").unwrap()), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9677), + }; + unsorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("154").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "34572686050049115524117736286529744084162467349680365734578449291092091566196", + ) + .unwrap(), + ), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9725), + }; + unsorted_querie.push(q); + + unsorted_querie + } + fn witness_input_sorted>(cs: &mut CS) -> Vec> { + let mut sorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + let zero_8 = UInt8::allocated_constant(cs, 0); + let one_8 = UInt8::allocated_constant(cs, 1); + let zero_32 = UInt32::allocated_constant(cs, 0); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("962072674308").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("32776").unwrap()), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_true, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9441), + }; + + sorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "26331131646299181274004581916076390273434308111684230560370784413089286382145", + ) + .unwrap(), + ), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("32769").unwrap()), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9597), + }; + sorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "39698723498166066574330386068075452510013183019908537087846976369872031173837", + ) + .unwrap(), + ), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("32").unwrap()), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9677), + }; + sorted_querie.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32781)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("154").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "34572686050049115524117736286529744084162467349680365734578449291092091566196", + ) + .unwrap(), + ), + rw_flag: bool_true, + aux_byte: one_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 9725), + }; + sorted_querie.push(q); + + sorted_querie + } +} diff --git a/crates/zkevm_circuits/src/main_vm/cycle.rs b/crates/zkevm_circuits/src/main_vm/cycle.rs new file mode 100644 index 00000000..1e70c5b4 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/cycle.rs @@ -0,0 +1,966 @@ +use arrayvec::ArrayVec; + +use super::opcodes::context::apply_context; +use super::pre_state::{create_prestate, PendingSponge}; +use super::state_diffs::{ + StateDiffsAccumulator, MAX_ADD_SUB_RELATIONS_PER_CYCLE, MAX_MUL_DIV_RELATIONS_PER_CYCLE, +}; +use super::*; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::CSGeometry; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +use crate::base_structures::decommit_query::DecommitQuery; +use crate::base_structures::log_query::LogQuery; +use crate::base_structures::memory_query::{self, MemoryQuery}; +use crate::base_structures::register::VMRegister; +use crate::base_structures::vm_state::callstack::Callstack; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; +use crate::base_structures::vm_state::{ArithmeticFlagsPort, GlobalContext}; +use crate::base_structures::vm_state::{VmLocalState, FULL_SPONGE_QUEUE_STATE_WIDTH}; +use crate::main_vm::opcodes::*; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use boojum::cs::traits::cs::DstBuffer; +use boojum::gadgets::u256::UInt256; + +pub(crate) fn vm_cycle< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + current_state: VmLocalState, + witness_oracle: &SynchronizedWitnessOracle, + global_context: &GlobalContext, + round_function: &R, +) -> VmLocalState +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + // first we create a pre-state + + if crate::config::CIRCUIT_VERSOBE { + println!("------------------------------------------------------------"); + println!("Start of new cycle"); + // synchronization point + let _current_state = current_state.witness_hook(&*cs)().unwrap(); + // dbg!(_current_state); + dbg!(_current_state.pending_exception); + dbg!(_current_state.callstack.current_context.saved_context.pc); + dbg!(_current_state.flags); + } + + let (draft_next_state, common_opcode_state, opcode_carry_parts) = + create_prestate(cs, current_state, witness_oracle, round_function); + + if crate::config::CIRCUIT_VERSOBE { + // synchronization point + let _common_opcode_state = common_opcode_state.witness_hook(&*cs)().unwrap(); + dbg!(_common_opcode_state.src0); + dbg!(_common_opcode_state.src1); + } + + // then we apply each opcode and accumulate state diffs + + let mut diffs_accumulator = StateDiffsAccumulator::::default(); + + apply_nop( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_add_sub( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_jump( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_binop( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_context( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_ptr( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_log( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + witness_oracle, + round_function, + ); + apply_calls_and_ret( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + witness_oracle, + global_context, + round_function, + ); + apply_mul_div( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_shifts( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + ); + apply_uma( + cs, + &draft_next_state, + &common_opcode_state, + &opcode_carry_parts, + &mut diffs_accumulator, + witness_oracle, + round_function, + ); + + // and finally apply state diffs + + let mut new_state = draft_next_state; + + let mut write_dst0_bools = ArrayVec::, 8>::new(); + for el in diffs_accumulator.dst_0_values.iter() { + if el.0 { + write_dst0_bools.push(el.1); + } + } + // potentially we can have registers that update DST0 as memory location, + // so we choose only cases where it's indeed into memory. + // It is only a possibility for now. Later we will predicate it based on the + // decoded opcode properties + let dst0_update_potentially_to_memory = Boolean::multi_or(cs, &write_dst0_bools); + + // select dst0 and dst1 values + + let mut can_update_dst0_as_register_only = ArrayVec::, 8>::new(); + let mut should_update_dst1 = ArrayVec::, 8>::new(); + + // for DST0 it's possible to have opcode-constrainted updates only into registers + for el in diffs_accumulator.dst_0_values.iter() { + if el.0 == false { + can_update_dst0_as_register_only.push(el.1); + } + } + for el in diffs_accumulator.dst_1_values.iter() { + should_update_dst1.push(el.0); + } + + let can_update_dst0_as_register_only = Boolean::multi_or(cs, &can_update_dst0_as_register_only); + + let dst0_is_ptr_candidates_iter = + diffs_accumulator + .dst_0_values + .iter() + .map(|el: &(bool, Boolean, VMRegister)| { + (el.1.get_variable(), el.2.is_pointer.get_variable()) + }); + let num_candidates_len = dst0_is_ptr_candidates_iter.len(); + + // Safety: we know by orthogonality of opcodes that boolean selectors in our iterators form either a mask, + // or an empty mask. So we can use unchecked casts below. Even if none of the bits is set (like in NOP case), + // it's not a problem because in the same situation we will not have an update of register/memory anyway + + use boojum::gadgets::num::dot_product; + let dst0_is_ptr = dot_product(cs, dst0_is_ptr_candidates_iter, num_candidates_len); + let dst0_is_ptr = unsafe { Boolean::from_variable_unchecked(dst0_is_ptr) }; + + let mut dst0_value = UInt256::zero(cs); + for (idx, dst) in dst0_value.inner.iter_mut().enumerate() { + let src = + diffs_accumulator + .dst_0_values + .iter() + .map(|el: &(bool, Boolean, VMRegister)| { + (el.1.get_variable(), el.2.value.inner[idx].get_variable()) + }); + + let limb = dot_product(cs, src, num_candidates_len); + + let limb = unsafe { UInt32::from_variable_unchecked(limb) }; + *dst = limb; + } + + let dst1_is_ptr_candidates_iter = diffs_accumulator + .dst_1_values + .iter() + .map(|el| (el.0.get_variable(), el.1.is_pointer.get_variable())); + let num_candidates_len = dst1_is_ptr_candidates_iter.len(); + + let dst1_is_ptr = dot_product(cs, dst1_is_ptr_candidates_iter, num_candidates_len); + let dst1_is_ptr = unsafe { Boolean::from_variable_unchecked(dst1_is_ptr) }; + + let mut dst1_value = UInt256::zero(cs); + for (idx, dst) in dst1_value.inner.iter_mut().enumerate() { + let src = diffs_accumulator + .dst_1_values + .iter() + .map(|el: &(Boolean, VMRegister)| { + (el.0.get_variable(), el.1.value.inner[idx].get_variable()) + }); + + let limb = dot_product(cs, src, num_candidates_len); + + let limb = unsafe { UInt32::from_variable_unchecked(limb) }; + *dst = limb; + } + + let perform_dst0_memory_write_update = Boolean::multi_and( + cs, + &[ + opcode_carry_parts.dst0_performs_memory_access, + dst0_update_potentially_to_memory, + ], + ); + + // We know that UMA opcodes (currently by design) are not allowed to write dst argument into memory + // in any form, so if we do the write here we always base on the state of memory from prestate + + let memory_queue_tail_for_dst0_write = draft_next_state.memory_queue_state; + let memory_queue_length_for_dst0_write = draft_next_state.memory_queue_length; + + let dst0 = VMRegister { + is_pointer: dst0_is_ptr, + value: dst0_value, + }; + + let ( + (dst0_write_initial_state_to_enforce, dst0_write_final_state_to_enforce), + new_memory_queue_tail, + new_memory_queue_len, + ) = may_be_write_memory( + cs, + &perform_dst0_memory_write_update, + &dst0, + &common_opcode_state.timestamp_for_dst_write, + &opcode_carry_parts.dst0_memory_location, + &memory_queue_tail_for_dst0_write, + &memory_queue_length_for_dst0_write, + witness_oracle, + round_function, + ); + + // update tail in next state candidate + new_state.memory_queue_state = new_memory_queue_tail; + new_state.memory_queue_length = new_memory_queue_len; + + // if dst0 is not in memory then update + + let boolean_false = Boolean::allocated_constant(cs, false); + let _boolean_true = Boolean::allocated_constant(cs, true); + let zero_u256 = UInt256::zero(cs); + + // do at once for dst0 and dst1 + + // case when we want to update DST0 from potentially memory-writing opcodes, + // but we address register in fact + + let dst0_performs_reg_update = opcode_carry_parts.dst0_performs_memory_access.negated(cs); + let t = Boolean::multi_and( + cs, + &[dst0_performs_reg_update, dst0_update_potentially_to_memory], + ); + + let dst0_update_register = Boolean::multi_or(cs, &[can_update_dst0_as_register_only, t]); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(dst0_value.witness_hook(&*cs)().unwrap()); + dbg!(dst1_value.witness_hook(&*cs)().unwrap()); + } + + // We should update registers, and the only "exotic" case is if someone tries to put + // dst0 and dst1 in the same location. + + // Note that "register" is a "wide" structure, so it doesn't benefit too much from + // multiselect, and we can just do a sequence, that + // we update each register as being: + // - dst0 in some operation + // - dst1 in some operation + // - special update in some operation + + // outer cycle is over ALL REGISTERS + for (idx, (flag_dst0, flag_dst1)) in common_opcode_state.decoded_opcode.dst_regs_selectors[0] + .iter() + .zip(common_opcode_state.decoded_opcode.dst_regs_selectors[1].iter()) + .enumerate() + { + // form an iterator for all possible candidates + let write_as_dst0 = Boolean::multi_and(cs, &[dst0_update_register, *flag_dst0]); + // dst1 is always register + let write_as_dst1 = *flag_dst1; + + // unfortunately we can not use iter chaining here due to syntax constraint + let mut apply_ptr_update_as_dst0 = ArrayVec::, 32>::new(); + let mut apply_ptr_update_as_dst1 = ArrayVec::, 32>::new(); + let mut it_is_ptr_as_dst0 = ArrayVec::<(Boolean, Boolean), 32>::new(); + let mut it_is_ptr_as_dst1 = ArrayVec::<(Boolean, Boolean), 32>::new(); + let mut it_value_as_dst0 = ArrayVec::<(Boolean, UInt256), 32>::new(); + let mut it_value_as_dst1 = ArrayVec::<(Boolean, UInt256), 32>::new(); + + apply_ptr_update_as_dst0.push(write_as_dst0); + apply_ptr_update_as_dst1.push(write_as_dst1); + + it_is_ptr_as_dst0.push((write_as_dst0, dst0_is_ptr)); + it_value_as_dst0.push((write_as_dst0, dst0_value)); + + it_is_ptr_as_dst1.push((write_as_dst1, dst1_is_ptr)); + it_value_as_dst1.push((write_as_dst1, dst1_value)); + + // then chain all specific register updates. Opcodes that produce specific updates do not make non-specific register updates, + // so we just place them along with dst0 + for specific_update in diffs_accumulator.specific_registers_updates[idx].drain(..) { + apply_ptr_update_as_dst0.push(specific_update.0); + it_is_ptr_as_dst0.push((specific_update.0, specific_update.1.is_pointer)); + it_value_as_dst0.push((specific_update.0, specific_update.1.value)); + } + + // chain removal of pointer markers at once. Same, can be placed into dst0 + let mut tmp = ArrayVec::, 16>::new(); + for remove_ptr_request in diffs_accumulator.remove_ptr_on_specific_registers[idx].drain(..) + { + tmp.push(remove_ptr_request); + } + + if tmp.is_empty() == false { + let remove_ptr_marker = Boolean::multi_or(cs, &tmp); + apply_ptr_update_as_dst0.push(remove_ptr_marker); + it_is_ptr_as_dst0.push((remove_ptr_marker, boolean_false)); + } + + // chain zeroing at once. Same, can be placed into dst0 + let mut tmp = ArrayVec::, 16>::new(); + for zeroing_requests in diffs_accumulator.specific_registers_zeroing[idx].drain(..) { + tmp.push(zeroing_requests); + } + + if tmp.is_empty() == false { + let zero_out_reg = Boolean::multi_or(cs, &tmp); + it_value_as_dst0.push((zero_out_reg, zero_u256)); + } + + let any_ptr_update_as_dst0 = Boolean::multi_or(cs, &apply_ptr_update_as_dst0); + let any_ptr_update_as_dst1 = Boolean::multi_or(cs, &apply_ptr_update_as_dst1); + + // Safety: our update flags are preconditioned by the applicability of the opcodes, and if opcode + // updates specific registers it does NOT write using "normal" dst0/dst1 addressing, so our mask + // is indeed a bitmask or empty + + // as dst0 + let num_candidates = it_is_ptr_as_dst0.len(); + let is_ptr_as_dst0 = dot_product( + cs, + it_is_ptr_as_dst0 + .into_iter() + .map(|el| (el.0.get_variable(), el.1.get_variable())), + num_candidates, + ); + let is_ptr_as_dst0 = unsafe { Boolean::from_variable_unchecked(is_ptr_as_dst0) }; + + new_state.registers[idx].is_pointer = Boolean::conditionally_select( + cs, + any_ptr_update_as_dst0, + &is_ptr_as_dst0, + &new_state.registers[idx].is_pointer, + ); + + // now as dst1 + let num_candidates = it_is_ptr_as_dst1.len(); + let is_ptr_as_dst1 = dot_product( + cs, + it_is_ptr_as_dst1 + .into_iter() + .map(|el| (el.0.get_variable(), el.1.get_variable())), + num_candidates, + ); + let is_ptr_as_dst1 = unsafe { Boolean::from_variable_unchecked(is_ptr_as_dst1) }; + new_state.registers[idx].is_pointer = Boolean::conditionally_select( + cs, + any_ptr_update_as_dst1, + &is_ptr_as_dst1, + &new_state.registers[idx].is_pointer, + ); + + // for registers we just use parallel select, that has the same efficiency as multiselect, + // because internally it's [UInt32; 8] + + for (flag, value) in it_value_as_dst0 + .into_iter() + .chain(it_value_as_dst1.into_iter()) + { + new_state.registers[idx].value = + UInt256::conditionally_select(cs, flag, &value, &new_state.registers[idx].value); + } + } + + // apply smaller changes to VM state, such as ergs left, etc + + // PC + for (flag, value) in diffs_accumulator.new_pc_candidates.drain(..) { + new_state.callstack.current_context.saved_context.pc = UInt16::conditionally_select( + cs, + flag, + &value, + &new_state.callstack.current_context.saved_context.pc, + ); + } + + // Ergs + for (flag, value) in diffs_accumulator.new_ergs_left_candidates.drain(..) { + new_state + .callstack + .current_context + .saved_context + .ergs_remaining = UInt32::conditionally_select( + cs, + flag, + &value, + &new_state + .callstack + .current_context + .saved_context + .ergs_remaining, + ); + } + + // Ergs per pubdata + for (flag, value) in diffs_accumulator.new_ergs_per_pubdata.into_iter() { + new_state.ergs_per_pubdata_byte = + UInt32::conditionally_select(cs, flag, &value, &new_state.ergs_per_pubdata_byte); + } + + // Tx number in block + for (flag, value) in diffs_accumulator.new_tx_number.into_iter() { + new_state.tx_number_in_block = + UInt32::conditionally_select(cs, flag, &value, &new_state.tx_number_in_block); + } + + // Page counter + new_state.memory_page_counter = diffs_accumulator.memory_page_counters.expect("is some"); + + // Context value + for (flag, value) in diffs_accumulator.context_u128_candidates.drain(..) { + new_state.context_composite_u128 = + UInt32::parallel_select(cs, flag, &value, &new_state.context_composite_u128); + } + + // Heap limit + for (flag, value) in diffs_accumulator.new_heap_bounds.drain(..) { + new_state + .callstack + .current_context + .saved_context + .heap_upper_bound = UInt32::conditionally_select( + cs, + flag, + &value, + &new_state + .callstack + .current_context + .saved_context + .heap_upper_bound, + ); + } + + // Axu heap limit + for (flag, value) in diffs_accumulator.new_aux_heap_bounds.drain(..) { + new_state + .callstack + .current_context + .saved_context + .aux_heap_upper_bound = UInt32::conditionally_select( + cs, + flag, + &value, + &new_state + .callstack + .current_context + .saved_context + .aux_heap_upper_bound, + ); + } + + // variable queue states + + // Memory due to UMA + for (flag, length, state) in diffs_accumulator.memory_queue_candidates.into_iter() { + new_state.memory_queue_length = + UInt32::conditionally_select(cs, flag, &length, &new_state.memory_queue_length); + + new_state.memory_queue_state = + Num::parallel_select(cs, flag, &state, &new_state.memory_queue_state); + } + + // decommittment due to far call + for (flag, length, state) in diffs_accumulator.decommitment_queue_candidates.into_iter() { + new_state.code_decommittment_queue_length = UInt32::conditionally_select( + cs, + flag, + &length, + &new_state.code_decommittment_queue_length, + ); + + new_state.code_decommittment_queue_state = + Num::parallel_select(cs, flag, &state, &new_state.code_decommittment_queue_state); + } + + // forward storage log + for (flag, length, state) in diffs_accumulator.log_queue_forward_candidates.into_iter() { + new_state + .callstack + .current_context + .log_queue_forward_part_length = UInt32::conditionally_select( + cs, + flag, + &length, + &new_state + .callstack + .current_context + .log_queue_forward_part_length, + ); + + new_state.callstack.current_context.log_queue_forward_tail = Num::parallel_select( + cs, + flag, + &state, + &new_state.callstack.current_context.log_queue_forward_tail, + ); + } + + // rollback log head(!) + for (flag, length, state) in diffs_accumulator.log_queue_rollback_candidates.into_iter() { + new_state + .callstack + .current_context + .saved_context + .reverted_queue_segment_len = UInt32::conditionally_select( + cs, + flag, + &length, + &new_state + .callstack + .current_context + .saved_context + .reverted_queue_segment_len, + ); + + new_state + .callstack + .current_context + .saved_context + .reverted_queue_head = Num::parallel_select( + cs, + flag, + &state, + &new_state + .callstack + .current_context + .saved_context + .reverted_queue_head, + ); + } + + // flags + for (flag, flags) in diffs_accumulator.flags.iter() { + new_state.flags = + ArithmeticFlagsPort::conditionally_select(cs, *flag, flags, &new_state.flags); + } + + // and now we either replace or not the callstack in full + for (flag, callstack) in diffs_accumulator.callstacks.into_iter() { + new_state.callstack = + Callstack::conditionally_select(cs, flag, &callstack, &new_state.callstack); + } + + // other state parts + let new_pending_exception = Boolean::multi_or(cs, &diffs_accumulator.pending_exceptions); + new_state.pending_exception = new_pending_exception; + + // conditional u32 range checks. All of those are of the fixed length per opcode, so we just select + { + let (_, mut to_enforce) = diffs_accumulator + .u32_conditional_range_checks + .pop() + .unwrap(); + for (applies, candidate) in diffs_accumulator.u32_conditional_range_checks.drain(..) { + to_enforce = UInt32::parallel_select(cs, applies, &candidate, &to_enforce); + } + + let _ = to_enforce.map(|el| UInt32::from_variable_checked(cs, el.get_variable())); + } + + // add/sub relations + let cap = diffs_accumulator.add_sub_relations.len(); + for _ in 0..MAX_ADD_SUB_RELATIONS_PER_CYCLE { + let mut relations = Vec::with_capacity(cap); + for (flag, values) in diffs_accumulator.add_sub_relations.iter_mut() { + if let Some(el) = values.pop() { + relations.push((*flag, el)); + } + } + + if let Some((_, selected)) = relations.pop() { + let mut selected = selected; + for (flag, el) in relations.into_iter() { + selected = AddSubRelation::conditionally_select(cs, flag, &el, &selected); + } + + enforce_addition_relation(cs, selected); + } + } + + let cap = diffs_accumulator.mul_div_relations.len(); + for _ in 0..MAX_MUL_DIV_RELATIONS_PER_CYCLE { + let mut relations = Vec::with_capacity(cap); + for (flag, values) in diffs_accumulator.mul_div_relations.iter_mut() { + if let Some(el) = values.pop() { + relations.push((*flag, el)); + } + } + + if let Some((_, selected)) = relations.pop() { + let mut selected = selected; + for (flag, el) in relations.into_iter() { + selected = MulDivRelation::conditionally_select(cs, flag, &el, &selected); + } + + enforce_mul_relation(cs, selected); + } + } + + // now we can enforce sponges. There are only 2 outcomes + // - we have dst0 write (and may be src0 read), that we taken care above + // - opcode itself modified memory queue, based on outcome of src0 read + // in parallel opcodes either + // - do not use sponges and only rely on src0/dst0 + // - can not have src0/dst0 in memory, but use sponges (UMA, near_call, far call, ret) + + let src0_read_state_pending_sponge = opcode_carry_parts.src0_read_sponge_data; + let dst0_write_state_pending_sponge = PendingSponge { + initial_state: dst0_write_initial_state_to_enforce, + final_state: dst0_write_final_state_to_enforce, + should_enforce: perform_dst0_memory_write_update, + }; + + let mut first_sponge_candidate = src0_read_state_pending_sponge; + for (can_use_sponge_for_src0, can_use_sponge_for_dst0, opcode_applies, sponge_data) in + diffs_accumulator.sponge_candidates_to_run.iter_mut() + { + assert!(*can_use_sponge_for_src0 == false); + assert!(*can_use_sponge_for_dst0 == false); + + if let Some((should_enforce, initial_state, final_state)) = sponge_data.pop() { + // we can conditionally select + let formal_sponge = PendingSponge { + initial_state: initial_state, + final_state: final_state, + should_enforce: should_enforce, + }; + + first_sponge_candidate = Selectable::conditionally_select( + cs, + *opcode_applies, + &formal_sponge, + &first_sponge_candidate, + ); + } + } + + let mut second_sponge_candidate = dst0_write_state_pending_sponge; + for (can_use_sponge_for_src0, can_use_sponge_for_dst0, opcode_applies, sponge_data) in + diffs_accumulator.sponge_candidates_to_run.iter_mut() + { + assert!(*can_use_sponge_for_src0 == false); + assert!(*can_use_sponge_for_dst0 == false); + + if let Some((should_enforce, initial_state, final_state)) = sponge_data.pop() { + // we can conditionally select + let formal_sponge = PendingSponge { + initial_state: initial_state, + final_state: final_state, + should_enforce: should_enforce, + }; + + second_sponge_candidate = Selectable::conditionally_select( + cs, + *opcode_applies, + &formal_sponge, + &second_sponge_candidate, + ); + } + } + + use super::state_diffs::MAX_SPONGES_PER_CYCLE; + let mut selected_sponges_to_enforce = ArrayVec::<_, MAX_SPONGES_PER_CYCLE>::new(); + selected_sponges_to_enforce.push(first_sponge_candidate); + selected_sponges_to_enforce.push(second_sponge_candidate); + + for _ in 2..MAX_SPONGES_PER_CYCLE { + let mut selected = None; + for (_, _, opcode_applies, sponge_data) in + diffs_accumulator.sponge_candidates_to_run.iter_mut() + { + if let Some((should_enforce, initial_state, final_state)) = sponge_data.pop() { + if let Some(selected) = selected.as_mut() { + // we can conditionally select + let formal_sponge = PendingSponge { + initial_state: initial_state, + final_state: final_state, + should_enforce: should_enforce, + }; + + *selected = Selectable::conditionally_select( + cs, + *opcode_applies, + &formal_sponge, + &*selected, + ); + } else { + let should_enforce = Boolean::multi_and(cs, &[should_enforce, *opcode_applies]); + let formal_sponge = PendingSponge { + initial_state: initial_state, + final_state: final_state, + should_enforce: should_enforce, + }; + selected = Some(formal_sponge); + } + } + } + + let selected = selected.expect("non-trivial sponge"); + selected_sponges_to_enforce.push(selected); + } + + // ensure that we selected everything + for (_, _, _, sponge_data) in diffs_accumulator.sponge_candidates_to_run.iter_mut() { + assert!(sponge_data.is_empty()); + } + assert_eq!(selected_sponges_to_enforce.len(), MAX_SPONGES_PER_CYCLE); + + // dbg!(new_state.memory_queue_state.witness_hook(&*cs)().unwrap()); + // dbg!(new_state.memory_queue_length.witness_hook(&*cs)().unwrap()); + + // actually enforce_sponges + + enforce_sponges(cs, &selected_sponges_to_enforce, round_function); + + if crate::config::CIRCUIT_VERSOBE { + // synchronization point + let _wit = new_state.witness_hook(&*cs)().unwrap(); + // dbg!(_wit.memory_queue_state); + // dbg!(_wit.memory_queue_length); + println!("End of cycle"); + } + + new_state +} + +use crate::main_vm::pre_state::MemoryLocation; + +fn may_be_write_memory< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + should_write_dst0: &Boolean, + dst0_value: &VMRegister, + timestamp: &UInt32, + location: &MemoryLocation, + current_memory_sponge_tail: &[Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + current_memory_sponge_length: &UInt32, + witness_oracle: &SynchronizedWitnessOracle, + _round_function: &R, +) -> ( + ( + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + UInt32, +) +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + if crate::config::CIRCUIT_VERSOBE { + if should_write_dst0.witness_hook(&*cs)().unwrap() { + println!("Will write DST0 to memory"); + dbg!(location.witness_hook(&*cs)().unwrap()); + dbg!(dst0_value.witness_hook(&*cs)().unwrap()); + } + } + + let MemoryLocation { page, index } = location; + let boolean_true = Boolean::allocated_constant(cs, true); + + let query = MemoryQuery { + timestamp: *timestamp, + memory_page: *page, + index: *index, + is_ptr: dst0_value.is_pointer, + value: dst0_value.value, + rw_flag: boolean_true, + }; + + use boojum::gadgets::traits::encodable::CircuitEncodable; + let packed_query = query.encode(cs); + + // absorb by replacement + + use boojum::gadgets::queue::full_state_queue::simulate_new_tail_for_full_state_queue; + + use crate::base_structures::memory_query::MEMORY_QUERY_PACKED_WIDTH; + + let simulated_values = simulate_new_tail_for_full_state_queue::< + F, + 8, + FULL_SPONGE_QUEUE_STATE_WIDTH, + 4, + MEMORY_QUERY_PACKED_WIDTH, + R, + _, + >( + cs, + packed_query, + current_memory_sponge_tail.map(|el| el.get_variable()), + *should_write_dst0, + ); + + // create absorbed initial state + + let initial_state = [ + Num::from_variable(packed_query[0]), + Num::from_variable(packed_query[1]), + Num::from_variable(packed_query[2]), + Num::from_variable(packed_query[3]), + Num::from_variable(packed_query[4]), + Num::from_variable(packed_query[5]), + Num::from_variable(packed_query[6]), + Num::from_variable(packed_query[7]), + current_memory_sponge_tail[8], + current_memory_sponge_tail[9], + current_memory_sponge_tail[10], + current_memory_sponge_tail[11], + ]; + + let simulated_final_state = simulated_values.map(|el| Num::from_variable(el)); + + // for all reasonable execution traces it's fine + let new_len_candidate = unsafe { current_memory_sponge_length.increment_unchecked(cs) }; + + let new_length = UInt32::conditionally_select( + cs, + *should_write_dst0, + &new_len_candidate, + ¤t_memory_sponge_length, + ); + + let final_state = Num::parallel_select( + cs, + *should_write_dst0, + &simulated_final_state, + current_memory_sponge_tail, + ); + + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = + Vec::with_capacity( as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1); + dependencies.push(should_write_dst0.get_variable().into()); + dependencies.extend(Place::from_variables(query.flatten_as_variables())); + + cs.set_values_with_dependencies_vararg( + &dependencies, + &[], + move |inputs: &[F], _buffer: &mut DstBuffer<'_, '_, F>| { + let execute = >::cast_from_source(inputs[0]); + + use crate::main_vm::cycle::memory_query::MemoryQueryWitness; + + let mut query = [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + query.copy_from_slice(&inputs[1..]); + let query: MemoryQueryWitness = CSAllocatableExt::witness_from_set_of_values(query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + guard.push_memory_witness(&query, execute); + drop(guard); + }, + ); + + ( + (initial_state, simulated_final_state), + final_state, + new_length, + ) +} + +fn enforce_sponges< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + candidates: &[PendingSponge], + _round_function: &R, +) { + for el in candidates.iter() { + let PendingSponge { + initial_state, + final_state, + should_enforce, + } = el; + let true_final = R::compute_round_function_over_nums(cs, *initial_state); + for (a, b) in true_final.iter().zip(final_state.iter()) { + Num::conditionally_enforce_equal(cs, *should_enforce, a, b); + } + } +} + +pub const fn reference_vm_geometry() -> CSGeometry { + CSGeometry { + num_columns_under_copy_permutation: 140, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 8, + } +} diff --git a/crates/zkevm_circuits/src/main_vm/decoded_opcode.rs b/crates/zkevm_circuits/src/main_vm/decoded_opcode.rs new file mode 100644 index 00000000..13093d61 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/decoded_opcode.rs @@ -0,0 +1,576 @@ +use super::*; +use boojum::cs::gates::{ConstantAllocatableCS, ReductionByPowersGate, ReductionGate}; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::{Place, Variable}; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::impls::limbs_decompose::reduce_terms; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::serde_utils::BigArraySerde; +use boojum::{field::SmallField, gadgets::u16::UInt16}; +use cs_derive::*; + +use zkevm_opcode_defs::{ + OPCODE_INPUT_VARIANT_FLAGS, OPCODE_OUTPUT_VARIANT_FLAGS, OPCODE_TYPE_BITS, REGISTERS_COUNT, +}; + +pub const NUM_SRC_REGISTERS: usize = 2; +pub const NUM_DST_REGISTERS: usize = 2; +pub const REGISTER_ENCODING_BITS: usize = 4; + +use super::opcode_bitmask::OpcodeBitmask; +use super::opcode_bitmask::TOTAL_OPCODE_MEANINGFULL_DESCRIPTION_BITS; + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Debug)] +pub struct OpcodePropertiesDecoding { + pub properties_bits: OpcodeBitmask, + pub src_regs_selectors: [[Boolean; REGISTERS_COUNT]; NUM_SRC_REGISTERS], + pub dst_regs_selectors: [[Boolean; REGISTERS_COUNT]; NUM_DST_REGISTERS], + pub imm0: UInt16, + pub imm1: UInt16, +} + +/// we assume that +/// - we did read the opcode either from memory, or have skipped opcode, or something else +/// - if we should have skipped cycle then we did it already +/// Now we need to decide either to mask into exception or into NOP, or execute +pub fn perform_initial_decoding>( + cs: &mut CS, + raw_opcode: [UInt32; 2], + encoded_flags: Num, + is_kernel_mode: Boolean, + is_static_context: Boolean, + callstack_is_full: Boolean, + ergs_left: UInt32, + did_skip_cycle: Boolean, +) -> (OpcodePropertiesDecoding, UInt32) { + // decode and resolve condition immediatelly + // If we will later on mask into PANIC then we will just ignore resolved condition + let initial_decoding = + partially_decode_from_integer_and_resolve_condition(cs, raw_opcode, encoded_flags); + + let (opcode_boolean_spread_data, aux_bools) = + split_out_aux_bits(cs, initial_decoding.opcode_boolean_spread_data); + let condition_if_not_masked_later = initial_decoding.condition; + + // resolve fast exceptions + // - out of ergs + // - kernel mode + // - writes in static context + // - callstack is full + + // set ergs cost to 0 if we are skipping cycle + let masked_ergs_cost = initial_decoding.ergs_cost.mask_negated(cs, did_skip_cycle); + + if crate::config::CIRCUIT_VERSOBE { + println!( + "Have {} ergs left, opcode cost is {}", + ergs_left.witness_hook(&*cs)().unwrap(), + masked_ergs_cost.witness_hook(&*cs)().unwrap(), + ); + } + + let (ergs_left, out_of_ergs_exception) = ergs_left.overflowing_sub(cs, masked_ergs_cost); + let ergs_left = ergs_left.mask_negated(cs, out_of_ergs_exception); // it's 0 if we underflow + + let requires_kernel_mode = aux_bools[zkevm_opcode_defs::KERNER_MODE_FLAG_IDX]; + let can_be_used_in_static_context = + aux_bools[zkevm_opcode_defs::CAN_BE_USED_IN_STATIC_CONTEXT_FLAG_IDX]; + let explicit_panic = aux_bools[zkevm_opcode_defs::EXPLICIT_PANIC_FLAG_IDX]; + + let normal_mode = is_kernel_mode.negated(cs); + let kernel_mode_exception = Boolean::multi_and(cs, &[requires_kernel_mode, normal_mode]); + let opcode_can_not_be_used_in_static_context = can_be_used_in_static_context.negated(cs); + let write_in_static_exception = Boolean::multi_and( + cs, + &[is_static_context, opcode_can_not_be_used_in_static_context], + ); + + let any_exception = Boolean::multi_or( + cs, + &[ + explicit_panic, + out_of_ergs_exception, + kernel_mode_exception, + write_in_static_exception, + callstack_is_full, + ], + ); + + // if we do have an exception then we have mask properties into PANIC + let mask_into_panic = any_exception; + if crate::config::CIRCUIT_VERSOBE { + if mask_into_panic.witness_hook(&*cs)().unwrap() { + println!("Masking into PANIC in decoding phase"); + dbg!([ + explicit_panic, + out_of_ergs_exception, + kernel_mode_exception, + write_in_static_exception, + callstack_is_full, + ] + .witness_hook(&*cs)() + .unwrap()); + } + } + let panic_encoding = *zkevm_opcode_defs::PANIC_BITSPREAD_U64; + + // mask out aux bits (those are 0, but do it just in case) + let panic_encoding = panic_encoding & OPCODE_PROPS_BITMASK_FOR_BITSPREAD_ENCODING; + + let panic_encoding = F::from_u64(panic_encoding).expect("fits into field"); + let panic_encoding = Num::allocated_constant(cs, panic_encoding); + let opcode_boolean_spread_data = Num::conditionally_select( + cs, + mask_into_panic, + &panic_encoding, + &opcode_boolean_spread_data, + ); + + let no_panic = mask_into_panic.negated(cs); + let condition_is_not_fulfilled = condition_if_not_masked_later.negated(cs); + + // then if we didn't mask into panic and condition was false then mask into NOP + let mask_into_nop = Boolean::multi_and(cs, &[no_panic, condition_is_not_fulfilled]); + if crate::config::CIRCUIT_VERSOBE { + if mask_into_nop.witness_hook(&*cs)().unwrap() { + println!("Masking into NOP in decoding phase"); + } + } + + let nop_encoding = *zkevm_opcode_defs::NOP_BITSPREAD_U64; + // mask out aux bits (those are 0, but do it just in case) + let nop_encoding = nop_encoding & OPCODE_PROPS_BITMASK_FOR_BITSPREAD_ENCODING; + let nop_encoding = F::from_u64(nop_encoding).expect("fits into field"); + let nop_encoding = Num::allocated_constant(cs, nop_encoding); + + let opcode_boolean_spread_data = Num::conditionally_select( + cs, + mask_into_nop, + &nop_encoding, + &opcode_boolean_spread_data, + ); + + let mask_any = Boolean::multi_or(cs, &[mask_into_nop, mask_into_panic]); + + // Ok, now just decompose spreads into bitmasks, and spread and decompose register indexes + + let all_opcodes_props_bits = + opcode_boolean_spread_data + .spread_into_bits::<_, TOTAL_OPCODE_MEANINGFULL_DESCRIPTION_BITS>(cs); + + let src_regs_encoding = initial_decoding + .src_regs_encoding + .mask_negated(cs, mask_any); + let dst_regs_encoding = initial_decoding + .dst_regs_encoding + .mask_negated(cs, mask_any); + + // split encodings into 4 bit chunks unchecked + + let [src0_encoding, src1_encoding] = split_register_encoding_byte(cs, src_regs_encoding); + let [dst0_encoding, dst1_encoding] = split_register_encoding_byte(cs, dst_regs_encoding); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(&src0_encoding.witness_hook(&*cs)().unwrap()); + dbg!(&src1_encoding.witness_hook(&*cs)().unwrap()); + + dbg!(&dst0_encoding.witness_hook(&*cs)().unwrap()); + dbg!(&dst1_encoding.witness_hook(&*cs)().unwrap()); + } + + // and enforce their bit length by table access, and simultaneously get + // bitmasks for selection + + // for every register we first need to spread integer index -> bitmask as integer, and then transform integer bitmask into individual bits + + let src0_mask = reg_idx_into_bitspread(cs, src0_encoding); + let src0_bitspread = src0_mask.spread_into_bits::<_, REGISTERS_COUNT>(cs); + + let src1_mask = reg_idx_into_bitspread(cs, src1_encoding); + let src1_bitspread = src1_mask.spread_into_bits::<_, REGISTERS_COUNT>(cs); + + let dst0_mask = reg_idx_into_bitspread(cs, dst0_encoding); + let dst0_bitspread = dst0_mask.spread_into_bits::<_, REGISTERS_COUNT>(cs); + + let dst1_mask = reg_idx_into_bitspread(cs, dst1_encoding); + let dst1_bitspread = dst1_mask.spread_into_bits::<_, REGISTERS_COUNT>(cs); + + let imm0 = initial_decoding.imm0; + let imm1 = initial_decoding.imm1; + + // place everything into struct + + let opcode_props = OpcodeBitmask::from_full_mask(all_opcodes_props_bits); + + let new = OpcodePropertiesDecoding { + properties_bits: opcode_props, + src_regs_selectors: [src0_bitspread, src1_bitspread], + dst_regs_selectors: [dst0_bitspread, dst1_bitspread], + imm0, + imm1, + }; + + (new, ergs_left) +} + +// for integer N returns a field element with value 0 if N is zero, and 1 << (N-1) otherwise +pub fn reg_idx_into_bitspread>( + cs: &mut CS, + integer: Num, +) -> Num { + use crate::tables::integer_to_boolean_mask::RegisterIndexToBitmaskTable; + + let table_id = cs + .get_table_id_for_marker::() + .expect("table must be added before"); + + let vals = cs.perform_lookup::<1, 2>(table_id, &[integer.get_variable()]); + let bitspread = vals[0]; + + Num::from_variable(bitspread) +} + +use zkevm_opcode_defs::{ + CONDITIONAL_BITS_SHIFT, OPCODES_TABLE_WIDTH, VARIANT_AND_CONDITION_ENCODING_BITS, +}; + +use crate::bit_width_to_bitmask; + +pub const VARIANT_AND_CONDITION_ENCODING_MASK: u64 = + bit_width_to_bitmask(VARIANT_AND_CONDITION_ENCODING_BITS); +pub const VARIANT_ENCODING_MASK: u64 = bit_width_to_bitmask(OPCODES_TABLE_WIDTH); +pub const OPCODE_PROPS_BITMASK_FOR_BITSPREAD_ENCODING: u64 = + bit_width_to_bitmask(TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED); + +use zkevm_opcode_defs::TOTAL_AUX_BITS; + +use super::opcode_bitmask::{ + OPCODE_FLAGS_BITS, OPCODE_VARIANT_BITS, TOTAL_OPCODE_DESCRIPTION_AND_AUX_BITS, + TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED, +}; + +const CONDITION_ENCODING_BITS: usize = 3; + +const UNUSED_GAP: usize = + VARIANT_AND_CONDITION_ENCODING_BITS - OPCODES_TABLE_WIDTH - CONDITION_ENCODING_BITS; + +pub const NUM_BITS_BEFORE_AUX_INFORMATION: usize = OPCODE_TYPE_BITS + + OPCODE_VARIANT_BITS + + OPCODE_FLAGS_BITS + + OPCODE_INPUT_VARIANT_FLAGS + + OPCODE_OUTPUT_VARIANT_FLAGS; + +use crate::base_structures::vm_state::ArithmeticFlagsPort; + +pub(crate) fn encode_flags>( + cs: &mut CS, + flags: &ArithmeticFlagsPort, +) -> Num { + if cs.gate_is_allowed::>() { + let zero = Num::allocated_constant(cs, F::ZERO); + let inputs = [ + flags.overflow_or_less_than.get_variable(), + flags.equal.get_variable(), + flags.greater_than.get_variable(), + zero.get_variable(), + ]; + let result = reduce_terms(cs, F::TWO, inputs); + + Num::from_variable(result) + } else if cs.gate_is_allowed::>() { + let zero = Num::allocated_constant(cs, F::ZERO); + let inputs = [ + flags.overflow_or_less_than.get_variable(), + flags.equal.get_variable(), + flags.greater_than.get_variable(), + zero.get_variable(), + ]; + let contants = [F::ONE, F::TWO, F::from_u64_unchecked(4), F::ZERO]; + let result = ReductionGate::::reduce_terms(cs, contants, inputs); + + Num::from_variable(result) + } else { + unimplemented!() + } +} + +pub struct OpcodePreliminaryDecoding { + pub condition: Boolean, + pub opcode_boolean_spread_data: Num, // this has both flags that describe the opcode itself, and aux flags for EH + pub src_regs_encoding: UInt8, + pub dst_regs_encoding: UInt8, + pub imm0: UInt16, + pub imm1: UInt16, + pub ergs_cost: UInt32, +} + +pub fn split_out_aux_bits>( + cs: &mut CS, + opcode_boolean_spread_data: Num, +) -> (Num, [Boolean; TOTAL_AUX_BITS]) { + assert!(TOTAL_OPCODE_DESCRIPTION_AND_AUX_BITS <= 64); + assert!(TOTAL_OPCODE_DESCRIPTION_AND_AUX_BITS <= F::CAPACITY_BITS); + + let main_props_var = cs.alloc_variable_without_value(); + let extra_props_vars = cs.alloc_multiple_variables_without_values::(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let mut all_outputs = [Variable::placeholder(); TOTAL_AUX_BITS + 1]; + all_outputs[0] = main_props_var; + all_outputs[1..].copy_from_slice(&extra_props_vars); + + let value_fn = move |inputs: [F; 1]| { + let witness = inputs[0].as_u64_reduced(); + let mut result = [F::ZERO; TOTAL_AUX_BITS + 1]; + + let props = witness & OPCODE_PROPS_BITMASK_FOR_BITSPREAD_ENCODING; // bits without AUX flag + + result[0] = F::from_u64(props).expect("must fit into field"); + + assert!(OPCODE_PROPS_BITMASK_FOR_BITSPREAD_ENCODING < u64::MAX); + let aux_bits_as_u64 = witness >> TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED; + let mut aux_bits_as_u64 = aux_bits_as_u64 as u64; + for idx in 0..TOTAL_AUX_BITS { + let bit = (aux_bits_as_u64 & 1u64) == 1; + result[idx + 1] = if bit { F::ONE } else { F::ZERO }; + + aux_bits_as_u64 >>= 1; + } + debug_assert!(aux_bits_as_u64 == 0); + + result + }; + + let dependencies = [opcode_boolean_spread_data.get_variable().into()]; + + cs.set_values_with_dependencies( + &dependencies, + &Place::from_variables(all_outputs), + value_fn, + ); + } + + let main_props = Num::from_variable(main_props_var); + let extra_props_bits = extra_props_vars.map(|el| Boolean::from_variable_checked(cs, el)); + + // we should enforce bit length because we just did the splitting + let _ = main_props.constraint_bit_length_as_bytes(cs, TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED); + + // now just make a combination to prove equality + + let to_enforce = [ + (main_props_var, F::ONE), + ( + extra_props_vars[0], + F::from_u64_unchecked(1u64 << (TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED)), + ), + ( + extra_props_vars[1], + F::from_u64_unchecked(1u64 << (TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED + 1)), + ), + ( + extra_props_vars[2], + F::from_u64_unchecked(1u64 << (TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED + 2)), + ), + (opcode_boolean_spread_data.get_variable(), F::MINUS_ONE), + ]; + + Num::enforce_zero_for_linear_combination(cs, &to_enforce); + + (main_props, extra_props_bits) +} + +/// Decodes only necessary parts of the opcode to resolve condition +/// for masking into NOP if opcode does nothing. +/// We also output imm0/imm1 parts that will NOT be ever masked, +/// and register index encoding parts too that would be masked into 0. +/// Please remember that we mask only bitspread part after condition is resolved, and we do not need +/// to recompute the cost(!) +pub fn partially_decode_from_integer_and_resolve_condition< + F: SmallField, + CS: ConstraintSystem, +>( + cs: &mut CS, + opcode_properties_words: [UInt32; 2], + encoded_flags: Num, +) -> OpcodePreliminaryDecoding { + // we need into total 4 elements: + // - 11 bits that encode opcode + variant + addressing mode + etc + // - 2x1 unused bits + // - conditional 3bit integer (constrainted later by lookup table) + + let word_0_bytes = opcode_properties_words[0].decompose_into_bytes(cs); + + let opcode_variant_and_conditional_word = + UInt16::from_le_bytes(cs, [word_0_bytes[0], word_0_bytes[1]]); + + let variant_var = cs.alloc_variable_without_value(); + let unused_bits_vars = cs.alloc_multiple_variables_without_values::<2>(); + let conditionals_var = cs.alloc_variable_without_value(); + + // booleanity constraints + let _unused_bits = unused_bits_vars.map(|el| Boolean::from_variable_checked(cs, el)); + + if ::WitnessConfig::EVALUATE_WITNESS { + let all_outputs = [ + variant_var, + unused_bits_vars[0], + unused_bits_vars[1], + conditionals_var, + ]; + + let value_fn = move |inputs: [F; 1]| { + debug_assert_eq!(VARIANT_AND_CONDITION_ENCODING_BITS, 16); + + let variant_and_condition = >::cast_from_source(inputs[0]); + let variant_and_condition = variant_and_condition as u64; + let variant_and_condition = variant_and_condition & VARIANT_AND_CONDITION_ENCODING_MASK; + + let variant = variant_and_condition & VARIANT_ENCODING_MASK; + + let unused_bits = + (variant_and_condition >> OPCODES_TABLE_WIDTH) & bit_width_to_bitmask(UNUSED_GAP); + let unused_bit_0 = unused_bits & 1 > 0; + let unused_bit_1 = (unused_bits >> 1) & 1 > 0; + let condition = variant_and_condition >> CONDITIONAL_BITS_SHIFT; + + if crate::config::CIRCUIT_VERSOBE { + let opcode = zkevm_opcode_defs::OPCODES_TABLE[variant as usize]; + dbg!(opcode); + let condition = zkevm_opcode_defs::condition::Condition::materialize_variant( + condition as usize, + ); + dbg!(condition); + } + + [ + F::from_u64_unchecked(variant), + F::from_u64_unchecked(unused_bit_0 as u64), + F::from_u64_unchecked(unused_bit_1 as u64), + F::from_u64_unchecked(condition), + ] + }; + + let dependencies = [opcode_variant_and_conditional_word.get_variable().into()]; + + cs.set_values_with_dependencies( + &dependencies, + &Place::from_variables(all_outputs), + value_fn, + ); + } + + // enforce our claimed decomposition + Num::enforce_zero_for_linear_combination( + cs, + &[ + (variant_var, F::ONE), + (unused_bits_vars[0], F::SHIFTS[OPCODES_TABLE_WIDTH]), + (unused_bits_vars[1], F::SHIFTS[OPCODES_TABLE_WIDTH + 1]), + (conditionals_var, F::SHIFTS[OPCODES_TABLE_WIDTH + 2]), + ( + opcode_variant_and_conditional_word.get_variable(), + F::MINUS_ONE, + ), + ], + ); + + // range check parts by feeding into the tables + + use crate::tables::opcodes_decoding::VMOpcodeDecodingTable; + let table_id = cs + .get_table_id_for_marker::() + .expect("table must exist"); + + // bit check variant and spread it + let values = cs.perform_lookup::<1, 2>(table_id, &[variant_var]); + // by our definition of the table we check the prices to fit into u32 + let opcode_cost = unsafe { UInt32::from_variable_unchecked(values[0]) }; + let opcode_properties = Num::from_variable(values[1]); + + // condition is checked to be 3 bits through resolution here + use crate::tables::conditional::VMConditionalResolutionTable; + let table_id = cs + .get_table_id_for_marker::() + .expect("table must exist"); + + let values = + cs.perform_lookup::<2, 1>(table_id, &[conditionals_var, encoded_flags.get_variable()]); + let resolution = unsafe { Boolean::from_variable_unchecked(values[0]) }; + + // decode the end + let src_regs_encoding = word_0_bytes[2]; + let dst_regs_encoding = word_0_bytes[3]; + + let word_1_bytes = opcode_properties_words[1].decompose_into_bytes(cs); + + let imm0 = UInt16::from_le_bytes(cs, [word_1_bytes[0], word_1_bytes[1]]); + let imm1 = UInt16::from_le_bytes(cs, [word_1_bytes[2], word_1_bytes[3]]); + + let props = OpcodePreliminaryDecoding { + condition: resolution, + opcode_boolean_spread_data: opcode_properties, + src_regs_encoding, + dst_regs_encoding, + imm0, + imm1, + ergs_cost: opcode_cost, + }; + + props +} + +fn split_register_encoding_byte>( + cs: &mut CS, + encoding: UInt8, +) -> [Num; 2] { + // we only need one FMA gate, so we write the routine manually + + let outputs = cs.alloc_multiple_variables_without_values::<2>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + const MASK: u64 = (1u64 << REGISTER_ENCODING_BITS) - 1; + let value_fn = move |inputs: [F; 1]| { + let mut as_u64 = inputs[0].as_u64(); + let src0 = as_u64 & MASK; + as_u64 >>= REGISTER_ENCODING_BITS; + debug_assert!(as_u64 <= MASK); + let src1 = as_u64; + + [F::from_u64_unchecked(src0), F::from_u64_unchecked(src1)] + }; + + let dependencies = Place::from_variables([encoding.get_variable()]); + + cs.set_values_with_dependencies(&dependencies, &Place::from_variables(outputs), value_fn); + } + + if ::SetupConfig::KEEP_SETUP { + use boojum::cs::gates::FmaGateInBaseFieldWithoutConstant; + + if cs.gate_is_allowed::>() { + let one = cs.allocate_constant(F::ONE); + let mut gate = FmaGateInBaseFieldWithoutConstant::empty(); + gate.quadratic_part = (one, outputs[0]); + gate.linear_part = outputs[1]; + use boojum::cs::gates::fma_gate_without_constant::FmaGateInBaseWithoutConstantParams; + gate.params = FmaGateInBaseWithoutConstantParams { + coeff_for_quadtaric_part: F::ONE, + linear_term_coeff: F::from_u64_unchecked(1u64 << REGISTER_ENCODING_BITS), + }; + gate.rhs_part = encoding.get_variable(); + + gate.add_to_cs(cs); + } else { + unimplemented!() + } + } + + outputs.map(|el| Num::from_variable(el)) +} diff --git a/crates/zkevm_circuits/src/main_vm/loading.rs b/crates/zkevm_circuits/src/main_vm/loading.rs new file mode 100644 index 00000000..7b4d0cb3 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/loading.rs @@ -0,0 +1,226 @@ +use super::*; +use crate::base_structures::register::VMRegister; +use crate::base_structures::vm_state::callstack::Callstack; +use crate::base_structures::vm_state::callstack::FullExecutionContext; +use crate::base_structures::vm_state::{ + VmLocalState, FULL_SPONGE_QUEUE_STATE_WIDTH, QUEUE_STATE_WIDTH, +}; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::u160::UInt160; +use boojum::gadgets::u256::{decompose_u256_as_u32x8, UInt256}; + +pub fn initial_bootloader_state< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + memory_queue_initial_length: UInt32, + memory_queue_initial_tail: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + decommitment_queue_initial_length: UInt32, + decommitment_queue_initial_tail: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + initial_rollback_queue_value: [Num; QUEUE_STATE_WIDTH], + _round_function: &R, +) -> VmLocalState { + // first create the context + let mut ctx = FullExecutionContext::uninitialized(cs); + + ctx.saved_context.base_page = + UInt32::allocated_constant(cs, zkevm_opcode_defs::BOOTLOADER_BASE_PAGE); + ctx.saved_context.code_page = + UInt32::allocated_constant(cs, zkevm_opcode_defs::BOOTLOADER_CODE_PAGE); + + let zero_num = Num::zero(cs); + let zero_u32 = UInt32::zero(cs); + let zero_u16 = UInt16::zero(cs); + let _boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + + ctx.saved_context.pc = zero_u16; + ctx.saved_context.exception_handler_loc = UInt16::allocated_constant( + cs, + zkevm_opcode_defs::system_params::INITIAL_FRAME_FORMAL_EH_LOCATION, + ); + ctx.saved_context.ergs_remaining = + UInt32::allocated_constant(cs, zkevm_opcode_defs::system_params::VM_INITIAL_FRAME_ERGS); + + let formal_bootloader_address_low = UInt32::allocated_constant( + cs, + zkevm_opcode_defs::system_params::BOOTLOADER_FORMAL_ADDRESS_LOW as u32, + ); + + let formal_bootloader_address = UInt160 { + inner: [ + formal_bootloader_address_low, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + ], + }; + + ctx.saved_context.code_address = formal_bootloader_address; + ctx.saved_context.this = formal_bootloader_address; + ctx.saved_context.caller = UInt160::zero(cs); // is called from nowhere + + // circuit specific bit + ctx.saved_context.reverted_queue_tail = initial_rollback_queue_value; + ctx.saved_context.reverted_queue_head = ctx.saved_context.reverted_queue_tail; + + // mark as kernel + ctx.saved_context.is_kernel_mode = boolean_true; + + // bootloader should not pay for resizes + ctx.saved_context.heap_upper_bound = + UInt32::allocated_constant(cs, zkevm_opcode_defs::system_params::BOOTLOADER_MAX_MEMORY); + ctx.saved_context.aux_heap_upper_bound = + UInt32::allocated_constant(cs, zkevm_opcode_defs::system_params::BOOTLOADER_MAX_MEMORY); + + // now push that to the callstack, manually + + let mut empty_entry = FullExecutionContext::uninitialized(cs); + empty_entry.saved_context.reverted_queue_tail = initial_rollback_queue_value; + empty_entry.saved_context.reverted_queue_head = ctx.saved_context.reverted_queue_tail; + empty_entry.saved_context.is_kernel_mode = boolean_true; + + use boojum::gadgets::traits::encodable::CircuitEncodable; + let empty_entry_encoding = empty_entry.saved_context.encode(cs); // only saved part + + let callstack_empty_state = [zero_num; FULL_SPONGE_QUEUE_STATE_WIDTH]; + + let mut current_state = callstack_empty_state.map(|el| el.get_variable()); + + // absorb by replacement + let round_0_initial = [ + empty_entry_encoding[0], + empty_entry_encoding[1], + empty_entry_encoding[2], + empty_entry_encoding[3], + empty_entry_encoding[4], + empty_entry_encoding[5], + empty_entry_encoding[6], + empty_entry_encoding[7], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_0_final = R::compute_round_function(cs, round_0_initial); + + current_state = round_0_final; + + let round_1_initial = [ + empty_entry_encoding[8], + empty_entry_encoding[9], + empty_entry_encoding[10], + empty_entry_encoding[11], + empty_entry_encoding[12], + empty_entry_encoding[13], + empty_entry_encoding[14], + empty_entry_encoding[15], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_1_final = R::compute_round_function(cs, round_1_initial); + + current_state = round_1_final; + + let round_2_initial = [ + empty_entry_encoding[16], + empty_entry_encoding[17], + empty_entry_encoding[18], + empty_entry_encoding[19], + empty_entry_encoding[20], + empty_entry_encoding[21], + empty_entry_encoding[22], + empty_entry_encoding[23], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_2_final = R::compute_round_function(cs, round_2_initial); + + current_state = round_2_final; + + let round_3_initial = [ + empty_entry_encoding[24], + empty_entry_encoding[25], + empty_entry_encoding[26], + empty_entry_encoding[27], + empty_entry_encoding[28], + empty_entry_encoding[29], + empty_entry_encoding[30], + empty_entry_encoding[31], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_3_final = R::compute_round_function(cs, round_3_initial); + + current_state = round_3_final; + + let callstack_initial_state = current_state.map(|el| Num::from_variable(el)); + + let callstack_depth = UInt32::allocated_constant(cs, 1u32); + + let callstack = Callstack { + current_context: ctx, + context_stack_depth: callstack_depth, + stack_sponge_state: callstack_initial_state, + }; + + let mut bootloaded_state = VmLocalState::uninitialized(cs); + // memory + bootloaded_state.memory_queue_length = memory_queue_initial_length; + bootloaded_state.memory_queue_state = memory_queue_initial_tail; + // code decommittments + bootloaded_state.code_decommittment_queue_length = decommitment_queue_initial_length; + bootloaded_state.code_decommittment_queue_state = decommitment_queue_initial_tail; + // rest + bootloaded_state.callstack = callstack; + // timestamp and global counters + bootloaded_state.timestamp = + UInt32::allocated_constant(cs, zkevm_opcode_defs::STARTING_TIMESTAMP); + bootloaded_state.memory_page_counter = + UInt32::allocated_constant(cs, zkevm_opcode_defs::STARTING_BASE_PAGE); + + // we also FORMALLY mark r1 as "pointer" type, even though we will NOT have any calldata + // Nevertheless we put it "formally" to make an empty slice to designated page + + let formal_ptr = zkevm_opcode_defs::FatPointer { + offset: 0, + memory_page: zkevm_opcode_defs::BOOTLOADER_CALLDATA_PAGE, + start: 0, + length: 0, + }; + let formal_ptr_encoding = formal_ptr.to_u256(); + + let decomposition = decompose_u256_as_u32x8(formal_ptr_encoding); + let l0 = UInt32::allocated_constant(cs, decomposition[0]); + let l1 = UInt32::allocated_constant(cs, decomposition[1]); + let l2 = UInt32::allocated_constant(cs, decomposition[2]); + let l3 = UInt32::allocated_constant(cs, decomposition[3]); + + debug_assert_eq!(decomposition[4], 0); + debug_assert_eq!(decomposition[5], 0); + debug_assert_eq!(decomposition[6], 0); + debug_assert_eq!(decomposition[7], 0); + + bootloaded_state.registers[0] = VMRegister { + is_pointer: boolean_true, + value: UInt256 { + inner: [l0, l1, l2, l3, zero_u32, zero_u32, zero_u32, zero_u32], + }, + }; + + bootloaded_state +} diff --git a/crates/zkevm_circuits/src/main_vm/mod.rs b/crates/zkevm_circuits/src/main_vm/mod.rs new file mode 100644 index 00000000..3be2bc2f --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/mod.rs @@ -0,0 +1,232 @@ +use super::*; + +use boojum::cs::gates::PublicInputGate; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::cs::{Place, Variable}; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::queue::{QueueState, QueueTailState}; +use boojum::gadgets::traits::castable::WitnessCastable; + +use crate::base_structures::vm_state::VmLocalState; +use boojum::config::*; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::{field::SmallField, gadgets::u16::UInt16}; + +pub mod cycle; +pub mod decoded_opcode; +pub mod loading; +pub mod opcode_bitmask; +pub mod opcodes; +pub mod pre_state; +pub mod register_input_view; +pub mod state_diffs; +pub mod utils; +pub mod witness_oracle; + +use crate::base_structures::decommit_query::DecommitQuery; +use crate::base_structures::log_query::LogQuery; +use crate::base_structures::memory_query::MemoryQuery; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; +use crate::base_structures::vm_state::{FULL_SPONGE_QUEUE_STATE_WIDTH, QUEUE_STATE_WIDTH}; +use crate::fsm_input_output::circuit_inputs::main_vm::VmCircuitWitness; +use crate::fsm_input_output::circuit_inputs::main_vm::*; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::fsm_input_output::commit_variable_length_encodable_item; +use crate::fsm_input_output::ClosedFormInputCompactForm; +use crate::main_vm::cycle::vm_cycle; +use crate::main_vm::loading::initial_bootloader_state; +use crate::main_vm::witness_oracle::{SynchronizedWitnessOracle, WitnessOracle}; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::gadgets::traits::allocatable::{CSAllocatableExt, CSPlaceholder}; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::traits::witnessable::WitnessHookable; + +pub fn main_vm_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + witness: VmCircuitWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let VmCircuitWitness { + closed_form_input, + witness_oracle, + } = witness; + + let mut structured_input = + VmCircuitInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + + let start_flag = structured_input.start_flag; + let observable_input = structured_input.observable_input.clone(); + let hidden_fsm_input = structured_input.hidden_fsm_input.clone(); + + let VmInputData { + rollback_queue_tail_for_block, + memory_queue_initial_state, + decommitment_queue_initial_state, + per_block_context, + } = observable_input; + + // we also need to create the state that reflects the "initial" state for boot process + + let bootloader_state = initial_bootloader_state( + cs, + memory_queue_initial_state.length, + memory_queue_initial_state.tail, + decommitment_queue_initial_state.length, + decommitment_queue_initial_state.tail, + rollback_queue_tail_for_block, + round_function, + ); + + // or may be it's from FSM, so select + let mut state = + VmLocalState::conditionally_select(cs, start_flag, &bootloader_state, &hidden_fsm_input); + + let synchronized_oracle = SynchronizedWitnessOracle::new(witness_oracle); + + // we run `limit` of "normal" cycles + for _cycle_idx in 0..limit { + state = vm_cycle( + cs, + state, + &synchronized_oracle, + &per_block_context, + round_function, + ); + } + + // here we have too large state to run self-tests, so we will compare it only against the full committments + + // check for "done" flag + let done = state.callstack.is_empty(cs); + + // we can not fail exiting, so check for our convention that pc == 0 on success, and != 0 in failure + let bootloader_exited_successfully = + state.callstack.current_context.saved_context.pc.is_zero(cs); + + // bootloader must exist succesfully + bootloader_exited_successfully.conditionally_enforce_true(cs, done); + + structured_input.completion_flag = done; + + let final_state = state; + + let mut observable_output = VmOutputData::placeholder(cs); + + let full_empty_state_large = QueueState::::empty(cs); + + // select tails + + // memory + + let memory_queue_current_tail = QueueTailState { + tail: final_state.memory_queue_state, + length: final_state.memory_queue_length, + }; + let memory_queue_final_tail = QueueTailState::conditionally_select( + cs, + structured_input.completion_flag, + &memory_queue_current_tail, + &full_empty_state_large.tail, + ); + + // code decommit + let decommitment_queue_current_tail = QueueTailState { + tail: final_state.code_decommittment_queue_state, + length: final_state.code_decommittment_queue_length, + }; + let decommitment_queue_final_tail = QueueTailState::conditionally_select( + cs, + structured_input.completion_flag, + &decommitment_queue_current_tail, + &full_empty_state_large.tail, + ); + + // log. We IGNORE rollbacks that never happened obviously + let final_log_state_tail = final_state.callstack.current_context.log_queue_forward_tail; + let final_log_state_length = final_state + .callstack + .current_context + .log_queue_forward_part_length; + + // but we CAN still check that it's potentially mergeable, basically to check that witness generation is good + for (a, b) in final_log_state_tail.iter().zip( + final_state + .callstack + .current_context + .saved_context + .reverted_queue_head + .iter(), + ) { + Num::conditionally_enforce_equal(cs, structured_input.completion_flag, a, b); + } + + let full_empty_state_small = QueueState::::empty(cs); + + let log_queue_current_tail = QueueTailState { + tail: final_log_state_tail, + length: final_log_state_length, + }; + let log_queue_final_tail = QueueTailState::conditionally_select( + cs, + structured_input.completion_flag, + &log_queue_current_tail, + &full_empty_state_small.tail, + ); + + // set everything + + observable_output.log_queue_final_state.tail = log_queue_final_tail; + observable_output.memory_queue_final_state.tail = memory_queue_final_tail; + observable_output.decommitment_queue_final_state.tail = decommitment_queue_final_tail; + + structured_input.observable_output = observable_output; + structured_input.hidden_fsm_output = final_state; + + // if we generate witness then we can self-check + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |_ins: [F; 1]| { + let mut guard = synchronized_oracle.inner.write().expect("not poisoned"); + let consumed_witness = std::mem::replace(&mut *guard, W::default()); + drop(guard); + + consumed_witness.at_completion(); + + [] + }; + + cs.set_values_with_dependencies( + &[structured_input.completion_flag.get_variable().into()], + &[], + value_fn, + ); + } + + structured_input.hook_compare_witness(&*cs, &closed_form_input); + + let compact_form = + ClosedFormInputCompactForm::from_full_form(cs, &structured_input, round_function); + + let input_commitment: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + 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 +} diff --git a/crates/zkevm_circuits/src/main_vm/opcode_bitmask.rs b/crates/zkevm_circuits/src/main_vm/opcode_bitmask.rs new file mode 100644 index 00000000..f0398f3f --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcode_bitmask.rs @@ -0,0 +1,130 @@ +use super::*; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::u16::UInt16; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; + +use zkevm_opcode_defs::{ + ISAVersion, ImmMemHandlerFlags, OPCODE_INPUT_VARIANT_FLAGS, OPCODE_OUTPUT_VARIANT_FLAGS, + OPCODE_TYPE_BITS, TOTAL_AUX_BITS, +}; + +// opcode defs only provide runtime-computeable variable, so we have to pin ISA version and assert + +pub const SUPPORTED_ISA_VERSION: ISAVersion = ISAVersion(1); + +const _: () = if SUPPORTED_ISA_VERSION.0 != zkevm_opcode_defs::DEFAULT_ISA_VERSION.0 { + panic!() +} else { + () +}; + +pub(crate) const OPCODE_VARIANT_BITS: usize = 10; +pub(crate) const OPCODE_FLAGS_BITS: usize = 2; +pub(crate) const TOTAL_OPCODE_MEANINGFULL_DESCRIPTION_BITS: usize = 38; +pub(crate) const TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED: usize = 48; +pub(crate) const TOTAL_OPCODE_DESCRIPTION_AND_AUX_BITS: usize = + TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED + TOTAL_AUX_BITS; + +/// We hide all the source selection and updating in preprocessing, +/// so we only need imms and some variant properties +#[derive(Derivative)] +#[derivative(Debug)] +pub struct UsedOpcode { + pub properties_bitmask: OpcodeBitmask, + pub imm0: UInt16, + pub imm1: UInt16, +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Debug)] +pub struct OpcodeBitmask { + pub opcode_type_booleans: [Boolean; OPCODE_TYPE_BITS], + pub opcode_variant_booleans: [Boolean; OPCODE_VARIANT_BITS], + pub flag_booleans: [Boolean; OPCODE_FLAGS_BITS], + pub input_variant_booleans: [Boolean; OPCODE_INPUT_VARIANT_FLAGS], + pub output_variant_booleans: [Boolean; OPCODE_OUTPUT_VARIANT_FLAGS], +} + +use zkevm_opcode_defs::Opcode; + +impl OpcodeBitmask { + pub fn boolean_for_opcode(&self, opcode: Opcode) -> Boolean { + let opcode_idx = opcode.variant_idx(); + self.opcode_type_booleans[opcode_idx] + } + + pub fn boolean_for_variant(&self, opcode: Opcode) -> Boolean { + let variant_idx = opcode.materialize_subvariant_idx(); + self.opcode_variant_booleans[variant_idx] + } + + pub fn boolean_for_src_mem_access(&self, access_type: ImmMemHandlerFlags) -> Boolean { + let variant_idx = access_type.variant_index(); + self.input_variant_booleans[variant_idx] + } + + pub fn boolean_for_dst_mem_access(&self, access_type: ImmMemHandlerFlags) -> Boolean { + assert!(access_type.is_allowed_for_dst()); + let variant_idx = access_type.variant_index(); + self.output_variant_booleans[variant_idx] + } + + pub fn from_full_mask(mask: [Boolean; TOTAL_OPCODE_MEANINGFULL_DESCRIPTION_BITS]) -> Self { + // assert to not mismatch alignments + debug_assert_eq!( + OPCODE_VARIANT_BITS, + zkevm_opcode_defs::max_num_variants_for_version(SUPPORTED_ISA_VERSION) + ); + debug_assert_eq!( + OPCODE_FLAGS_BITS, + zkevm_opcode_defs::max_num_flags_for_version(SUPPORTED_ISA_VERSION) + ); + debug_assert_eq!( + TOTAL_OPCODE_DESCRIPTION_BITS_FLATTENED, + zkevm_opcode_defs::total_description_bits_rounded_for_version(SUPPORTED_ISA_VERSION) + ); + debug_assert_eq!( + TOTAL_OPCODE_MEANINGFULL_DESCRIPTION_BITS, + zkevm_opcode_defs::total_description_bits_for_version(SUPPORTED_ISA_VERSION) + ); + + let mut offset = 0; + let opcode_type_booleans: [Boolean; OPCODE_TYPE_BITS] = mask + [offset..(offset + OPCODE_TYPE_BITS)] + .try_into() + .unwrap(); + offset += OPCODE_TYPE_BITS; + let opcode_variant_booleans: [Boolean; OPCODE_VARIANT_BITS] = mask + [offset..(offset + OPCODE_VARIANT_BITS)] + .try_into() + .unwrap(); + offset += OPCODE_VARIANT_BITS; + let flag_booleans: [Boolean; OPCODE_FLAGS_BITS] = mask + [offset..(offset + OPCODE_FLAGS_BITS)] + .try_into() + .unwrap(); + offset += OPCODE_FLAGS_BITS; + let input_variant_booleans: [Boolean; OPCODE_INPUT_VARIANT_FLAGS] = mask + [offset..(offset + OPCODE_INPUT_VARIANT_FLAGS)] + .try_into() + .unwrap(); + offset += OPCODE_INPUT_VARIANT_FLAGS; + let output_variant_booleans: [Boolean; OPCODE_OUTPUT_VARIANT_FLAGS] = mask + [offset..(offset + OPCODE_OUTPUT_VARIANT_FLAGS)] + .try_into() + .unwrap(); + offset += OPCODE_OUTPUT_VARIANT_FLAGS; + debug_assert_eq!(offset, TOTAL_OPCODE_MEANINGFULL_DESCRIPTION_BITS); + + Self { + opcode_type_booleans, + opcode_variant_booleans, + flag_booleans, + input_variant_booleans, + output_variant_booleans, + } + } +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/add_sub.rs b/crates/zkevm_circuits/src/main_vm/opcodes/add_sub.rs new file mode 100644 index 00000000..4f745f9f --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/add_sub.rs @@ -0,0 +1,282 @@ +use arrayvec::ArrayVec; + +use crate::base_structures::{register::VMRegister, vm_state::ArithmeticFlagsPort}; +use boojum::gadgets::{traits::castable::WitnessCastable, u256::UInt256}; + +use super::*; + +pub(crate) fn apply_add_sub>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + // main point of merging add/sub is to enforce single add/sub relation, that doesn't leak into any + // other opcodes + + let (addition_result_unchecked, of_unchecked) = allocate_addition_result_unchecked( + cs, + &common_opcode_state.src0_view.u32x8_view, + &common_opcode_state.src1_view.u32x8_view, + ); + + let (subtraction_result_unchecked, uf_unchecked) = allocate_subtraction_result_unchecked( + cs, + &common_opcode_state.src0_view.u32x8_view, + &common_opcode_state.src1_view.u32x8_view, + ); + + const ADD_OPCODE: zkevm_opcode_defs::Opcode = Opcode::Add(AddOpcode::Add); + const SUB_OPCODE: zkevm_opcode_defs::Opcode = Opcode::Sub(SubOpcode::Sub); + + // now we need to properly select and enforce + let apply_add = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(ADD_OPCODE); + let apply_sub = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(SUB_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (apply_add.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying ADD"); + } + if (apply_sub.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying SUB"); + } + } + + let result = UInt32::::parallel_select( + cs, + apply_add, + &addition_result_unchecked, + &subtraction_result_unchecked, + ); + + // even though we will select for range check in final state diffs application, we already need a selection + // over result here, so we just add one conditional check + let conditional_range_checks = result; + + // now we need to enforce relation + // we enforce a + b = c + 2^N * of, + // so if we subtract, then we need to swap some staff + + // relation is a + b == c + of * 2^N, + // but we compute d - e + 2^N * borrow = f, + // so e + f = d + of * 2^N + + // Naive options + // let add_relation = AddSubRelation { + // a: common_opcode_state.src0_view.u32x8_view, + // b: common_opcode_state.src1_view.u32x8_view, + // c: addition_result_unchecked, + // of + // }; + + // let sub_relation = AddSubRelation { + // a: common_opcode_state.src1_view.u32x8_view, + // b: subtraction_result_unchecked, + // c: common_opcode_state.src0_view.u32x8_view, + // of: uf, + // }; + + // Instead we select non-common part, using the fact + // that it's summetric over a/b + + let new_a = common_opcode_state.src1_view.u32x8_view; + + let new_b = UInt32::::parallel_select( + cs, + apply_add, + &common_opcode_state.src0_view.u32x8_view, + &subtraction_result_unchecked, + ); + + let new_c = UInt32::::parallel_select( + cs, + apply_add, + &addition_result_unchecked, + &common_opcode_state.src0_view.u32x8_view, + ); + + let new_of = Boolean::conditionally_select(cs, apply_add, &of_unchecked, &uf_unchecked); + + let relation = AddSubRelation { + a: new_a, + b: new_b, + c: new_c, + of: new_of, + }; + + // now we need to check for zero and output + let limb_is_zero = result.map(|el| el.is_zero(cs)); + let result_is_zero = Boolean::multi_and(cs, &limb_is_zero); + + // gt = !of & !zero, so it's !(of || zero) + let gt = Boolean::multi_or(cs, &[new_of, result_is_zero]).negated(cs); + + let update_flags = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[SET_FLAGS_FLAG_IDX]; + + let candidate_flags = ArithmeticFlagsPort { + overflow_or_less_than: new_of, + equal: result_is_zero, + greater_than: gt, + }; + + // we only update flags and dst0 + + let apply_any = Boolean::multi_or(cs, &[apply_add, apply_sub]); + let boolean_false = Boolean::allocated_constant(cs, false); + let dst0 = VMRegister { + is_pointer: boolean_false, + value: UInt256 { inner: result }, + }; + + let can_write_into_memory = ADD_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + debug_assert_eq!( + can_write_into_memory, + SUB_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) + ); + + let update_flags = Boolean::multi_and(cs, &[apply_any, update_flags]); + + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, apply_any, dst0)); + diffs_accumulator + .flags + .push((update_flags, candidate_flags)); + + // add range check request + diffs_accumulator + .u32_conditional_range_checks + .push((apply_any, conditional_range_checks)); + + let mut add_sub_relations = ArrayVec::new(); + add_sub_relations.push(relation); + diffs_accumulator + .add_sub_relations + .push((apply_any, add_sub_relations)); +} + +pub fn allocate_addition_result_unchecked>( + cs: &mut CS, + a: &[UInt32; 8], + b: &[UInt32; 8], +) -> ([UInt32; 8], Boolean) { + let limbs = cs.alloc_multiple_variables_without_values::<8>(); + let of = cs.alloc_variable_without_value(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: [F; 16]| { + let mut of = false; + let mut result = [F::ZERO; 9]; + for (idx, (a, b)) in inputs[..8].iter().zip(inputs[8..].iter()).enumerate() { + let a = >::cast_from_source(*a); + let b = >::cast_from_source(*b); + let (c, new_of_0) = a.overflowing_add(b); + let (c, new_of_1) = c.overflowing_add(of as u32); + + of = new_of_0 || new_of_1; + + result[idx] = F::from_u64_unchecked(c as u64); + } + + result[8] = F::from_u64_unchecked(of as u64); + + result + }; + + let dependencies = Place::from_variables([ + a[0].get_variable(), + a[1].get_variable(), + a[2].get_variable(), + a[3].get_variable(), + a[4].get_variable(), + a[5].get_variable(), + a[6].get_variable(), + a[7].get_variable(), + b[0].get_variable(), + b[1].get_variable(), + b[2].get_variable(), + b[3].get_variable(), + b[4].get_variable(), + b[5].get_variable(), + b[6].get_variable(), + b[7].get_variable(), + ]); + let outputs = Place::from_variables([ + limbs[0], limbs[1], limbs[2], limbs[3], limbs[4], limbs[5], limbs[6], limbs[7], of, + ]); + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + let limbs = limbs.map(|el| unsafe { UInt32::from_variable_unchecked(el) }); + let of = unsafe { Boolean::from_variable_unchecked(of) }; + + (limbs, of) +} + +pub fn allocate_subtraction_result_unchecked>( + cs: &mut CS, + a: &[UInt32; 8], + b: &[UInt32; 8], +) -> ([UInt32; 8], Boolean) { + let limbs = cs.alloc_multiple_variables_without_values::<8>(); + let of = cs.alloc_variable_without_value(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: [F; 16]| { + let mut uf = false; + let mut result = [F::ZERO; 9]; + for (idx, (a, b)) in inputs[..8].iter().zip(inputs[8..].iter()).enumerate() { + let a = >::cast_from_source(*a); + let b = >::cast_from_source(*b); + let (c, new_uf_0) = (a).overflowing_sub(b); + let (c, new_uf_1) = c.overflowing_sub(uf as u32); + + uf = new_uf_0 || new_uf_1; + + result[idx] = F::from_u64_unchecked(c as u64); + } + + result[8] = F::from_u64_unchecked(uf as u64); + + result + }; + + let dependencies = Place::from_variables([ + a[0].get_variable(), + a[1].get_variable(), + a[2].get_variable(), + a[3].get_variable(), + a[4].get_variable(), + a[5].get_variable(), + a[6].get_variable(), + a[7].get_variable(), + b[0].get_variable(), + b[1].get_variable(), + b[2].get_variable(), + b[3].get_variable(), + b[4].get_variable(), + b[5].get_variable(), + b[6].get_variable(), + b[7].get_variable(), + ]); + let outputs = Place::from_variables([ + limbs[0], limbs[1], limbs[2], limbs[3], limbs[4], limbs[5], limbs[6], limbs[7], of, + ]); + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + let limbs = limbs.map(|el| unsafe { UInt32::from_variable_unchecked(el) }); + let of = unsafe { Boolean::from_variable_unchecked(of) }; + + (limbs, of) +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/binop.rs b/crates/zkevm_circuits/src/main_vm/opcodes/binop.rs new file mode 100644 index 00000000..b6012bf1 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/binop.rs @@ -0,0 +1,244 @@ +use reduction_by_powers_gate::ReductionByPowersGate; + +use boojum::{ + cs::gates::{ + reduction_by_powers_gate, ConstantAllocatableCS, ReductionGate, ReductionGateParams, + }, + gadgets::u256::UInt256, +}; + +use crate::base_structures::{register::VMRegister, vm_state::ArithmeticFlagsPort}; + +use super::*; + +pub(crate) fn apply_binop>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + const AND_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Binop(zkevm_opcode_defs::definitions::binop::BinopOpcode::And); + const OR_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Binop(zkevm_opcode_defs::definitions::binop::BinopOpcode::Or); + const XOR_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Binop(zkevm_opcode_defs::definitions::binop::BinopOpcode::Xor); + + let should_apply = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(AND_OPCODE); + + let should_set_flags = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[SET_FLAGS_FLAG_IDX]; + + let is_and = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(AND_OPCODE); + let is_or = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(OR_OPCODE); + let _is_xor = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(XOR_OPCODE); + // main point of merging add/sub is to enforce single add/sub relation, that doesn't leak into any + // other opcodes + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying BINOP"); + if is_and.witness_hook(&*cs)().unwrap_or(false) { + println!("BINOP AND"); + } + if is_or.witness_hook(&*cs)().unwrap_or(false) { + println!("BINOP OR"); + } + if _is_xor.witness_hook(&*cs)().unwrap_or(false) { + println!("BINOP XOR"); + } + } + } + + let (and_result, or_result, xor_result) = get_binop_subresults( + cs, + &common_opcode_state.src0_view.u8x32_view, + &common_opcode_state.src1_view.u8x32_view, + ); + + // now we need to select, so we first reduce, and then select + + let mut and_chunks = common_opcode_state.src0_view.u32x8_view; + let mut or_chunks = common_opcode_state.src0_view.u32x8_view; + let mut xor_chunks = common_opcode_state.src0_view.u32x8_view; + + for (dst, src) in [&mut and_chunks, &mut or_chunks, &mut xor_chunks] + .into_iter() + .zip([and_result, or_result, xor_result].into_iter()) + { + for (dst, src) in dst.iter_mut().zip(src.array_chunks::<4>()) { + *dst = UInt32::from_le_bytes(cs, *src); + } + } + + // now select + + let mut result = UInt32::parallel_select(cs, is_and, &and_chunks, &xor_chunks); + result = UInt32::parallel_select(cs, is_or, &or_chunks, &result); + + let limb_is_zero = result.map(|el| el.is_zero(cs)); + let result_is_zero = Boolean::multi_and(cs, &limb_is_zero); + + let constant_false = Boolean::allocated_constant(cs, false); + + let candidate_flags = ArithmeticFlagsPort { + overflow_or_less_than: constant_false, + equal: result_is_zero, + greater_than: constant_false, + }; + + // we only update flags and dst0 + + let dst0 = VMRegister { + is_pointer: Boolean::allocated_constant(cs, false), + value: UInt256 { inner: result }, + }; + let can_write_into_memory = AND_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, should_apply, dst0)); + + let update_flags = Boolean::multi_and(cs, &[should_apply, should_set_flags]); + + diffs_accumulator + .flags + .push((update_flags, candidate_flags)); +} + +fn get_binop_subresults>( + cs: &mut CS, + a: &[UInt8; 32], + b: &[UInt8; 32], +) -> ([UInt8; 32], [UInt8; 32], [UInt8; 32]) { + // we apply our composite table twice - one to get compound result, and another one as range checks + // and add alreabraic relation + use boojum::gadgets::tables::binop_table::BinopTable; + + let table_id = cs + .get_table_id_for_marker::() + .expect("table must exist"); + + let mut composite_result = [Variable::placeholder(); 32]; + for ((a, b), dst) in a.iter().zip(b.iter()).zip(composite_result.iter_mut()) { + let [result] = cs.perform_lookup::<2, 1>(table_id, &[a.get_variable(), b.get_variable()]); + *dst = result; + } + + // now we need to pull out individual parts. For that we decompose a value + // let value = (xor_result as u64) << 32 | (or_result as u64) << 16 | (and_result as u64); + + let all_results = cs.alloc_multiple_variables_without_values::<96>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: [F; 32]| { + let mut results = [F::ZERO; 96]; + + const MASK: u64 = (1u64 << 8) - 1; + + for (src, dst) in inputs.iter().zip(results.array_chunks_mut::<3>()) { + let mut src = src.as_u64_reduced(); + let and_result = src & MASK; + src >>= 16; + let or_result = src & MASK; + src >>= 16; + let xor_result = src & MASK; + + *dst = [ + F::from_u64_unchecked(and_result), + F::from_u64_unchecked(or_result), + F::from_u64_unchecked(xor_result), + ] + } + + results + }; + + let dependencies = Place::from_variables(composite_result); + let outputs = Place::from_variables(all_results); + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + // lookup more, but this time using a table as a range check for all the and/or/xor chunks + for source_set in all_results.array_chunks::<2>() { + // value is irrelevant, it's just a range check + let _: [Variable; 1] = cs.perform_lookup::<2, 1>(table_id, &[source_set[0], source_set[1]]); + } + + let zero_var = cs.allocate_constant(F::ZERO); + + debug_assert!(F::CAPACITY_BITS >= 56); + + if ::SetupConfig::KEEP_SETUP { + // enforce. Note that there are no new variables here + for (src, decomposition) in composite_result.iter().zip(all_results.array_chunks::<3>()) { + if cs.gate_is_allowed::>() { + let mut gate = ReductionGate::::empty(); + gate.params = ReductionGateParams { + reduction_constants: [F::SHIFTS[0], F::SHIFTS[16], F::SHIFTS[32], F::ZERO], + }; + gate.reduction_result = *src; + gate.terms = [ + decomposition[0], + decomposition[1], + decomposition[2], + zero_var, + ]; + + gate.add_to_cs(cs); + } else if cs.gate_is_allowed::>() { + let mut gate = ReductionByPowersGate::::empty(); + use crate::main_vm::opcodes::binop::reduction_by_powers_gate::ReductionByPowersGateParams; + gate.params = ReductionByPowersGateParams { + reduction_constant: F::from_u64_unchecked(1u64 << 16), + }; + gate.reduction_result = *src; + gate.terms = [ + decomposition[0], + decomposition[1], + decomposition[2], + zero_var, + ]; + + gate.add_to_cs(cs); + } else { + unimplemented!() + } + } + } + + let mut and_results = [Variable::placeholder(); 32]; + let mut or_results = [Variable::placeholder(); 32]; + let mut xor_results = [Variable::placeholder(); 32]; + + for (((and, or), xor), src) in and_results + .iter_mut() + .zip(or_results.iter_mut()) + .zip(xor_results.iter_mut()) + .zip(all_results.array_chunks::<3>()) + { + *and = src[0]; + *or = src[1]; + *xor = src[2]; + } + + let and_results = and_results.map(|el| unsafe { UInt8::from_variable_unchecked(el) }); + let or_results = or_results.map(|el| unsafe { UInt8::from_variable_unchecked(el) }); + let xor_results = xor_results.map(|el| unsafe { UInt8::from_variable_unchecked(el) }); + + (and_results, or_results, xor_results) +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret.rs b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret.rs new file mode 100644 index 00000000..3fb6e5cc --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret.rs @@ -0,0 +1,512 @@ +use boojum::cs::traits::cs::DstBuffer; +use boojum::gadgets::traits::castable::WitnessCastable; + +use crate::base_structures::{ + log_query::LogQuery, vm_state::saved_context::ExecutionContextRecord, +}; + +use super::*; +use crate::base_structures::decommit_query::DecommitQuery; +use crate::base_structures::vm_state::GlobalContext; +use crate::base_structures::vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH; +use crate::main_vm::opcodes::call_ret_impl::*; +use crate::main_vm::state_diffs::MAX_SPONGES_PER_CYCLE; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use arrayvec::ArrayVec; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +// call and ret are merged because their main part is manipulation over callstack, +// and we will keep those functions here + +pub(crate) fn apply_calls_and_ret< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, + witness_oracle: &SynchronizedWitnessOracle, + global_context: &GlobalContext, + round_function: &R, +) where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let (common_part, far_call_abi, call_ret_forwarding_mode) = + compute_shared_abi_parts(cs, &common_opcode_state.src0_view); + + let near_call_data = callstack_candidate_for_near_call( + cs, + draft_vm_state, + common_opcode_state, + opcode_carry_parts, + witness_oracle, + ); + + let far_call_data = callstack_candidate_for_far_call( + cs, + draft_vm_state, + common_opcode_state, + opcode_carry_parts, + witness_oracle, + global_context, + &common_part, + &far_call_abi, + &call_ret_forwarding_mode, + round_function, + ); + + let ret_data = callstack_candidate_for_ret( + cs, + draft_vm_state, + common_opcode_state, + opcode_carry_parts, + witness_oracle, + &common_part, + &call_ret_forwarding_mode, + ); + + // select callstack that will become current + + let NearCallData { + apply_near_call, + old_context: old_context_for_near_call, + new_context: new_context_for_near_call, + } = near_call_data; + + let FarCallData { + apply_far_call, + old_context: old_context_for_far_call, + new_context: new_context_for_far_call, + new_decommittment_queue_tail, + new_decommittment_queue_len, + new_forward_queue_tail: new_forward_queue_state_for_far_call, + new_forward_queue_len: new_forward_queue_len_for_far_call, + + pending_sponges: pending_sponges_for_far_call, + + specific_registers_updates: specific_registers_updates_for_far_call, + specific_registers_zeroing: specific_registers_zeroing_for_far_call, + remove_ptr_on_specific_registers: remove_ptr_on_specific_registers_for_far_call, + + new_memory_pages_counter, + pending_exception: pending_exception_from_far_call, + } = far_call_data; + + let RetData { + apply_ret, + is_panic: is_ret_panic, + new_context: new_context_for_ret, + originally_popped_context: originally_popped_context_for_ret, + previous_callstack_state: previous_callstack_state_for_ret, + new_forward_queue_tail: new_forward_queue_state_for_ret, + new_forward_queue_len: new_forward_queue_len_for_ret, + did_return_from_far_call, + + specific_registers_updates: specific_registers_updates_for_ret, + specific_registers_zeroing: specific_registers_zeroing_for_ret, + remove_ptr_on_specific_registers: remove_ptr_on_specific_registers_for_ret, + } = ret_data; + + let is_call_like = Boolean::multi_or(cs, &[apply_near_call, apply_far_call]); + let apply_any = Boolean::multi_or(cs, &[is_call_like, apply_ret]); + let is_ret_panic_if_apply = Boolean::multi_and(cs, &[is_ret_panic, apply_ret]); + let pending_exception_if_far_call = + Boolean::multi_and(cs, &[pending_exception_from_far_call, apply_far_call]); + + let current_frame_is_local = draft_vm_state + .callstack + .current_context + .saved_context + .is_local_call; + + let _current_frame_is_global = current_frame_is_local.negated(cs); + + let is_far_return = Boolean::multi_and(cs, &[apply_ret, did_return_from_far_call]); + let reset_context_value = Boolean::multi_or(cs, &[is_far_return, apply_far_call]); + + // we only need select between candidates, and later on we will select on higher level between current and candidate from (near_call/far_call/ret) + + let mut new_callstack_entry = ExecutionContextRecord::conditionally_select( + cs, + apply_far_call, + &new_context_for_far_call, + &new_context_for_near_call, + ); + + new_callstack_entry = ExecutionContextRecord::conditionally_select( + cs, + apply_ret, + &new_context_for_ret, + &new_callstack_entry, + ); + + // this one will be largely no-op + let mut old_callstack_entry = ExecutionContextRecord::conditionally_select( + cs, + apply_far_call, + &old_context_for_far_call, + &old_context_for_near_call, + ); + + old_callstack_entry = ExecutionContextRecord::conditionally_select( + cs, + apply_ret, + &originally_popped_context_for_ret, + &old_callstack_entry, + ); + + // manual implementation of the stack: we either take a old entry and hash along with the saved context for call-like, or one popped in case of ret + + let initial_state_to_use_for_sponge = Num::parallel_select( + cs, + apply_ret, + &previous_callstack_state_for_ret, + &draft_vm_state.callstack.stack_sponge_state, + ); + + // now we simulate absorb. Note that we have already chosen an initial state, + // so we just use initial state and absorb + + let mut current_state = initial_state_to_use_for_sponge.map(|el| el.get_variable()); + use boojum::gadgets::traits::encodable::CircuitEncodable; + + let encoded_execution_record = old_callstack_entry.encode(cs); + + use boojum::gadgets::round_function::simulate_round_function; + + // absorb by replacement + let round_0_initial = [ + encoded_execution_record[0], + encoded_execution_record[1], + encoded_execution_record[2], + encoded_execution_record[3], + encoded_execution_record[4], + encoded_execution_record[5], + encoded_execution_record[6], + encoded_execution_record[7], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_0_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_0_initial, apply_any); + + current_state = round_0_final; + + let round_1_initial = [ + encoded_execution_record[8], + encoded_execution_record[9], + encoded_execution_record[10], + encoded_execution_record[11], + encoded_execution_record[12], + encoded_execution_record[13], + encoded_execution_record[14], + encoded_execution_record[15], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_1_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_1_initial, apply_any); + + current_state = round_1_final; + + let round_2_initial = [ + encoded_execution_record[16], + encoded_execution_record[17], + encoded_execution_record[18], + encoded_execution_record[19], + encoded_execution_record[20], + encoded_execution_record[21], + encoded_execution_record[22], + encoded_execution_record[23], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_2_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_2_initial, apply_any); + + current_state = round_2_final; + + let round_3_initial = [ + encoded_execution_record[24], + encoded_execution_record[25], + encoded_execution_record[26], + encoded_execution_record[27], + encoded_execution_record[28], + encoded_execution_record[29], + encoded_execution_record[30], + encoded_execution_record[31], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_3_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_3_initial, apply_any); + + current_state = round_3_final; + + let potential_final_state = current_state.map(|el| Num::from_variable(el)); + + for (a, b) in potential_final_state + .iter() + .zip(draft_vm_state.callstack.stack_sponge_state.iter()) + { + Num::conditionally_enforce_equal(cs, apply_ret, a, b); + } + + let new_callstack_state = Num::parallel_select( + cs, + apply_ret, + &previous_callstack_state_for_ret, + &potential_final_state, + ); + + let depth_increased = unsafe { + draft_vm_state + .callstack + .context_stack_depth + .increment_unchecked(cs) + }; + let one_u32 = UInt32::allocated_constant(cs, 1); + let (depth_decreased, uf) = draft_vm_state + .callstack + .context_stack_depth + .overflowing_sub(cs, one_u32); + + uf.conditionally_enforce_false(cs, apply_ret); + + let new_callstack_depth = + UInt32::conditionally_select(cs, apply_ret, &depth_decreased, &depth_increased); + + // assemble a new callstack in full + + let new_log_queue_forward_tail = Num::parallel_select( + cs, + apply_ret, + &new_forward_queue_state_for_ret, + &new_forward_queue_state_for_far_call, + ); + + let new_log_queue_forward_len = UInt32::conditionally_select( + cs, + apply_ret, + &new_forward_queue_len_for_ret, + &new_forward_queue_len_for_far_call, + ); + + use crate::base_structures::vm_state::callstack::FullExecutionContext; + + let new_context = FullExecutionContext { + saved_context: new_callstack_entry, + log_queue_forward_tail: new_log_queue_forward_tail, + log_queue_forward_part_length: new_log_queue_forward_len, + }; + + use crate::base_structures::vm_state::callstack::Callstack; + + let new_callstack = Callstack { + current_context: new_context, + context_stack_depth: new_callstack_depth, + stack_sponge_state: new_callstack_state, + }; + + let mut common_relations_buffer = ArrayVec::< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >::new(); + // first we push relations that are common, namely callstack sponge + + common_relations_buffer.push(( + apply_any, + round_0_initial.map(|el| Num::from_variable(el)), + round_0_final.map(|el| Num::from_variable(el)), + )); + + common_relations_buffer.push(( + apply_any, + round_1_initial.map(|el| Num::from_variable(el)), + round_1_final.map(|el| Num::from_variable(el)), + )); + + common_relations_buffer.push(( + apply_any, + round_2_initial.map(|el| Num::from_variable(el)), + round_2_final.map(|el| Num::from_variable(el)), + )); + + common_relations_buffer.push(( + apply_any, + round_3_initial.map(|el| Num::from_variable(el)), + round_3_final.map(|el| Num::from_variable(el)), + )); + + // and now we append relations for far call, that are responsible for storage read and decommittment + common_relations_buffer.extend(pending_sponges_for_far_call); + + // now just append relations to select later on + + // all the opcodes reset flags in full + let mut new_flags = common_opcode_state.reseted_flags; + new_flags.overflow_or_less_than = is_ret_panic_if_apply; + + // report to witness oracle + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = Vec::with_capacity( + as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 3, + ); + dependencies.push(apply_any.get_variable().into()); + dependencies.push(is_call_like.get_variable().into()); + dependencies.push(new_callstack_depth.get_variable().into()); + dependencies.extend(Place::from_variables( + new_callstack_entry.flatten_as_variables(), + )); + + cs.set_values_with_dependencies_vararg( + &dependencies, + &[], + move |inputs: &[F], _buffer: &mut DstBuffer<'_, '_, F>| { + let execute = >::cast_from_source(inputs[0]); + let is_call_like = >::cast_from_source(inputs[1]); + let new_depth = >::cast_from_source(inputs[2]); + + let mut query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + query.copy_from_slice(&inputs[3..]); + use crate::base_structures::vm_state::saved_context::ExecutionContextRecordWitness; + let query: ExecutionContextRecordWitness = + CSAllocatableExt::witness_from_set_of_values(query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + guard.report_new_callstack_frame(&query, new_depth, is_call_like, execute); + drop(guard); + }, + ); + + // add everything to state diffs + + // we should check that opcode can not use src0/dst0 in memory + const FAR_CALL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::FarCall(zkevm_opcode_defs::FarCallOpcode::Normal); + const NEAR_CALL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::NearCall(zkevm_opcode_defs::NearCallOpcode); + const RET_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Ret(zkevm_opcode_defs::RetOpcode::Ok); + + assert!(FAR_CALL_OPCODE.can_have_src0_from_mem(SUPPORTED_ISA_VERSION) == false); + assert!(NEAR_CALL_OPCODE.can_have_src0_from_mem(SUPPORTED_ISA_VERSION) == false); + assert!(RET_OPCODE.can_have_src0_from_mem(SUPPORTED_ISA_VERSION) == false); + + assert!(FAR_CALL_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) == false); + assert!(NEAR_CALL_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) == false); + assert!(RET_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) == false); + + diffs_accumulator.sponge_candidates_to_run.push(( + false, + false, + apply_any, + common_relations_buffer, + )); + diffs_accumulator.flags.push((apply_any, new_flags)); + + // each opcode may have different register updates + for (idx, el) in specific_registers_updates_for_far_call + .into_iter() + .enumerate() + { + if let Some(el) = el { + diffs_accumulator.specific_registers_updates[idx].push(el); + } + } + + for (idx, el) in specific_registers_updates_for_ret.into_iter().enumerate() { + if let Some(el) = el { + diffs_accumulator.specific_registers_updates[idx].push(el); + } + } + + // same for zeroing out and removing ptr markers + for (idx, el) in specific_registers_zeroing_for_far_call + .into_iter() + .enumerate() + { + if let Some(el) = el { + diffs_accumulator.specific_registers_zeroing[idx].push(el); + } + } + + for (idx, el) in specific_registers_zeroing_for_ret.into_iter().enumerate() { + if let Some(el) = el { + diffs_accumulator.specific_registers_zeroing[idx].push(el); + } + } + + for (idx, el) in remove_ptr_on_specific_registers_for_far_call + .into_iter() + .enumerate() + { + if let Some(el) = el { + diffs_accumulator.remove_ptr_on_specific_registers[idx].push(el); + } + } + + for (idx, el) in remove_ptr_on_specific_registers_for_ret + .into_iter() + .enumerate() + { + if let Some(el) = el { + diffs_accumulator.remove_ptr_on_specific_registers[idx].push(el); + } + } + + // pending exception if any + diffs_accumulator + .pending_exceptions + .push(pending_exception_if_far_call); + + // callstacks in full + diffs_accumulator + .callstacks + .push((apply_any, new_callstack)); + + // far call already chosen it + debug_assert!(diffs_accumulator.memory_page_counters.is_none()); + diffs_accumulator.memory_page_counters = Some(new_memory_pages_counter); + + let zero_u32 = UInt32::zero(cs); + let empty_context_value = [zero_u32; 4]; + + diffs_accumulator + .context_u128_candidates + .push((reset_context_value, empty_context_value)); + + debug_assert!(diffs_accumulator.decommitment_queue_candidates.is_none()); + diffs_accumulator.decommitment_queue_candidates = Some(( + apply_far_call, + new_decommittment_queue_len, + new_decommittment_queue_tail, + )); +} 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 new file mode 100644 index 00000000..125ee247 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/far_call.rs @@ -0,0 +1,1594 @@ +use zkevm_opcode_defs::system_params::STORAGE_AUX_BYTE; + +use crate::base_structures::{ + log_query::{self, LogQuery}, + register::VMRegister, + vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH, +}; +use boojum::gadgets::{u160::UInt160, u256::UInt256}; + +use super::*; +use crate::base_structures::decommit_query::DecommitQuery; +use crate::base_structures::decommit_query::DecommitQueryWitness; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecordWitness; +use crate::base_structures::vm_state::GlobalContext; +use crate::base_structures::vm_state::QUEUE_STATE_WIDTH; +use crate::main_vm::opcodes::call_ret_impl::far_call::log_query::LogQueryWitness; +use crate::main_vm::state_diffs::MAX_SPONGES_PER_CYCLE; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use arrayvec::ArrayVec; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::traits::cs::DstBuffer; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +const FORCED_ERGS_FOR_MSG_VALUE_SIMUALTOR: bool = false; + +pub(crate) struct FarCallData { + pub(crate) apply_far_call: Boolean, + pub(crate) old_context: ExecutionContextRecord, + pub(crate) new_context: ExecutionContextRecord, + pub(crate) new_decommittment_queue_tail: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + pub(crate) new_decommittment_queue_len: UInt32, + pub(crate) new_forward_queue_tail: [Num; QUEUE_STATE_WIDTH], + pub(crate) new_forward_queue_len: UInt32, + pub(crate) pending_sponges: ArrayVec< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >, + pub(crate) specific_registers_updates: [Option<(Boolean, VMRegister)>; REGISTERS_COUNT], + pub(crate) specific_registers_zeroing: [Option>; REGISTERS_COUNT], + pub(crate) remove_ptr_on_specific_registers: [Option>; REGISTERS_COUNT], + pub(crate) pending_exception: Boolean, + pub(crate) new_memory_pages_counter: UInt32, +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] + +pub(crate) struct FarCallPartialABI { + pub(crate) ergs_passed: UInt32, + pub(crate) shard_id: UInt8, + pub(crate) constructor_call: Boolean, + pub(crate) system_call: Boolean, +} + +use crate::main_vm::register_input_view::RegisterInputView; + +impl FarCallPartialABI { + pub fn from_register_view>( + cs: &mut CS, + input: &RegisterInputView, + ) -> Self { + // low part of highest 64 bits + let ergs_passed = input.u32x8_view[6]; + + // higher parts of highest 64 bits + let shard_id = input.u8x32_view + [zkevm_opcode_defs::definitions::abi::far_call::FAR_CALL_SHARD_ID_BYTE_IDX]; + let constructor_call = input.u8x32_view + [zkevm_opcode_defs::definitions::abi::far_call::FAR_CALL_CONSTRUCTOR_CALL_BYTE_IDX] + .is_zero(cs) + .negated(cs); + let system_call = input.u8x32_view + [zkevm_opcode_defs::definitions::abi::far_call::FAR_CALL_SYSTEM_CALL_BYTE_IDX] + .is_zero(cs) + .negated(cs); + + let new = Self { + ergs_passed, + shard_id, + constructor_call, + system_call, + }; + + new + } +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] + +pub(crate) struct CommonCallRetABI { + pub(crate) fat_ptr: FatPtrInABI, + pub(crate) upper_bound: UInt32, + pub(crate) ptr_validation_data: PtrValidationData, +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] + +pub(crate) struct CallRetForwardingMode { + pub(crate) use_heap: Boolean, + pub(crate) use_aux_heap: Boolean, + pub(crate) forward_fat_pointer: Boolean, +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub(crate) struct FatPtrInABI { + pub(crate) offset: UInt32, + pub(crate) page: UInt32, + pub(crate) start: UInt32, + pub(crate) length: UInt32, +} + +impl Selectable for FatPtrInABI { + fn conditionally_select>( + cs: &mut CS, + flag: Boolean, + a: &Self, + b: &Self, + ) -> Self { + // do via multiselect + let a = [a.offset, a.page, a.start, a.length]; + let b = [b.offset, b.page, b.start, b.length]; + + let result = UInt32::parallel_select(cs, flag, &a, &b); + + Self { + offset: result[0], + page: result[1], + start: result[2], + length: result[3], + } + } +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub(crate) struct PtrValidationData { + pub(crate) generally_invalid: Boolean, // common invariants + pub(crate) is_non_addressable: Boolean, +} + +impl FatPtrInABI { + pub(crate) fn parse_and_validate>( + cs: &mut CS, + input: &RegisterInputView, + as_fresh: Boolean, + ) -> (Self, UInt32, PtrValidationData) { + // we can never address a range [2^32 - 32..2^32] this way, but we don't care because + // it's impossible to pay for such memory growth + let offset = input.u32x8_view[0]; + let page = input.u32x8_view[1]; + let start = input.u32x8_view[2]; + let length = input.u32x8_view[3]; + + let offset_is_zero = offset.is_zero(cs); + let offset_is_non_zero = offset_is_zero.negated(cs); + + let non_zero_offset_if_should_be_fresh = + Boolean::multi_and(cs, &[offset_is_non_zero, as_fresh]); + + let (end_non_inclusive, slice_u32_range_overflow) = start.overflowing_add(cs, length); + + // offset <= length, that captures the empty slice (0, 0) + let (_, is_invalid_as_slice) = length.overflowing_sub(cs, offset); + + let ptr_is_invalid = Boolean::multi_or( + cs, + &[ + non_zero_offset_if_should_be_fresh, + slice_u32_range_overflow, + is_invalid_as_slice, + ], + ); + + let offset = offset.mask_negated(cs, ptr_is_invalid); + let page = page.mask_negated(cs, ptr_is_invalid); + let start = start.mask_negated(cs, ptr_is_invalid); + let length = length.mask_negated(cs, ptr_is_invalid); + + let new = Self { + offset, + page, + start, + length, + }; + + let validation_data = PtrValidationData { + generally_invalid: ptr_is_invalid, + is_non_addressable: slice_u32_range_overflow, + }; + + (new, end_non_inclusive, validation_data) + } + + pub(crate) fn mask_into_empty>( + &self, + cs: &mut CS, + set_empty: Boolean, + ) -> Self { + let offset = self.offset.mask_negated(cs, set_empty); + let page = self.page.mask_negated(cs, set_empty); + let start = self.start.mask_negated(cs, set_empty); + let length = self.length.mask_negated(cs, set_empty); + + let new = Self { + offset, + page, + start, + length, + }; + + new + } + + // ONLY call after validations + pub(crate) fn readjust>(&self, cs: &mut CS) -> Self { + // if we have prevalidated everything, then we KNOW that "length + start" doesn't overflow and is within addressable bound, + // and that offset < length, so overflows here can be ignored + let new_start = self.start.add_no_overflow(cs, self.offset); + let new_length = self.length.sub_no_overflow(cs, self.offset); + + let zero_u32 = UInt32::zero(cs); + + let new = Self { + offset: zero_u32, + page: self.page, + start: new_start, + length: new_length, + }; + + new + } + + pub(crate) fn into_register>(self, cs: &mut CS) -> VMRegister { + let zero_u32 = UInt32::zero(cs); + let boolean_true = Boolean::allocated_constant(cs, true); + + let result = VMRegister { + is_pointer: boolean_true, + value: UInt256 { + inner: [ + self.offset, + self.page, + self.start, + self.length, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + ], + }, + }; + + result + } +} + +pub(crate) fn callstack_candidate_for_far_call< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + witness_oracle: &SynchronizedWitnessOracle, + global_context: &GlobalContext, + common_abi_parts: &CommonCallRetABI, + far_call_abi: &FarCallPartialABI, + forwarding_data: &CallRetForwardingMode, + round_function: &R, +) -> FarCallData +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + // new callstack should be just the same a the old one, but we also need to update the pricing for pubdata in the rare case + + const FAR_CALL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::FarCall(zkevm_opcode_defs::FarCallOpcode::Normal); + + let execute = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(FAR_CALL_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (execute.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying FAR CALL"); + } + } + + let _is_normal_call = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(zkevm_opcode_defs::Opcode::FarCall( + zkevm_opcode_defs::FarCallOpcode::Normal, + )); + let is_delegated_call = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(zkevm_opcode_defs::Opcode::FarCall( + zkevm_opcode_defs::FarCallOpcode::Delegate, + )); + let is_mimic_call = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(zkevm_opcode_defs::Opcode::FarCall( + zkevm_opcode_defs::FarCallOpcode::Mimic, + )); + + let is_kernel_mode = draft_vm_state + .callstack + .current_context + .saved_context + .is_kernel_mode; + let mut current_callstack_entry = draft_vm_state.callstack.current_context.saved_context; + // perform all known modifications, like PC/SP saving + current_callstack_entry.pc = opcode_carry_parts.next_pc; + + // we need a completely fresh one + let mut new_callstack_entry = ExecutionContextRecord::uninitialized(cs); + // apply memory stipends right away + new_callstack_entry.heap_upper_bound = UInt32::allocated_constant( + cs, + zkevm_opcode_defs::system_params::NEW_FRAME_MEMORY_STIPEND, + ); + new_callstack_entry.aux_heap_upper_bound = UInt32::allocated_constant( + cs, + zkevm_opcode_defs::system_params::NEW_FRAME_MEMORY_STIPEND, + ); + + // now also create target for mimic + let implicit_mimic_call_reg = draft_vm_state.registers + [zkevm_opcode_defs::definitions::far_call::CALL_IMPLICIT_PARAMETER_REG_IDX as usize]; + + // - get code destination address + // - resolve caller/callee dependencies + // - resolve calldata page + // - resolve ergs + + let caller_address_for_mimic = UInt160 { + inner: [ + implicit_mimic_call_reg.value.inner[0], + implicit_mimic_call_reg.value.inner[1], + implicit_mimic_call_reg.value.inner[2], + implicit_mimic_call_reg.value.inner[3], + implicit_mimic_call_reg.value.inner[4], + ], + }; + + // in src0 lives the ABI + // in src1 lives the destination + + // we also reuse pre-parsed ABI + + // src1 is target address + let destination_address = UInt160 { + inner: [ + common_opcode_state.src1_view.u32x8_view[0], + common_opcode_state.src1_view.u32x8_view[1], + common_opcode_state.src1_view.u32x8_view[2], + common_opcode_state.src1_view.u32x8_view[3], + common_opcode_state.src1_view.u32x8_view[4], + ], + }; + + let is_static_call = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[FAR_CALL_STATIC_FLAG_IDX]; + let is_call_shard = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[FAR_CALL_SHARD_FLAG_IDX]; + + let destination_shard = far_call_abi.shard_id; + let caller_shard_id = current_callstack_entry.this_shard_id; + let destination_shard = + UInt8::conditionally_select(cs, is_call_shard, &destination_shard, &caller_shard_id); + let target_is_zkporter = destination_shard.is_zero(cs).negated(cs); + + let target_is_kernel = { + let destination_16_32 = UInt16::from_le_bytes( + cs, + [ + common_opcode_state.src1_view.u8x32_view[2], + common_opcode_state.src1_view.u8x32_view[3], + ], + ); + + let destination_16_32_is_zero = destination_16_32.is_zero(cs); + let destination_32_64_is_zero = common_opcode_state.src1_view.u32x8_view[1].is_zero(cs); + let destination_64_96_is_zero = common_opcode_state.src1_view.u32x8_view[2].is_zero(cs); + let destination_96_128_is_zero = common_opcode_state.src1_view.u32x8_view[3].is_zero(cs); + let destination_128_160_is_zero = common_opcode_state.src1_view.u32x8_view[4].is_zero(cs); + + let higher_bytes_are_zeroes = Boolean::multi_and( + cs, + &[ + destination_16_32_is_zero, + destination_32_64_is_zero, + destination_64_96_is_zero, + destination_96_128_is_zero, + destination_128_160_is_zero, + ], + ); + + higher_bytes_are_zeroes + }; + + let mut far_call_abi = *far_call_abi; + + // mask flags in ABI if not applicable + far_call_abi.constructor_call = + Boolean::multi_and(cs, &[far_call_abi.constructor_call, is_kernel_mode]); + far_call_abi.system_call = + Boolean::multi_and(cs, &[far_call_abi.system_call, target_is_kernel]); + + if crate::config::CIRCUIT_VERSOBE { + if execute.witness_hook(&*cs)().unwrap_or(false) { + dbg!(forwarding_data.witness_hook(&*cs)().unwrap()); + dbg!(far_call_abi.witness_hook(&*cs)().unwrap()); + } + } + + // the same as we use for LOG + let timestamp_to_use_for_decommittment_request = + common_opcode_state.timestamp_for_first_decommit_or_precompile_read; + let default_target_memory_page = draft_vm_state.memory_page_counter; + + // increment next counter + let new_base_page = draft_vm_state.memory_page_counter; + let memory_pages_per_far_call = UInt32::allocated_constant(cs, NEW_MEMORY_PAGES_PER_FAR_CALL); + let new_memory_pages_counter = draft_vm_state + .memory_page_counter + .add_no_overflow(cs, memory_pages_per_far_call); + + let new_memory_pages_counter = UInt32::conditionally_select( + cs, + execute, + &new_memory_pages_counter, + &draft_vm_state.memory_page_counter, + ); + + // now we have everything to perform code read and decommittment + + let mut all_pending_sponges = ArrayVec::new(); + + let (bytecode_hash_is_trivial, bytecode_hash, (new_forward_queue_tail, new_forward_queue_len)) = + may_be_read_code_hash( + cs, + &mut all_pending_sponges, + &destination_address, + &destination_shard, + &target_is_zkporter, + &global_context.zkporter_is_available, + &execute, + &global_context.default_aa_code_hash, + &target_is_kernel, + draft_vm_state + .callstack + .current_context + .log_queue_forward_tail, + draft_vm_state + .callstack + .current_context + .log_queue_forward_part_length, + timestamp_to_use_for_decommittment_request, + draft_vm_state.tx_number_in_block, + witness_oracle, + round_function, + ); + + // now we should do validation BEFORE decommittment + + let zero_u32 = UInt32::zero(cs); + + let target_code_memory_page = UInt32::conditionally_select( + cs, + bytecode_hash_is_trivial, + &zero_u32, + &default_target_memory_page, + ); + + // first we validate if code hash is indeed in the format that we expect + + // If we do not do "constructor call" then 2nd byte should be 0, + // otherwise it's 1 + + let bytecode_hash_upper_decomposition = bytecode_hash.inner[7].decompose_into_bytes(cs); + + let version_byte = bytecode_hash_upper_decomposition[3]; + let code_hash_version_byte = + UInt8::allocated_constant(cs, zkevm_opcode_defs::ContractCodeSha256::VERSION_BYTE); + let versioned_byte_is_valid = UInt8::equals(cs, &version_byte, &code_hash_version_byte); + let versioned_byte_is_invalid = versioned_byte_is_valid.negated(cs); + + let marker_byte = bytecode_hash_upper_decomposition[2]; + let is_normal_call_marker = marker_byte.is_zero(cs); + let now_in_construction_marker_byte = UInt8::allocated_constant( + cs, + zkevm_opcode_defs::ContractCodeSha256::YET_CONSTRUCTED_MARKER, + ); + let is_constructor_call_marker = + UInt8::equals(cs, &marker_byte, &now_in_construction_marker_byte); + let unknown_marker = + Boolean::multi_or(cs, &[is_normal_call_marker, is_constructor_call_marker]).negated(cs); + + // NOTE: if bytecode hash is trivial then it's 0, so version byte is not valid! + let code_format_exception = Boolean::multi_or(cs, &[versioned_byte_is_invalid, unknown_marker]); + + // we do not remask right away yet + + let normal_call_code = far_call_abi.constructor_call.negated(cs); + + let can_call_normally = Boolean::multi_and(cs, &[is_normal_call_marker, normal_call_code]); + let can_call_constructor = Boolean::multi_and( + cs, + &[is_constructor_call_marker, far_call_abi.constructor_call], + ); + let can_call_code = Boolean::multi_or(cs, &[can_call_normally, can_call_constructor]); + + let marker_byte_masked = UInt8::allocated_constant( + cs, + zkevm_opcode_defs::ContractCodeSha256::CODE_AT_REST_MARKER, + ); + + let bytecode_at_rest_top_word = UInt32::from_le_bytes( + cs, + [ + bytecode_hash_upper_decomposition[0], + bytecode_hash_upper_decomposition[1], + marker_byte_masked, + code_hash_version_byte, + ], + ); + + let mut bytecode_at_storage_format = bytecode_hash; + bytecode_at_storage_format.inner[7] = bytecode_at_rest_top_word; + + let zero_u256 = UInt256::zero(cs); + + let masked_value_if_mask = UInt256::conditionally_select( + cs, + target_is_kernel, + &zero_u256, + &global_context.default_aa_code_hash, + ); + + let masked_bytecode_hash = UInt256::conditionally_select( + cs, + can_call_code, + &bytecode_at_storage_format, + &masked_value_if_mask, + ); + + // at the end of the day all our exceptions will lead to memory page being 0 + + let masked_bytecode_hash_upper_decomposition = + masked_bytecode_hash.inner[7].decompose_into_bytes(cs); + + let mut code_hash_length_in_words = UInt16::from_le_bytes( + cs, + [ + masked_bytecode_hash_upper_decomposition[0], + masked_bytecode_hash_upper_decomposition[1], + ], + ); + code_hash_length_in_words = code_hash_length_in_words.mask_negated(cs, code_format_exception); + + // if we call now-in-construction system contract, then we formally mask into 0 (even though it's not needed), + // and we should put an exception here + + let can_not_call_code = can_call_code.negated(cs); + let call_now_in_construction_kernel = + Boolean::multi_and(cs, &[can_not_call_code, target_is_kernel]); + + // exceptions, along with `bytecode_hash_is_trivial` indicate whether we will or will decommit code + // into memory, or will just use UNMAPPED_PAGE + let mut exceptions = ArrayVec::, 5>::new(); + exceptions.push(code_format_exception); + exceptions.push(call_now_in_construction_kernel); + + // resolve passed ergs, passed calldata page, etc + + let forward_fat_pointer = forwarding_data.forward_fat_pointer; + let src0_is_integer = common_opcode_state.src0_view.is_ptr.negated(cs); + + let fat_ptr_expected_exception = + Boolean::multi_and(cs, &[forward_fat_pointer, src0_is_integer]); + exceptions.push(fat_ptr_expected_exception); + + // add pointer validation cases + exceptions.push(common_abi_parts.ptr_validation_data.generally_invalid); + exceptions.push(common_abi_parts.ptr_validation_data.is_non_addressable); + + let do_not_forward_ptr = forward_fat_pointer.negated(cs); + + let exceptions_collapsed = Boolean::multi_or(cs, &exceptions); + + // if crate::config::CIRCUIT_VERSOBE { + // if execute.witness_hook(&*cs)().unwrap() { + // dbg!(code_format_exception.witness_hook(&*cs)().unwrap()); + // dbg!(call_now_in_construction_kernel.witness_hook(&*cs)().unwrap()); + // dbg!(fat_ptr_expected_exception.witness_hook(&*cs)().unwrap()); + // } + // } + + let fat_ptr = common_abi_parts.fat_ptr; + // we readjust before heap resize + + let fat_ptr_adjusted_if_forward = fat_ptr.readjust(cs); + + let page = UInt32::conditionally_select( + cs, + forwarding_data.use_heap, + &opcode_carry_parts.heap_page, + &opcode_carry_parts.aux_heap_page, + ); + + let fat_ptr_for_heaps = FatPtrInABI { + offset: zero_u32, + page, + start: fat_ptr.start, + length: fat_ptr.length, + }; + + let final_fat_ptr = FatPtrInABI::conditionally_select( + cs, + forwarding_data.forward_fat_pointer, + &fat_ptr_adjusted_if_forward, + &fat_ptr_for_heaps, + ); + + // and mask in case of exceptions + + let final_fat_ptr = final_fat_ptr.mask_into_empty(cs, exceptions_collapsed); + + if crate::config::CIRCUIT_VERSOBE { + if execute.witness_hook(&*cs)().unwrap_or(false) { + dbg!(final_fat_ptr.witness_hook(&*cs)().unwrap()); + } + } + + // now we can resize memory + + let upper_bound = common_abi_parts.upper_bound; + // first mask to 0 if exceptions happened + let upper_bound = upper_bound.mask_negated(cs, exceptions_collapsed); + // then compute to penalize for out of memory access attemp + let memory_region_is_not_addressable = common_abi_parts.ptr_validation_data.is_non_addressable; + + // and penalize if pointer is fresh and not addressable + let penalize_heap_overflow = + Boolean::multi_and(cs, &[memory_region_is_not_addressable, do_not_forward_ptr]); + let u32_max = UInt32::allocated_constant(cs, u32::MAX); + + let upper_bound = + UInt32::conditionally_select(cs, penalize_heap_overflow, &u32_max, &upper_bound); + + // potentially pay for memory growth for heap and aux heap + + let heap_max_accessed = upper_bound.mask(cs, forwarding_data.use_heap); + let heap_bound = current_callstack_entry.heap_upper_bound; + let (mut heap_growth, uf) = heap_max_accessed.overflowing_sub(cs, heap_bound); + heap_growth = heap_growth.mask_negated(cs, uf); // if we access in bounds then it's 0 + let new_heap_upper_bound = + UInt32::conditionally_select(cs, uf, &heap_bound, &heap_max_accessed); + let grow_heap = Boolean::multi_and(cs, &[forwarding_data.use_heap, execute]); + + let aux_heap_max_accessed = upper_bound.mask(cs, forwarding_data.use_aux_heap); + let aux_heap_bound = current_callstack_entry.aux_heap_upper_bound; + let (mut aux_heap_growth, uf) = aux_heap_max_accessed.overflowing_sub(cs, aux_heap_bound); + aux_heap_growth = aux_heap_growth.mask_negated(cs, uf); // if we access in bounds then it's 0 + let new_aux_heap_upper_bound = + UInt32::conditionally_select(cs, uf, &aux_heap_bound, &aux_heap_max_accessed); + let grow_aux_heap = Boolean::multi_and(cs, &[forwarding_data.use_aux_heap, execute]); + + let mut growth_cost = heap_growth.mask(cs, grow_heap); + growth_cost = UInt32::conditionally_select(cs, grow_aux_heap, &aux_heap_growth, &growth_cost); + + // if crate::config::CIRCUIT_VERSOBE { + // if execute.witness_hook(&*cs)().unwrap() { + // dbg!(opcode_carry_parts.preliminary_ergs_left.witness_hook(&*cs)().unwrap()); + // dbg!(growth_cost.witness_hook(&*cs)().unwrap()); + // } + // } + + let (ergs_left_after_growth, uf) = opcode_carry_parts + .preliminary_ergs_left + .overflowing_sub(cs, growth_cost); + + let mut exceptions = ArrayVec::, 5>::new(); + exceptions.push(exceptions_collapsed); + + let ergs_left_after_growth = ergs_left_after_growth.mask_negated(cs, uf); // if not enough - set to 0 + exceptions.push(uf); + + // if crate::config::CIRCUIT_VERSOBE { + // if execute.witness_hook(&*cs)().unwrap() { + // dbg!(ergs_left_after_growth.witness_hook(&*cs)().unwrap()); + // } + // } + + current_callstack_entry.heap_upper_bound = UInt32::conditionally_select( + cs, + grow_heap, + &new_heap_upper_bound, + ¤t_callstack_entry.heap_upper_bound, + ); + + current_callstack_entry.aux_heap_upper_bound = UInt32::conditionally_select( + cs, + grow_aux_heap, + &new_aux_heap_upper_bound, + ¤t_callstack_entry.aux_heap_upper_bound, + ); + + // now any extra cost + let callee_stipend = if FORCED_ERGS_FOR_MSG_VALUE_SIMUALTOR == false { + zero_u32 + } else { + let is_msg_value_simulator_address_low = + UInt32::allocated_constant(cs, zkevm_opcode_defs::ADDRESS_MSG_VALUE as u32); + let target_low_is_msg_value_simulator = UInt32::equals( + cs, + &destination_address.inner[0], + &is_msg_value_simulator_address_low, + ); + // we know that that msg.value simulator is kernel, so we test equality of low address segment and test for kernel + let target_is_msg_value = + Boolean::multi_and(cs, &[target_is_kernel, target_low_is_msg_value_simulator]); + let is_system_abi = far_call_abi.system_call; + let require_extra = Boolean::multi_and(cs, &[target_is_msg_value, is_system_abi]); + + let additive_cost = UInt32::allocated_constant( + cs, + zkevm_opcode_defs::system_params::MSG_VALUE_SIMULATOR_ADDITIVE_COST, + ); + let max_pubdata_bytes = UInt32::allocated_constant( + cs, + zkevm_opcode_defs::system_params::MSG_VALUE_SIMULATOR_PUBDATA_BYTES_TO_PREPAY, + ); + + let pubdata_cost = + max_pubdata_bytes.non_widening_mul(cs, &draft_vm_state.ergs_per_pubdata_byte); + let cost = pubdata_cost.add_no_overflow(cs, additive_cost); + + cost.mask(cs, require_extra) + }; + + let (ergs_left_after_extra_costs, uf) = + ergs_left_after_growth.overflowing_sub(cs, callee_stipend); + let ergs_left_after_extra_costs = ergs_left_after_extra_costs.mask_negated(cs, uf); // if not enough - set to 0 + let callee_stipend = callee_stipend.mask_negated(cs, uf); // also set to 0 if we were not able to take it + exceptions.push(uf); + + // now we can indeed decommit + + let exception = Boolean::multi_or(cs, &exceptions); + let valid_execution = exception.negated(cs); + let should_decommit = Boolean::multi_and(cs, &[execute, valid_execution]); + + let target_code_memory_page = target_code_memory_page.mask(cs, should_decommit); + + // if crate::config::CIRCUIT_VERSOBE { + // if execute.witness_hook(&*cs)().unwrap() { + // dbg!(exception.witness_hook(&*cs)().unwrap()); + // dbg!(ergs_left_after_extra_costs.witness_hook(&*cs)().unwrap()); + // } + // } + + let ( + not_enough_ergs_to_decommit, + code_memory_page, + (new_decommittment_queue_tail, new_decommittment_queue_len), + ergs_remaining_after_decommit, + ) = add_to_decommittment_queue( + cs, + &mut all_pending_sponges, + &should_decommit, + &ergs_left_after_extra_costs, + &masked_bytecode_hash, + &code_hash_length_in_words, + &draft_vm_state.code_decommittment_queue_state, + &draft_vm_state.code_decommittment_queue_length, + ×tamp_to_use_for_decommittment_request, + &target_code_memory_page, + witness_oracle, + round_function, + ); + + let exception = Boolean::multi_or(cs, &[exception, not_enough_ergs_to_decommit]); + + if crate::config::CIRCUIT_VERSOBE { + if execute.witness_hook(&*cs)().unwrap_or(false) { + dbg!(exception.witness_hook(&*cs)().unwrap()); + } + } + + // on call-like path we continue the forward queue, but have to allocate the rollback queue state from witness + let call_timestamp = draft_vm_state.timestamp; + + let oracle = witness_oracle.clone(); + + let dependencies = [ + call_timestamp.get_variable().into(), + execute.get_variable().into(), + ]; + + // we always access witness, as even for writes we have to get a claimed read value! + let potential_rollback_queue_segment_tail = + Num::allocate_multiple_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let call_timestamp = >::cast_from_source(inputs[0]); + let execute = >::cast_from_source(inputs[1]); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = + guard.get_rollback_queue_tail_witness_for_call(call_timestamp, execute); + drop(guard); + + witness + }, + &dependencies, + ); + + new_callstack_entry.reverted_queue_tail = potential_rollback_queue_segment_tail; + new_callstack_entry.reverted_queue_head = potential_rollback_queue_segment_tail; + new_callstack_entry.reverted_queue_segment_len = zero_u32; + + let dst_pc = UInt16::zero(cs); + let eh_pc = common_opcode_state.decoded_opcode.imm0; + + // if crate::config::CIRCUIT_VERSOBE { + // if execute.witness_hook(&*cs)().unwrap() { + // dbg!(ergs_remaining_after_decommit.witness_hook(&*cs)().unwrap()); + // } + // } + + // now we should resolve all passed ergs. That means + // that we have to read it from ABI, and then use 63/64 rule + let preliminary_ergs_left = ergs_remaining_after_decommit; + + let (ergs_div_by_64, _) = preliminary_ergs_left.div_by_constant(cs, 64); + + let constant_63 = UInt32::allocated_constant(cs, 63); + // NOTE: max passable is 63 / 64 * preliminary_ergs_left, that is itself u32, so it's safe to just + // mul as field elements + let max_passable = Num::from_variable(ergs_div_by_64.get_variable()) + .mul(cs, &Num::from_variable(constant_63.get_variable())); + let max_passable = unsafe { UInt32::from_variable_unchecked(max_passable.get_variable()) }; + + // max passable is <= preliminary_ergs_left from computations above, so it's also safe + let leftover = Num::from_variable(preliminary_ergs_left.get_variable()) + .sub(cs, &Num::from_variable(max_passable.get_variable())); + let leftover = unsafe { UInt32::from_variable_unchecked(leftover.get_variable()) }; + let ergs_to_pass = far_call_abi.ergs_passed; + + let (remaining_from_max_passable, uf) = max_passable.overflowing_sub(cs, ergs_to_pass); + // this one can overflow IF one above underflows, but we are not interested in it's overflow value + let (leftover_and_remaining_if_no_uf, _of) = + leftover.overflowing_add(cs, remaining_from_max_passable); + + let ergs_to_pass = UInt32::conditionally_select(cs, uf, &max_passable, &ergs_to_pass); + + let remaining_for_this_context = + UInt32::conditionally_select(cs, uf, &leftover, &leftover_and_remaining_if_no_uf); + + let remaining_ergs_if_pass = remaining_for_this_context; + let passed_ergs_if_pass = ergs_to_pass; + let passed_ergs_if_pass = passed_ergs_if_pass.add_no_overflow(cs, callee_stipend); + + current_callstack_entry.ergs_remaining = remaining_ergs_if_pass; + + // resolve this/callee shard + let new_this_shard_id = + UInt8::conditionally_select(cs, is_delegated_call, &caller_shard_id, &destination_shard); + + // default is normal call + let mut this_for_next = destination_address; + let mut caller_for_next = current_callstack_entry.this; + + // change if delegate or mimic + // - "this" only changed if delegate + this_for_next = UInt160::conditionally_select( + cs, + is_delegated_call, + ¤t_callstack_entry.this, + &this_for_next, + ); + // "caller" changes in both cases + + caller_for_next = UInt160::conditionally_select( + cs, + is_delegated_call, + ¤t_callstack_entry.caller, + &caller_for_next, + ); + + caller_for_next = UInt160::conditionally_select( + cs, + is_mimic_call, + &caller_address_for_mimic, + &caller_for_next, + ); + + // resolve static, etc + let next_is_static = Boolean::multi_or( + cs, + &[is_static_call, current_callstack_entry.is_static_execution], + ); + + // actually parts to the new one + new_callstack_entry.ergs_remaining = passed_ergs_if_pass; + new_callstack_entry.pc = dst_pc; + new_callstack_entry.exception_handler_loc = eh_pc; + new_callstack_entry.is_static_execution = next_is_static; + + // we need to decide whether new frame is kernel or not for degelatecall + let new_frame_is_kernel = Boolean::conditionally_select( + cs, + is_delegated_call, + ¤t_callstack_entry.is_kernel_mode, + &target_is_kernel, + ); + new_callstack_entry.is_kernel_mode = new_frame_is_kernel; + + // code part + new_callstack_entry.code_shard_id = destination_shard; + new_callstack_entry.code_address = destination_address; + // this part + new_callstack_entry.this_shard_id = new_this_shard_id; + new_callstack_entry.this = this_for_next; + // caller part + new_callstack_entry.caller = caller_for_next; + new_callstack_entry.caller_shard_id = caller_shard_id; + // code page + new_callstack_entry.code_page = code_memory_page; + // base page + new_callstack_entry.base_page = new_base_page; + // context u128 + // if we do delegatecall then we propagate current context value, otherwise + // we capture the current one + new_callstack_entry.context_u128_value_composite = UInt32::parallel_select( + cs, + is_delegated_call, + ¤t_callstack_entry.context_u128_value_composite, + &draft_vm_state.context_composite_u128, + ); + // non-local call + let boolean_false = Boolean::allocated_constant(cs, false); + new_callstack_entry.is_local_call = boolean_false; + + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = Vec::with_capacity( + as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 2, + ); + dependencies.push(execute.get_variable().into()); + dependencies.push( + draft_vm_state + .callstack + .context_stack_depth + .get_variable() + .into(), + ); + dependencies.extend(Place::from_variables( + current_callstack_entry.flatten_as_variables(), + )); + + cs.set_values_with_dependencies_vararg( + &dependencies, + &[], + move |inputs: &[F], _buffer: &mut DstBuffer<'_, '_, F>| { + let execute = >::cast_from_source(inputs[0]); + let current_depth = >::cast_from_source(inputs[1]); + + let mut query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + query.copy_from_slice(&inputs[2..]); + let query: ExecutionContextRecordWitness = + CSAllocatableExt::witness_from_set_of_values(query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + guard.push_callstack_witness(&query, current_depth, execute); + drop(guard); + }, + ); + + // and update registers following our ABI rules + + let new_r1 = final_fat_ptr.into_register(cs); + + let one = Num::allocated_constant(cs, F::ONE); + + let r2_low = Num::fma( + cs, + &Num::from_variable(far_call_abi.constructor_call.get_variable()), + &one, + &F::ONE, + &Num::from_variable(far_call_abi.system_call.get_variable()), + &F::TWO, + ); + + let r2_low = unsafe { UInt32::from_variable_unchecked(r2_low.get_variable()) }; + + let new_r2 = VMRegister { + is_pointer: boolean_false, + value: UInt256 { + inner: [ + r2_low, zero_u32, zero_u32, zero_u32, zero_u32, zero_u32, zero_u32, zero_u32, + ], + }, + }; + + let mut specific_registers_updates = [None; REGISTERS_COUNT]; + specific_registers_updates[0] = Some((execute, new_r1)); + specific_registers_updates[1] = Some((execute, new_r2)); + + let non_system_call = far_call_abi.system_call.negated(cs); + let cleanup_register = Boolean::multi_and(cs, &[execute, non_system_call]); + + let mut register_zero_out = [None; REGISTERS_COUNT]; + + for reg_idx in zkevm_opcode_defs::definitions::far_call::CALL_SYSTEM_ABI_REGISTERS { + register_zero_out[reg_idx as usize] = Some(cleanup_register); + } + for reg_idx in zkevm_opcode_defs::definitions::far_call::CALL_RESERVED_RANGE { + register_zero_out[reg_idx as usize] = Some(execute); + } + register_zero_out + [zkevm_opcode_defs::definitions::far_call::CALL_IMPLICIT_PARAMETER_REG_IDX as usize] = + Some(execute); + + // erase markers everywhere anyway + let mut erase_ptr_markers = [None; REGISTERS_COUNT]; + + for reg_idx in zkevm_opcode_defs::definitions::far_call::CALL_SYSTEM_ABI_REGISTERS { + erase_ptr_markers[reg_idx as usize] = Some(execute); + } + for reg_idx in zkevm_opcode_defs::definitions::far_call::CALL_RESERVED_RANGE { + erase_ptr_markers[reg_idx as usize] = Some(execute); + } + erase_ptr_markers + [zkevm_opcode_defs::definitions::far_call::CALL_IMPLICIT_PARAMETER_REG_IDX as usize] = + Some(execute); + + // if we didn't decommit for ANY reason then we will have target memory page == UNMAPPED PAGE, that will trigger panic + let full_data = FarCallData { + apply_far_call: execute, + old_context: current_callstack_entry, + new_context: new_callstack_entry, + new_decommittment_queue_tail, + new_decommittment_queue_len, + new_forward_queue_tail, + new_forward_queue_len, + new_memory_pages_counter, + pending_sponges: all_pending_sponges, + specific_registers_updates, + specific_registers_zeroing: register_zero_out, + remove_ptr_on_specific_registers: erase_ptr_markers, + pending_exception: exception, + }; + + if crate::config::CIRCUIT_VERSOBE { + if (execute.witness_hook(&*cs))().unwrap_or(false) { + println!( + "New frame as a result of FAR CALL: {:?}", + full_data.new_context.witness_hook(cs)() + ); + } + } + + full_data +} + +// We read code hash from the storage if we have enough ergs, and mask out +// a case if code hash is 0 into either default AA or 0 if destination is kernel +pub fn may_be_read_code_hash< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + relations_buffer: &mut ArrayVec< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >, + call_target: &UInt160, + shard_id: &UInt8, + target_is_zkporter: &Boolean, + zkporter_is_available: &Boolean, + should_execute: &Boolean, + default_aa_code_hash: &UInt256, + target_is_kernel: &Boolean, + forward_queue_tail: [Num; QUEUE_STATE_WIDTH], + forward_queue_length: UInt32, + timestamp_to_use_for_read_request: UInt32, + tx_number_in_block: UInt32, + witness_oracle: &SynchronizedWitnessOracle, + round_function: &R, +) -> ( + Boolean, + UInt256, + ([Num; QUEUE_STATE_WIDTH], UInt32), +) +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let target_is_porter_and_its_available = + Boolean::multi_and(cs, &[*target_is_zkporter, *zkporter_is_available]); + let target_is_rollup = target_is_zkporter.negated(cs); + let zkporter_is_not_available = zkporter_is_available.negated(cs); + + let can_read = Boolean::multi_or(cs, &[target_is_rollup, target_is_porter_and_its_available]); + let should_read = Boolean::multi_and(cs, &[*should_execute, can_read]); + let needs_porter_mask = + Boolean::multi_and(cs, &[*target_is_zkporter, zkporter_is_not_available]); + + let zero_u32 = UInt32::zero(cs); + let target_as_u256 = UInt256 { + inner: [ + call_target.inner[0], + call_target.inner[1], + call_target.inner[2], + call_target.inner[3], + call_target.inner[4], + zero_u32, + zero_u32, + zero_u32, + ], + }; + + let deployer_contract_address_low = UInt32::allocated_constant( + cs, + zkevm_opcode_defs::system_params::DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u32, + ); + let deployer_contract_address = UInt160 { + inner: [ + deployer_contract_address_low, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + ], + }; + + let zero_u256 = UInt256::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + let aux_byte_for_storage = UInt8::allocated_constant(cs, STORAGE_AUX_BYTE); + + let mut log = LogQuery { + address: deployer_contract_address, + key: target_as_u256, + read_value: zero_u256, + written_value: zero_u256, + rw_flag: boolean_false, + aux_byte: aux_byte_for_storage, + rollback: boolean_false, + is_service: boolean_false, + shard_id: *shard_id, + tx_number_in_block, + timestamp: timestamp_to_use_for_read_request, + }; + + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = + Vec::with_capacity( as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 2); + dependencies.push(should_read.get_variable().into()); + dependencies.push(should_execute.get_variable().into()); + dependencies.extend(Place::from_variables(log.flatten_as_variables())); + + // we always access witness, as even for writes we have to get a claimed read value! + let read_value = UInt256::allocate_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let is_storage = >::cast_from_source(inputs[0]); + let execute = >::cast_from_source(inputs[1]); + let mut log_query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + log_query.copy_from_slice(&inputs[2..]); + let log_query: LogQueryWitness = + CSAllocatableExt::witness_from_set_of_values(log_query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = guard.get_storage_read_witness(&log_query, is_storage, execute); + drop(guard); + + witness + }, + &dependencies, + ); + + log.read_value = read_value; + log.written_value = read_value; // our convension as in LOG opcode + + let code_hash_from_storage = read_value; + + let mut bytecode_hash = code_hash_from_storage; + let limbs_are_zero = bytecode_hash.inner.map(|el| el.is_zero(cs)); + let bytecode_is_empty = Boolean::multi_and(cs, &limbs_are_zero); + + let target_is_userspace = target_is_kernel.negated(cs); + let mask_for_default_aa = + Boolean::multi_and(cs, &[should_read, bytecode_is_empty, target_is_userspace]); + + // mask based on some conventions + // first - mask for default AA + bytecode_hash = UInt256::conditionally_select( + cs, + mask_for_default_aa, + &default_aa_code_hash, + &bytecode_hash, + ); + + // - if we couldn't read porter + bytecode_hash = + UInt256::conditionally_select(cs, needs_porter_mask, &zero_u256, &bytecode_hash); + + let dont_mask_to_default_aa = mask_for_default_aa.negated(cs); + let t0 = Boolean::multi_and(cs, &[bytecode_is_empty, dont_mask_to_default_aa]); + let skip_read = should_read.negated(cs); + let bytecode_hash_is_trivial = Boolean::multi_or(cs, &[t0, needs_porter_mask, skip_read]); + + // now process the sponges on whether we did read + let (new_forward_queue_tail, new_forward_queue_length) = + construct_hash_relations_code_hash_read( + cs, + relations_buffer, + &log, + &forward_queue_tail, + &forward_queue_length, + &should_read, + round_function, + ); + + let new_forward_queue_tail = Num::parallel_select( + cs, + should_read, + &new_forward_queue_tail, + &forward_queue_tail, + ); + + ( + bytecode_hash_is_trivial, + bytecode_hash, + (new_forward_queue_tail, new_forward_queue_length), + ) +} + +fn construct_hash_relations_code_hash_read< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + relations_buffer: &mut ArrayVec< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >, + log: &LogQuery, + forward_queue_tail: &[Num; 4], + forward_queue_length: &UInt32, + should_read: &Boolean, + _round_function: &R, +) -> ([Num; 4], UInt32) { + // we absort with replacement + + let mut current_state = R::create_empty_state(cs); + // TODO: may be decide on length specialization + use boojum::gadgets::traits::encodable::CircuitEncodable; + + let forward_packed_log = log.encode(cs); + + // NOTE: since we do merged call/ret, we simulate proper relations here always, + // because we will do join enforcement on call/ret + + let boolean_true = Boolean::allocated_constant(cs, true); + + // absorb by replacement + let round_0_initial = [ + forward_packed_log[0], + forward_packed_log[1], + forward_packed_log[2], + forward_packed_log[3], + forward_packed_log[4], + forward_packed_log[5], + forward_packed_log[6], + forward_packed_log[7], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + use boojum::gadgets::round_function::simulate_round_function; + + let round_0_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_0_initial, boolean_true); + + current_state = round_0_final; + + // absorb by replacement + let round_1_initial = [ + forward_packed_log[8], + forward_packed_log[9], + forward_packed_log[10], + forward_packed_log[11], + forward_packed_log[12], + forward_packed_log[13], + forward_packed_log[14], + forward_packed_log[15], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_1_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_1_initial, boolean_true); + + current_state = round_1_final; + + // absorb by replacement + let round_2_initial = [ + forward_packed_log[16], + forward_packed_log[17], + forward_packed_log[18], + forward_packed_log[19], + forward_queue_tail[0].get_variable(), + forward_queue_tail[1].get_variable(), + forward_queue_tail[2].get_variable(), + forward_queue_tail[3].get_variable(), + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_2_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_2_initial, boolean_true); + + let new_forward_queue_tail = [ + round_2_final[0], + round_2_final[1], + round_2_final[2], + round_2_final[3], + ]; + + let new_forward_queue_length_candidate = + unsafe { forward_queue_length.increment_unchecked(cs) }; + let new_forward_queue_length = UInt32::conditionally_select( + cs, + *should_read, + &new_forward_queue_length_candidate, + &forward_queue_length, + ); + + relations_buffer.push(( + *should_read, + round_0_initial.map(|el| Num::from_variable(el)), + round_0_final.map(|el| Num::from_variable(el)), + )); + + relations_buffer.push(( + *should_read, + round_1_initial.map(|el| Num::from_variable(el)), + round_1_final.map(|el| Num::from_variable(el)), + )); + + relations_buffer.push(( + *should_read, + round_2_initial.map(|el| Num::from_variable(el)), + round_2_final.map(|el| Num::from_variable(el)), + )); + + ( + new_forward_queue_tail.map(|el| Num::from_variable(el)), + new_forward_queue_length, + ) +} + +pub fn add_to_decommittment_queue< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + relations_buffer: &mut ArrayVec< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >, + should_decommit: &Boolean, + ergs_remaining: &UInt32, + bytecode_hash: &UInt256, + num_words_in_bytecode: &UInt16, + current_decommittment_queue_tail: &[Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + current_decommittment_queue_len: &UInt32, + timestamp_to_use_for_decommittment_request: &UInt32, + target_memory_page: &UInt32, + witness_oracle: &SynchronizedWitnessOracle, + _round_function: &R, +) -> ( + Boolean, + UInt32, + ([Num; FULL_SPONGE_QUEUE_STATE_WIDTH], UInt32), + UInt32, +) +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + // compute any associated extra costs + + let cost_of_decommit_per_word = + UInt32::allocated_constant(cs, zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT); + let num_words_in_bytecode = + unsafe { UInt32::from_variable_unchecked(num_words_in_bytecode.get_variable()) }; + + let cost_of_decommittment = + cost_of_decommit_per_word.non_widening_mul(cs, &num_words_in_bytecode); + + let (ergs_after_decommit_may_be, uf) = + ergs_remaining.overflowing_sub(cs, cost_of_decommittment); + + let not_enough_ergs_to_decommit = uf; + let have_enough_ergs_to_decommit = uf.negated(cs); + let should_decommit = Boolean::multi_and(cs, &[*should_decommit, have_enough_ergs_to_decommit]); + + // if we do not decommit then we will eventually map into 0 page for 0 extra ergs + let ergs_remaining_after_decommit = UInt32::conditionally_select( + cs, + should_decommit, + &ergs_after_decommit_may_be, + &ergs_remaining, + ); + + if crate::config::CIRCUIT_VERSOBE { + if should_decommit.witness_hook(&*cs)().unwrap() { + dbg!(num_words_in_bytecode.witness_hook(&*cs)().unwrap()); + dbg!(ergs_after_decommit_may_be.witness_hook(&*cs)().unwrap()); + dbg!(ergs_remaining_after_decommit.witness_hook(&*cs)().unwrap()); + } + } + + // decommit and return new code page and queue states + + let boolean_false = Boolean::allocated_constant(cs, false); + + let mut decommittment_request = DecommitQuery { + code_hash: *bytecode_hash, + page: *target_memory_page, + is_first: boolean_false, + timestamp: *timestamp_to_use_for_decommittment_request, + }; + + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = + Vec::with_capacity( as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1); + dependencies.push(should_decommit.get_variable().into()); + dependencies.extend(Place::from_variables( + decommittment_request.flatten_as_variables(), + )); + + // we always access witness, as even for writes we have to get a claimed read value! + let suggested_page = UInt32::allocate_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let should_decommit = >::cast_from_source(inputs[0]); + + let mut query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + query.copy_from_slice(&inputs[1..]); + let query: DecommitQueryWitness = + CSAllocatableExt::witness_from_set_of_values(query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = guard.get_decommittment_request_suggested_page(&query, should_decommit); + drop(guard); + + witness + }, + &dependencies, + ); + + let is_first = UInt32::equals(cs, &target_memory_page, &suggested_page); + + decommittment_request.is_first = is_first; + decommittment_request.page = suggested_page; + + // kind of refund if we didn't decommit + + let was_decommitted_before = is_first.negated(cs); + + let refund = Boolean::multi_and(cs, &[should_decommit, was_decommitted_before]); + + let ergs_remaining_after_decommit = + UInt32::conditionally_select(cs, refund, &ergs_remaining, &ergs_remaining_after_decommit); + + use boojum::gadgets::traits::encodable::CircuitEncodable; + + let encoded_request = decommittment_request.encode(cs); + // absorb by replacement + let initial_state = [ + encoded_request[0], + encoded_request[1], + encoded_request[2], + encoded_request[3], + encoded_request[4], + encoded_request[5], + encoded_request[6], + encoded_request[7], + current_decommittment_queue_tail[8].get_variable(), + current_decommittment_queue_tail[9].get_variable(), + current_decommittment_queue_tail[10].get_variable(), + current_decommittment_queue_tail[11].get_variable(), + ]; + + use boojum::gadgets::round_function::simulate_round_function; + + // NOTE: since we do merged call/ret, we simulate proper relations here always, + // because we will do join enforcement on call/ret + + let final_state = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, initial_state, should_decommit); + + relations_buffer.push(( + should_decommit, + initial_state.map(|el| Num::from_variable(el)), + final_state.map(|el| Num::from_variable(el)), + )); + + let final_state = final_state.map(|el| Num::from_variable(el)); + + let new_decommittment_queue_tail = Num::parallel_select( + cs, + should_decommit, + &final_state, + ¤t_decommittment_queue_tail, + ); + + let new_decommittment_queue_len_candidate = + unsafe { current_decommittment_queue_len.increment_unchecked(cs) }; + let new_decommittment_queue_len = UInt32::conditionally_select( + cs, + should_decommit, + &new_decommittment_queue_len_candidate, + ¤t_decommittment_queue_len, + ); + // we use `should_decommit` as a marker that we did actually execute both read and decommittment (whether fresh or not) + + let target_memory_page = decommittment_request.page; + let unmapped_page = UInt32::allocated_constant(cs, UNMAPPED_PAGE); + let target_memory_page = + UInt32::conditionally_select(cs, should_decommit, &target_memory_page, &unmapped_page); + + ( + not_enough_ergs_to_decommit, + target_memory_page, + (new_decommittment_queue_tail, new_decommittment_queue_len), + ergs_remaining_after_decommit, + ) +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/mod.rs b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/mod.rs new file mode 100644 index 00000000..6f2b1758 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/mod.rs @@ -0,0 +1,86 @@ +use crate::main_vm::register_input_view::RegisterInputView; + +use super::*; +use cs_derive::*; + +pub mod far_call; +pub mod near_call; +pub mod ret; + +pub use self::far_call::*; +pub use self::near_call::*; +pub use self::ret::*; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug)] + +pub(crate) struct ForwardingModeABI { + pub(crate) forwarding_mode_byte: UInt8, +} + +impl ForwardingModeABI { + pub fn from_register_view>( + _cs: &mut CS, + input: &RegisterInputView, + ) -> Self { + // higher parts of highest 64 bits + let forwarding_mode_byte = input.u8x32_view + [zkevm_opcode_defs::definitions::abi::far_call::FAR_CALL_FORWARDING_MODE_BYTE_IDX]; + + let new = Self { + forwarding_mode_byte, + }; + + new + } +} + +pub(crate) fn compute_shared_abi_parts>( + cs: &mut CS, + src0_view: &RegisterInputView, +) -> ( + CommonCallRetABI, + FarCallPartialABI, + CallRetForwardingMode, +) { + let far_call_abi = FarCallPartialABI::from_register_view(cs, src0_view); + let forwarding_mode_abi = ForwardingModeABI::from_register_view(cs, &src0_view); + // we can share some checks + + let use_aux_heap_marker = + UInt8::allocated_constant(cs, FarCallForwardPageType::UseAuxHeap as u8); + let forward_fat_pointer_marker = + UInt8::allocated_constant(cs, FarCallForwardPageType::ForwardFatPointer as u8); + + let call_ret_use_aux_heap = UInt8::equals( + cs, + &forwarding_mode_abi.forwarding_mode_byte, + &use_aux_heap_marker, + ); + let call_ret_forward_fat_pointer = UInt8::equals( + cs, + &forwarding_mode_abi.forwarding_mode_byte, + &forward_fat_pointer_marker, + ); + let call_ret_use_heap = + Boolean::multi_or(cs, &[call_ret_use_aux_heap, call_ret_forward_fat_pointer]).negated(cs); + + let do_not_forward_ptr = call_ret_forward_fat_pointer.negated(cs); + + let (fat_ptr, upper_bound, ptr_validation_data) = + FatPtrInABI::parse_and_validate(cs, src0_view, do_not_forward_ptr); + + let common_parts = CommonCallRetABI { + fat_ptr, + upper_bound, + ptr_validation_data, + }; + + let forwarding_mode = CallRetForwardingMode { + use_heap: call_ret_use_heap, + use_aux_heap: call_ret_use_aux_heap, + forward_fat_pointer: call_ret_forward_fat_pointer, + }; + + (common_parts, far_call_abi, forwarding_mode) +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/near_call.rs b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/near_call.rs new file mode 100644 index 00000000..54a57eda --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/near_call.rs @@ -0,0 +1,184 @@ +use super::*; + +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecordWitness; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::castable::WitnessCastable; + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub(crate) struct NearCallData { + pub(crate) apply_near_call: Boolean, + pub(crate) old_context: ExecutionContextRecord, + pub(crate) new_context: ExecutionContextRecord, + // we do not need to change queues on call +} + +struct NearCallABI { + ergs_passed: UInt32, +} + +use crate::main_vm::register_input_view::RegisterInputView; + +impl NearCallABI { + fn from_register_view(input: &RegisterInputView) -> Self { + Self { + ergs_passed: input.u32x8_view[0], + } + } +} + +pub(crate) fn callstack_candidate_for_near_call< + F: SmallField, + CS: ConstraintSystem, + W: WitnessOracle, +>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + witness_oracle: &SynchronizedWitnessOracle, +) -> NearCallData +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + // new callstack should be just the same a the old one, but we also need to update the pricing for pubdata in the rare case + const NEAR_CALL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::NearCall(zkevm_opcode_defs::NearCallOpcode); + + let execute = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(NEAR_CALL_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (execute.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying NEAR CALL"); + } + } + + let mut current_callstack_entry = draft_vm_state.callstack.current_context.saved_context; + + // perform all known modifications, like PC/SP saving + current_callstack_entry.pc = opcode_carry_parts.next_pc; + + // for NEAR CALL the next callstack entry is largely the same + let mut new_callstack_entry = current_callstack_entry.clone(); + // on call-like path we continue the forward queue, but have to allocate the rollback queue state from witness + let call_timestamp = draft_vm_state.timestamp; + + let oracle = witness_oracle.clone(); + let dependencies = [ + execute.get_variable().into(), + call_timestamp.get_variable().into(), + ]; + let potential_rollback_queue_segment_tail = + Num::allocate_multiple_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let execute = >::cast_from_source(inputs[0]); + let timestamp = >::cast_from_source(inputs[1]); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = guard.get_rollback_queue_tail_witness_for_call(timestamp, execute); + drop(guard); + + witness + }, + &dependencies, + ); + + let zero_u32 = UInt32::zero(cs); + + new_callstack_entry.reverted_queue_tail = potential_rollback_queue_segment_tail; + new_callstack_entry.reverted_queue_head = potential_rollback_queue_segment_tail; + new_callstack_entry.reverted_queue_segment_len = zero_u32; + + let dst_pc = common_opcode_state.decoded_opcode.imm0; + let eh_pc = common_opcode_state.decoded_opcode.imm1; + + let near_call_abi = NearCallABI::from_register_view(&common_opcode_state.src0_view); + let pass_all_ergs = near_call_abi.ergs_passed.is_zero(cs); + + let preliminary_ergs_left = opcode_carry_parts.preliminary_ergs_left; + + // we did spend some ergs on decoding, so we use one from prestate + let ergs_to_pass = UInt32::conditionally_select( + cs, + pass_all_ergs, + &preliminary_ergs_left, + &near_call_abi.ergs_passed, + ); + + let (remaining_for_this_context, uf) = preliminary_ergs_left.overflowing_sub(cs, ergs_to_pass); + + let remaining_ergs_if_pass = remaining_for_this_context; + let passed_ergs_if_pass = ergs_to_pass; + + // if underflow than we pass everything! + let remaining_ergs_if_pass = + UInt32::conditionally_select(cs, uf, &zero_u32, &remaining_ergs_if_pass); + + let passed_ergs_if_pass = + UInt32::conditionally_select(cs, uf, &preliminary_ergs_left, &passed_ergs_if_pass); + + current_callstack_entry.ergs_remaining = remaining_ergs_if_pass; + + let oracle = witness_oracle.clone(); + let mut dependencies = Vec::with_capacity( + as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 2, + ); + dependencies.push(execute.get_variable().into()); + dependencies.push( + draft_vm_state + .callstack + .context_stack_depth + .get_variable() + .into(), + ); + dependencies.extend(Place::from_variables( + current_callstack_entry.flatten_as_variables(), + )); + + let _: [Num; 0] = Num::allocate_multiple_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let execute = >::cast_from_source(inputs[0]); + let current_depth = >::cast_from_source(inputs[1]); + + let mut context = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + context.copy_from_slice(&inputs[2..]); + let context: ExecutionContextRecordWitness = + CSAllocatableExt::witness_from_set_of_values(context); + + let mut guard = oracle.inner.write().expect("not poisoned"); + guard.push_callstack_witness(&context, current_depth, execute); + drop(guard); + + [] + }, + &dependencies, + ); + + // --------------------- + // actually "apply" far call + + let boolean_true = Boolean::allocated_constant(cs, true); + + new_callstack_entry.ergs_remaining = passed_ergs_if_pass; + new_callstack_entry.pc = dst_pc; + new_callstack_entry.exception_handler_loc = eh_pc; + new_callstack_entry.is_local_call = boolean_true; + + let full_data = NearCallData { + apply_near_call: execute, + old_context: current_callstack_entry, + new_context: new_callstack_entry, + }; + + full_data +} 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 new file mode 100644 index 00000000..6f9773e4 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/call_ret_impl/ret.rs @@ -0,0 +1,463 @@ +use crate::base_structures::{register::VMRegister, vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH}; +use boojum::config::*; +use boojum::cs::traits::cs::DstBuffer; + +use super::*; + +use crate::base_structures::vm_state::saved_context::ExecutionContextRecord; +use crate::base_structures::vm_state::QUEUE_STATE_WIDTH; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; + +use arrayvec::ArrayVec; + +pub(crate) struct RetData { + pub(crate) apply_ret: Boolean, + pub(crate) is_panic: Boolean, + pub(crate) did_return_from_far_call: Boolean, + pub(crate) originally_popped_context: ExecutionContextRecord, + pub(crate) new_context: ExecutionContextRecord, + pub(crate) previous_callstack_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + pub(crate) new_forward_queue_tail: [Num; QUEUE_STATE_WIDTH], // after we glue + pub(crate) new_forward_queue_len: UInt32, + pub(crate) specific_registers_updates: [Option<(Boolean, VMRegister)>; REGISTERS_COUNT], + pub(crate) specific_registers_zeroing: [Option>; REGISTERS_COUNT], + pub(crate) remove_ptr_on_specific_registers: [Option>; REGISTERS_COUNT], +} + +pub(crate) fn callstack_candidate_for_ret< + F: SmallField, + CS: ConstraintSystem, + W: WitnessOracle, +>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + witness_oracle: &SynchronizedWitnessOracle, + common_abi_parts: &CommonCallRetABI, + forwarding_data: &CallRetForwardingMode, +) -> RetData +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + // new callstack should be just the same a the old one, but we also need to update the pricing for pubdata in the rare case + const RET_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Ret(zkevm_opcode_defs::RetOpcode::Ok); + + let execute = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(RET_OPCODE); + + let is_ret_ok = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(zkevm_opcode_defs::Opcode::Ret( + zkevm_opcode_defs::RetOpcode::Ok, + )); + // revert and panic are different only in ABI: whether we zero-out any hints (returndata) about why we reverted or not + let is_ret_revert = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(zkevm_opcode_defs::Opcode::Ret( + zkevm_opcode_defs::RetOpcode::Revert, + )); + let is_ret_panic = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(zkevm_opcode_defs::Opcode::Ret( + zkevm_opcode_defs::RetOpcode::Panic, + )); + + let is_local_frame = draft_vm_state + .callstack + .current_context + .saved_context + .is_local_call; + + if crate::config::CIRCUIT_VERSOBE { + if execute.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying RET"); + if is_local_frame.witness_hook(&*cs)().unwrap_or(false) { + println!("Is local RET"); + } else { + println!("Is global RET"); + } + + if is_ret_ok.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying RET Ok"); + } + if is_ret_revert.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying RET Revert"); + } + if is_ret_panic.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying RET Panic"); + } + } + } + + let current_callstack_entry = draft_vm_state.callstack.current_context.saved_context; + + // we may want to return to label + let is_to_label = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[zkevm_opcode_defs::ret::RET_TO_LABEL_BIT_IDX]; + + let label_pc = common_opcode_state.decoded_opcode.imm0; + + let current_depth = draft_vm_state.callstack.context_stack_depth; + + // it's a composite allocation, so we handwrite it + + let (mut new_callstack_entry, previous_callstack_state) = { + // this applies necessary constraints + let raw_callstack_entry = ExecutionContextRecord::create_without_value(cs); + let raw_previous_callstack_state = + cs.alloc_multiple_variables_without_values::(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let oracle = witness_oracle.clone(); + + let dependencies = [ + current_depth.get_variable().into(), + execute.get_variable().into(), + ]; + + let mut outputs_to_set = Vec::with_capacity( + as CSAllocatableExt>::INTERNAL_STRUCT_LEN + + FULL_SPONGE_QUEUE_STATE_WIDTH, + ); + outputs_to_set.extend(Place::from_variables( + raw_callstack_entry.flatten_as_variables(), + )); + outputs_to_set.extend(Place::from_variables(raw_previous_callstack_state)); + + cs.set_values_with_dependencies_vararg( + &dependencies, + &outputs_to_set, + move |inputs: &[F], buffer: &mut DstBuffer<'_, '_, F>| { + let callstack_depth = + >::cast_from_source(inputs[0]); + let execute = >::cast_from_source(inputs[1]); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let (record_witness, previous_state) = + guard.get_callstack_witness(execute, callstack_depth); + drop(guard); + + ExecutionContextRecord::set_internal_variables_values(record_witness, buffer); + buffer.extend(previous_state); + }, + ); + } + + let previous_callstack_state = + raw_previous_callstack_state.map(|el| Num::from_variable(el)); + + (raw_callstack_entry, previous_callstack_state) + }; + + let originally_popped_context = new_callstack_entry; + + // pass back all the ergs (after we paid the cost of "ret" itself), + // with may be a small charge for memory growth + let preliminary_ergs_left = opcode_carry_parts.preliminary_ergs_left; + + // resolve some exceptions over fat pointer use and memory growth + + // exceptions that are specific only to return from non-local frame + 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 is_far_return = is_local_frame.negated(cs); + + // resolve returndata pointer if forwarded + let fat_ptr_expected_exception = + Boolean::multi_and(cs, &[forward_fat_pointer, src0_is_integer, is_far_return]); + non_local_frame_exceptions.push(fat_ptr_expected_exception); + + let do_not_forward_ptr = forward_fat_pointer.negated(cs); + + // we also want unidirectional movement of returndata + // check if fat_ptr.memory_page < ctx.base_page and throw if it's the case + let (_, uf) = common_abi_parts.fat_ptr.page.overflowing_sub( + cs, + draft_vm_state + .callstack + .current_context + .saved_context + .base_page, + ); + + // if we try to forward then we should be unidirectional + let non_unidirectional_forwarding = Boolean::multi_and(cs, &[forward_fat_pointer, uf]); + + non_local_frame_exceptions.push(non_unidirectional_forwarding); + + non_local_frame_exceptions.push(is_ret_panic); // just feed it here as a shorthand + + let exceptions_collapsed = Boolean::multi_or(cs, &non_local_frame_exceptions); + + let fat_ptr = common_abi_parts + .fat_ptr + .mask_into_empty(cs, exceptions_collapsed); + + // now we can modify fat ptr that is prevalidated + + let fat_ptr_adjusted_if_forward = fat_ptr.readjust(cs); + + let page = UInt32::conditionally_select( + cs, + forwarding_data.use_heap, + &opcode_carry_parts.heap_page, + &opcode_carry_parts.aux_heap_page, + ); + + let zero_u32 = UInt32::zero(cs); + + let fat_ptr_for_heaps = FatPtrInABI { + offset: zero_u32, + page, + start: fat_ptr.start, + length: fat_ptr.length, + }; + + let fat_ptr = FatPtrInABI::conditionally_select( + cs, + forwarding_data.forward_fat_pointer, + &fat_ptr_adjusted_if_forward, + &fat_ptr_for_heaps, + ); + + // potentially pay for memory growth + + let memory_region_is_not_addressable = common_abi_parts.ptr_validation_data.is_non_addressable; + let upper_bound = common_abi_parts.upper_bound; + // first mask to 0 if exceptions happened + let upper_bound = upper_bound.mask_negated(cs, exceptions_collapsed); + // then compute to penalize for out of memory access attemp + + // and penalize if pointer is fresh and not addressable + let penalize_heap_overflow = + Boolean::multi_and(cs, &[memory_region_is_not_addressable, do_not_forward_ptr]); + let u32_max = UInt32::allocated_constant(cs, u32::MAX); + + let upper_bound = + UInt32::conditionally_select(cs, penalize_heap_overflow, &u32_max, &upper_bound); + + let heap_max_accessed = upper_bound.mask(cs, forwarding_data.use_heap); + let heap_bound = current_callstack_entry.heap_upper_bound; + let (mut heap_growth, uf) = heap_max_accessed.overflowing_sub(cs, heap_bound); + heap_growth = heap_growth.mask_negated(cs, uf); // of we access in bounds then it's 0 + let grow_heap = Boolean::multi_and(cs, &[forwarding_data.use_heap, execute, is_far_return]); + + let aux_heap_max_accessed = upper_bound.mask(cs, forwarding_data.use_aux_heap); + let aux_heap_bound = current_callstack_entry.aux_heap_upper_bound; + let (mut aux_heap_growth, uf) = aux_heap_max_accessed.overflowing_sub(cs, aux_heap_bound); + aux_heap_growth = aux_heap_growth.mask_negated(cs, uf); // of we access in bounds then it's 0 + let grow_aux_heap = + Boolean::multi_and(cs, &[forwarding_data.use_aux_heap, execute, is_far_return]); + + let mut growth_cost = heap_growth.mask(cs, grow_heap); + growth_cost = UInt32::conditionally_select(cs, grow_aux_heap, &aux_heap_growth, &growth_cost); + + // subtract + + let (ergs_left_after_growth, uf) = preliminary_ergs_left.overflowing_sub(cs, growth_cost); + + let mut non_local_frame_exceptions = ArrayVec::, 4>::new(); + non_local_frame_exceptions.push(exceptions_collapsed); + + let ergs_left_after_growth = ergs_left_after_growth.mask_negated(cs, uf); // if not enough - set to 0 + non_local_frame_exceptions.push(uf); + + let ergs_left_after_growth = UInt32::conditionally_select( + cs, + is_local_frame, + &preliminary_ergs_left, + &ergs_left_after_growth, + ); + + non_local_frame_exceptions.push(is_ret_panic); + + let non_local_frame_panic = Boolean::multi_or(cs, &non_local_frame_exceptions); + let non_local_frame_panic = Boolean::multi_and(cs, &[non_local_frame_panic, is_far_return]); + let final_fat_ptr = fat_ptr.mask_into_empty(cs, non_local_frame_panic); + + // ----------------------------------------- + + let new_ergs_left = + ergs_left_after_growth.add_no_overflow(cs, new_callstack_entry.ergs_remaining); + + new_callstack_entry.ergs_remaining = new_ergs_left; + + // resolve merging of the queues + + // most likely it's the most interesting amount all the tricks that are pulled by this VM + + // During the execution we maintain the following queue segments of what is usually called a "storage log", that is basically a sequence of bookkeeped + // storage, events, precompiles, etc accesses + // - global "forward" queue - all the changes (both rollbackable and not (read-like)) go in there, and it's "global" per block + // - frame-specific "reverts" queue, where we put "canceling" state updates for all "write-like" things, like storage write, event, + // l1 message, etc. E.g. precompilecall is pure function and doesn't rollback, and we add nothing to this segment + // When frame ends we have to decide whether we discard it's changes or not. So we can do either: + // - if frame does NOT revert then we PREPEND all the changes in "rollback" segment to the rollback segment of the parent queue + // - if frame DOES revert, then we APPEND all the changes from "rollback" to the global "forward" segment + // It's easy to notice that this behavior is: + // - local O(1): only things like heads/tails of the queues are updated. Changes do accumulate along the O(N) potential changes in a frame, but + // then we can apply it O(1) + // - recursively consistent as one would expect it: if this frame does NOT revert, but parent REVERTS, then all the changes are rolled back! + + // Why one can not do simpler and just memorize the state of some "forward" queue on frame entry and return to it when revert happens? Because we can have + // a code like + // if (SLOAD(x)) { + // revert(0, 0) + // } else { + // .. something useful + // } + + // then we branch on result of SLOAD, but it is not observable (we discarded everything in "forward" queue)! So it can be maliciously manipulated! + + // if we revert then we should append rollback to forward + // if we return ok then we should prepend to the rollback of the parent + + let should_perform_revert = + Boolean::multi_or(cs, &[is_ret_revert, is_ret_panic, non_local_frame_panic]); + let perform_revert = Boolean::multi_and(cs, &[execute, should_perform_revert]); + + for (a, b) in current_callstack_entry.reverted_queue_head.iter().zip( + draft_vm_state + .callstack + .current_context + .log_queue_forward_tail + .iter(), + ) { + Num::conditionally_enforce_equal(cs, perform_revert, a, b); + } + + let new_forward_queue_len_if_revert = draft_vm_state + .callstack + .current_context + .log_queue_forward_part_length + .add_no_overflow(cs, current_callstack_entry.reverted_queue_segment_len); + + let no_exceptions = non_local_frame_panic.negated(cs); + + let should_perform_ret_ok = Boolean::multi_and(cs, &[execute, is_ret_ok, no_exceptions]); + + for (a, b) in new_callstack_entry + .reverted_queue_head + .iter() + .zip(current_callstack_entry.reverted_queue_tail.iter()) + { + Num::conditionally_enforce_equal(cs, should_perform_ret_ok, a, b); + } + + let new_rollback_queue_len_if_ok = new_callstack_entry + .reverted_queue_segment_len + .add_no_overflow(cs, current_callstack_entry.reverted_queue_segment_len); + + // update forward queue + + let new_forward_queue_tail = Num::parallel_select( + cs, + should_perform_revert, // it's only true if we DO execute and DO revert + ¤t_callstack_entry.reverted_queue_tail, + &draft_vm_state + .callstack + .current_context + .log_queue_forward_tail, + ); + + let new_forward_queue_len = UInt32::conditionally_select( + cs, + should_perform_revert, + &new_forward_queue_len_if_revert, + &draft_vm_state + .callstack + .current_context + .log_queue_forward_part_length, + ); + + // update rollback queue of the parent + let new_rollback_queue_head = Num::parallel_select( + cs, + should_perform_ret_ok, // it's only true if we DO execute and DO return ok + ¤t_callstack_entry.reverted_queue_head, + &new_callstack_entry.reverted_queue_head, + ); + + let new_rollback_queue_len = UInt32::conditionally_select( + cs, + should_perform_ret_ok, + &new_rollback_queue_len_if_ok, + &new_callstack_entry.reverted_queue_segment_len, + ); + + new_callstack_entry.reverted_queue_head = new_rollback_queue_head; + new_callstack_entry.reverted_queue_segment_len = new_rollback_queue_len; + + // we ignore label if we return from the root, of course + let should_use_label = Boolean::multi_and(cs, &[is_to_label, is_local_frame]); + + // Candidates for PC to return to + let ok_ret_pc = + UInt16::conditionally_select(cs, should_use_label, &label_pc, &new_callstack_entry.pc); + // but EH is stored in the CURRENT context + let eh_pc = UInt16::conditionally_select( + cs, + should_use_label, + &label_pc, + ¤t_callstack_entry.exception_handler_loc, + ); + + let dst_pc = UInt16::conditionally_select(cs, perform_revert, &eh_pc, &ok_ret_pc); + + new_callstack_entry.pc = dst_pc; + + // and update registers following our ABI rules + + // everything goes into r1, and the rest is cleared + let new_r1 = final_fat_ptr.into_register(cs); + let update_specific_registers_on_ret = Boolean::multi_and(cs, &[execute, is_far_return]); + + let mut specific_registers_updates = [None; REGISTERS_COUNT]; + specific_registers_updates[0] = Some((update_specific_registers_on_ret, new_r1)); + + let is_panic = Boolean::multi_or(cs, &[is_ret_panic, non_local_frame_panic]); + + // the rest is cleared on far return + + let mut register_zero_out = [None; REGISTERS_COUNT]; + + for reg_idx in 1..REGISTERS_COUNT { + register_zero_out[reg_idx as usize] = Some(update_specific_registers_on_ret); + } + + // erase markers everywhere anyway + let mut erase_ptr_markers = [None; REGISTERS_COUNT]; + + for reg_idx in 1..REGISTERS_COUNT { + erase_ptr_markers[reg_idx as usize] = Some(update_specific_registers_on_ret); + } + + let full_data = RetData { + apply_ret: execute, + is_panic: is_panic, + did_return_from_far_call: is_far_return, + new_context: new_callstack_entry, + originally_popped_context, + previous_callstack_state, + new_forward_queue_tail, + new_forward_queue_len, + specific_registers_updates, + specific_registers_zeroing: register_zero_out, + remove_ptr_on_specific_registers: erase_ptr_markers, + }; + + full_data +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/context.rs b/crates/zkevm_circuits/src/main_vm/opcodes/context.rs new file mode 100644 index 00000000..69b3f8ec --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/context.rs @@ -0,0 +1,307 @@ +use boojum::gadgets::u256::UInt256; + +use crate::base_structures::register::VMRegister; + +use super::*; + +pub(crate) fn apply_context>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + const GET_THIS_ADDRESS_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::This, + ); + const GET_CALLER_ADDRESS_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::Caller, + ); + const GET_CODE_ADDRESS_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::CodeAddress, + ); + const GET_META_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::Meta, + ); + const GET_ERGS_LEFT_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::ErgsLeft, + ); + const GET_SP_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::Sp, + ); + const GET_CONTEXT_U128_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::GetContextU128, + ); + // attempt to execute in non-kernel mode for this opcode would be caught before + const SET_CONTEXT_U128_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::SetContextU128, + ); + const SET_PUBDATA_ERGS_OPCODE: zkevm_opcode_defs::Opcode = zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::SetErgsPerPubdataByte, + ); + const INCREMENT_TX_NUMBER_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Context( + zkevm_opcode_defs::definitions::context::ContextOpcode::IncrementTxNumber, + ); + + let should_apply = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(GET_THIS_ADDRESS_OPCODE) + }; + + let is_retrieve_this = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_THIS_ADDRESS_OPCODE) + }; + let is_retrieve_caller = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_CALLER_ADDRESS_OPCODE) + }; + let is_retrieve_code_address = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_CODE_ADDRESS_OPCODE) + }; + let is_retrieve_meta = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_META_OPCODE) + }; + let is_retrieve_ergs_left = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_ERGS_LEFT_OPCODE) + }; + let _is_retrieve_sp = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_SP_OPCODE) + }; + let is_get_context_u128 = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(GET_CONTEXT_U128_OPCODE) + }; + let is_set_context_u128 = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(SET_CONTEXT_U128_OPCODE) + }; + let is_set_pubdata_ergs = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(SET_PUBDATA_ERGS_OPCODE) + }; + let is_inc_tx_num = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(INCREMENT_TX_NUMBER_OPCODE) + }; + + let write_to_context = Boolean::multi_and(cs, &[should_apply, is_set_context_u128]); + let set_pubdata_ergs = Boolean::multi_and(cs, &[should_apply, is_set_pubdata_ergs]); + let increment_tx_counter = Boolean::multi_and(cs, &[should_apply, is_inc_tx_num]); + + // write in regards of dst0 register + let read_only = Boolean::multi_or( + cs, + &[is_set_context_u128, is_set_pubdata_ergs, is_inc_tx_num], + ); + let write_like = read_only.negated(cs); + + let write_to_dst0 = Boolean::multi_and(cs, &[should_apply, write_like]); + + let potentially_new_ergs_for_pubdata = common_opcode_state.src0_view.u32x8_view[0]; + + let one_u32 = UInt32::allocated_constant(cs, 1u32); + let (incremented_tx_number, _of) = draft_vm_state + .tx_number_in_block + .overflowing_add(cs, one_u32); + + let context_composite_to_set = [ + common_opcode_state.src0_view.u32x8_view[0], + common_opcode_state.src0_view.u32x8_view[1], + common_opcode_state.src0_view.u32x8_view[2], + common_opcode_state.src0_view.u32x8_view[3], + ]; + + let zero_u32 = UInt32::zero(cs); + let zero_u8 = UInt8::zero(cs); + + let meta_highest_u32 = UInt32::from_le_bytes( + cs, + [ + draft_vm_state + .callstack + .current_context + .saved_context + .this_shard_id, + draft_vm_state + .callstack + .current_context + .saved_context + .caller_shard_id, + draft_vm_state + .callstack + .current_context + .saved_context + .code_shard_id, + zero_u8, + ], + ); + + let meta_as_register = UInt256 { + inner: [ + draft_vm_state.ergs_per_pubdata_byte, + zero_u32, // reserved + draft_vm_state + .callstack + .current_context + .saved_context + .heap_upper_bound, + draft_vm_state + .callstack + .current_context + .saved_context + .aux_heap_upper_bound, + zero_u32, // reserved + zero_u32, // reserved + zero_u32, // reserved + meta_highest_u32, + ], + }; + + // now we will select in the growding width manner + + let low_u32_to_get_sp = unsafe { + UInt32::from_variable_unchecked( + draft_vm_state + .callstack + .current_context + .saved_context + .sp + .get_variable(), + ) + }; + + let low_u32_ergs_left = opcode_carry_parts.preliminary_ergs_left; + + let low_u32 = UInt32::conditionally_select( + cs, + is_retrieve_ergs_left, + &low_u32_ergs_left, + &low_u32_to_get_sp, + ); + + // now we have context + + let mut result_128 = [low_u32, zero_u32, zero_u32, zero_u32]; + + result_128 = UInt32::parallel_select( + cs, + is_get_context_u128, + &draft_vm_state + .callstack + .current_context + .saved_context + .context_u128_value_composite, + &result_128, + ); + + // then we have address-like values + + let mut result_160 = [ + result_128[0], + result_128[1], + result_128[2], + result_128[3], + zero_u32, + ]; + + result_160 = UInt32::parallel_select( + cs, + is_retrieve_this, + &draft_vm_state + .callstack + .current_context + .saved_context + .this + .inner, + &result_160, + ); + + result_160 = UInt32::parallel_select( + cs, + is_retrieve_caller, + &draft_vm_state + .callstack + .current_context + .saved_context + .caller + .inner, + &result_160, + ); + + result_160 = UInt32::parallel_select( + cs, + is_retrieve_code_address, + &draft_vm_state + .callstack + .current_context + .saved_context + .code_address + .inner, + &result_160, + ); + + // and finally full register for meta + + let mut result_256 = [ + result_160[0], + result_160[1], + result_160[2], + result_160[3], + result_160[4], + zero_u32, + zero_u32, + zero_u32, + ]; + + result_256 = + UInt32::parallel_select(cs, is_retrieve_meta, &meta_as_register.inner, &result_256); + + let boolean_false = Boolean::allocated_constant(cs, false); + + let dst0 = VMRegister { + is_pointer: boolean_false, + value: UInt256 { inner: result_256 }, + }; + let can_write_into_memory = + GET_THIS_ADDRESS_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, write_to_dst0, dst0)); + + diffs_accumulator + .context_u128_candidates + .push((write_to_context, context_composite_to_set)); + debug_assert!(diffs_accumulator.new_tx_number.is_none()); + diffs_accumulator.new_tx_number = Some((increment_tx_counter, incremented_tx_number)); + debug_assert!(diffs_accumulator.new_ergs_per_pubdata.is_none()); + diffs_accumulator.new_ergs_per_pubdata = + Some((set_pubdata_ergs, potentially_new_ergs_for_pubdata)); +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/jump.rs b/crates/zkevm_circuits/src/main_vm/opcodes/jump.rs new file mode 100644 index 00000000..a14751a3 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/jump.rs @@ -0,0 +1,38 @@ +use super::*; + +pub(crate) fn apply_jump>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + const JUMP_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Jump(zkevm_opcode_defs::definitions::jump::JumpOpcode); + + let should_apply = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(JUMP_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (should_apply.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying JUMP"); + } + } + + // main point of merging add/sub is to enforce single add/sub relation, that doesn't leak into any + // other opcodes + + let jump_dst = UInt16::from_le_bytes( + cs, + [ + common_opcode_state.src0_view.u8x32_view[0], + common_opcode_state.src0_view.u8x32_view[1], + ], + ); + + diffs_accumulator + .new_pc_candidates + .push((should_apply, jump_dst)); +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/log.rs b/crates/zkevm_circuits/src/main_vm/opcodes/log.rs new file mode 100644 index 00000000..83b3338b --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/log.rs @@ -0,0 +1,667 @@ +use boojum::gadgets::u256::UInt256; + +use crate::base_structures::{ + log_query::{self, LogQuery, LOG_QUERY_PACKED_WIDTH, ROLLBACK_PACKING_FLAG_VARIABLE_IDX}, + register::VMRegister, +}; + +use super::*; +use crate::main_vm::opcodes::log::log_query::LogQueryWitness; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +pub(crate) fn apply_log< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, + witness_oracle: &SynchronizedWitnessOracle, + round_function: &R, +) where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + const STORAGE_READ_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Log(LogOpcode::StorageRead); + const STORAGE_WRITE_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Log(LogOpcode::StorageWrite); + const L1_MESSAGE_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Log(LogOpcode::ToL1Message); + const EVENT_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Log(LogOpcode::Event); + const PRECOMPILE_CALL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Log(LogOpcode::PrecompileCall); + + let should_apply = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(STORAGE_READ_OPCODE); + + let is_storage_read = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(STORAGE_READ_OPCODE) + }; + let is_storage_write = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(STORAGE_WRITE_OPCODE) + }; + let is_event = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(EVENT_OPCODE) + }; + let is_l1_message = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(L1_MESSAGE_OPCODE) + }; + let is_precompile = { + common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(PRECOMPILE_CALL_OPCODE) + }; + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying LOG"); + if is_storage_read.witness_hook(&*cs)().unwrap_or(false) { + println!("SLOAD"); + } + if is_storage_write.witness_hook(&*cs)().unwrap_or(false) { + println!("SSTORE"); + } + if is_event.witness_hook(&*cs)().unwrap_or(false) { + println!("EVENT"); + } + if is_l1_message.witness_hook(&*cs)().unwrap_or(false) { + println!("L2 to L1 message"); + } + if is_precompile.witness_hook(&*cs)().unwrap_or(false) { + println!("PRECOMPILECALL"); + } + } + } + + let address = draft_vm_state.callstack.current_context.saved_context.this; + + let mut key = UInt256 { + inner: common_opcode_state.src0_view.u32x8_view, + }; + let written_value = UInt256 { + inner: common_opcode_state.src1_view.u32x8_view, + }; + + // modify the key by replacing parts for precompile call + let precompile_memory_page_to_read = opcode_carry_parts.heap_page; + let precompile_memory_page_to_write = opcode_carry_parts.heap_page; + // replace bits 128..160 and 160..192 + key.inner[4] = UInt32::conditionally_select( + cs, + is_precompile, + &precompile_memory_page_to_read, + &key.inner[4], + ); + key.inner[5] = UInt32::conditionally_select( + cs, + is_precompile, + &precompile_memory_page_to_write, + &key.inner[5], + ); + + use zkevm_opcode_defs::system_params::{ + INITIAL_STORAGE_WRITE_PUBDATA_BYTES, L1_MESSAGE_PUBDATA_BYTES, + }; + + let is_rollup = draft_vm_state + .callstack + .current_context + .saved_context + .this_shard_id + .is_zero(cs); + let write_to_rollup = Boolean::multi_and(cs, &[is_rollup, is_storage_write]); + + let emit_l1_message = is_l1_message; + + let l1_message_pubdata_bytes_constnt = + UInt32::allocated_constant(cs, L1_MESSAGE_PUBDATA_BYTES as u32); + let ergs_to_burn_for_l1_message = draft_vm_state + .ergs_per_pubdata_byte + .non_widening_mul(cs, &l1_message_pubdata_bytes_constnt); + + let ergs_to_burn_for_precompile_call = common_opcode_state.src1_view.u32x8_view[0]; + + let is_storage_access = Boolean::multi_or(cs, &[is_storage_read, is_storage_write]); + let is_nonrevertable = Boolean::multi_or(cs, &[is_storage_read, is_precompile]); + let is_revertable = is_nonrevertable.negated(cs); + + let aux_byte_variable = Num::linear_combination( + cs, + &[ + ( + is_storage_access.get_variable(), + F::from_u64_unchecked(zkevm_opcode_defs::system_params::STORAGE_AUX_BYTE as u64), + ), + ( + is_event.get_variable(), + F::from_u64_unchecked(zkevm_opcode_defs::system_params::EVENT_AUX_BYTE as u64), + ), + ( + is_l1_message.get_variable(), + F::from_u64_unchecked(zkevm_opcode_defs::system_params::L1_MESSAGE_AUX_BYTE as u64), + ), + ( + is_precompile.get_variable(), + F::from_u64_unchecked(zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE as u64), + ), + ], + ) + .get_variable(); + + let aux_byte = unsafe { UInt8::from_variable_unchecked(aux_byte_variable) }; + let timestamp = common_opcode_state.timestamp_for_first_decommit_or_precompile_read; + + let shard_id = draft_vm_state + .callstack + .current_context + .saved_context + .this_shard_id; + + // NOTE: our opcodes encoding guarantees that there is no "storage read + is first" + // variant encodable + let is_event_init = { + common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[FIRST_MESSAGE_FLAG_IDX] + }; + + let zero_u256 = UInt256::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + let tx_number = draft_vm_state.tx_number_in_block; + + let mut log = LogQuery { + address, + key, + read_value: zero_u256, + written_value, + rw_flag: is_revertable, + aux_byte, + rollback: boolean_false, + is_service: is_event_init, + shard_id, + tx_number_in_block: tx_number, + timestamp, + }; + + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = + Vec::with_capacity( as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 2); + dependencies.push(is_storage_write.get_variable().into()); + dependencies.push(should_apply.get_variable().into()); + dependencies.extend(Place::from_variables(log.flatten_as_variables())); + + let pubdata_refund = UInt32::allocate_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let is_write = >::cast_from_source(inputs[0]); + let execute = >::cast_from_source(inputs[1]); + let mut log_query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + log_query.copy_from_slice(&inputs[2..]); + let log_query: LogQueryWitness = + CSAllocatableExt::witness_from_set_of_values(log_query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = guard.get_refunds(&log_query, is_write, execute); + drop(guard); + + witness + }, + &dependencies, + ); + + let initial_storage_write_pubdata_bytes = + UInt32::allocated_constant(cs, INITIAL_STORAGE_WRITE_PUBDATA_BYTES as u32); + let net_cost = initial_storage_write_pubdata_bytes.sub_no_overflow(cs, pubdata_refund); + + let ergs_to_burn_for_rollup_storage_write = draft_vm_state + .ergs_per_pubdata_byte + .non_widening_mul(cs, &net_cost); + + let zero_u32 = UInt32::allocated_constant(cs, 0); + + // now we know net cost + let ergs_to_burn = UInt32::conditionally_select( + cs, + write_to_rollup, + &ergs_to_burn_for_rollup_storage_write, + &zero_u32, + ); + let ergs_to_burn = UInt32::conditionally_select( + cs, + is_precompile, + &ergs_to_burn_for_precompile_call, + &ergs_to_burn, + ); + let ergs_to_burn = UInt32::conditionally_select( + cs, + emit_l1_message, + &ergs_to_burn_for_l1_message, + &ergs_to_burn, + ); + + let (ergs_remaining, uf) = opcode_carry_parts + .preliminary_ergs_left + .overflowing_sub(cs, ergs_to_burn); + let not_enough_ergs_for_op = uf; + + // if not enough then leave only 0 + let ergs_remaining = ergs_remaining.mask_negated(cs, not_enough_ergs_for_op); + let have_enough_ergs = not_enough_ergs_for_op.negated(cs); + + let execute_either_in_practice = Boolean::multi_and(cs, &[should_apply, have_enough_ergs]); + + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = + Vec::with_capacity( as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 2); + dependencies.push(is_storage_access.get_variable().into()); + dependencies.push(execute_either_in_practice.get_variable().into()); + dependencies.extend(Place::from_variables(log.flatten_as_variables())); + + // we always access witness, as even for writes we have to get a claimed read value! + let read_value = UInt256::allocate_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let is_storage = >::cast_from_source(inputs[0]); + let execute = >::cast_from_source(inputs[1]); + let mut log_query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + log_query.copy_from_slice(&inputs[2..]); + let log_query: LogQueryWitness = + CSAllocatableExt::witness_from_set_of_values(log_query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = guard.get_storage_read_witness(&log_query, is_storage, execute); + drop(guard); + + witness + }, + &dependencies, + ); + + let u256_zero = UInt256::zero(cs); + + let read_value = UInt256::conditionally_select(cs, is_storage_access, &read_value, &u256_zero); + log.read_value = read_value.clone(); + // if we read then use the same value - convension! + log.written_value = + UInt256::conditionally_select(cs, log.rw_flag, &log.written_value, &log.read_value); + + use boojum::gadgets::traits::encodable::CircuitEncodable; + let packed_log_forward = log.encode(cs); + + let mut packed_log_rollback = packed_log_forward; + LogQuery::update_packing_for_rollback(cs, &mut packed_log_rollback); + + let execute_rollback = Boolean::multi_and(cs, &[execute_either_in_practice, is_revertable]); + + let current_forward_tail = draft_vm_state + .callstack + .current_context + .log_queue_forward_tail; + let current_rollback_head = draft_vm_state + .callstack + .current_context + .saved_context + .reverted_queue_head; + + let oracle = witness_oracle.clone(); + let mut dependencies = + Vec::with_capacity( as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1); + dependencies.push(execute_rollback.get_variable().into()); + dependencies.extend(Place::from_variables(log.flatten_as_variables())); + + let prev_revert_head_witness = Num::allocate_multiple_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + let execute_rollback = >::cast_from_source(inputs[0]); + let mut log_query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + log_query.copy_from_slice(&inputs[1..]); + let log_query: LogQueryWitness = + CSAllocatableExt::witness_from_set_of_values(log_query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = guard.get_rollback_queue_witness(&log_query, execute_rollback); + drop(guard); + + witness + }, + &dependencies, + ); + + let (new_forward_queue_tail, new_rollback_queue_head, relations) = + construct_hash_relations_for_log_and_new_queue_states( + cs, + &packed_log_forward, + &packed_log_rollback, + ¤t_forward_tail, + &prev_revert_head_witness, + ¤t_rollback_head, + &execute_either_in_practice, + &execute_rollback, + round_function, + ); + + // add actual update of register in case of write + let register_value_if_storage_read = read_value; + + let mut precompile_call_result = u256_zero; + precompile_call_result.inner[0] = + unsafe { UInt32::from_variable_unchecked(have_enough_ergs.get_variable()) }; + + let register_value = UInt256::conditionally_select( + cs, + is_storage_read, + ®ister_value_if_storage_read, + &precompile_call_result, + ); + + let dst0 = VMRegister { + value: register_value, + is_pointer: boolean_false, + }; + + let old_forward_queue_length = draft_vm_state + .callstack + .current_context + .log_queue_forward_part_length; + + let new_forward_queue_length_candidate = + unsafe { old_forward_queue_length.increment_unchecked(cs) }; + let new_forward_queue_length = UInt32::conditionally_select( + cs, + execute_either_in_practice, + &new_forward_queue_length_candidate, + &old_forward_queue_length, + ); + + let old_revert_queue_length = draft_vm_state + .callstack + .current_context + .saved_context + .reverted_queue_segment_len; + + let new_revert_queue_length_candidate = + unsafe { old_revert_queue_length.increment_unchecked(cs) }; + let new_revert_queue_length = UInt32::conditionally_select( + cs, + execute_rollback, + &new_revert_queue_length_candidate, + &old_revert_queue_length, + ); + + let can_update_dst0 = Boolean::multi_or(cs, &[is_storage_read, is_precompile]); + let should_update_dst0 = Boolean::multi_and(cs, &[can_update_dst0, should_apply]); + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap() { + dbg!(should_update_dst0.witness_hook(&*cs)().unwrap()); + dbg!(dst0.witness_hook(&*cs)().unwrap()); + } + } + + let can_write_into_memory = + STORAGE_READ_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, should_update_dst0, dst0)); + + diffs_accumulator.log_queue_forward_candidates.push(( + should_apply, + new_forward_queue_length, + new_forward_queue_tail, + )); + + diffs_accumulator.log_queue_rollback_candidates.push(( + should_apply, + new_revert_queue_length, + new_rollback_queue_head, + )); + + diffs_accumulator + .new_ergs_left_candidates + .push((should_apply, ergs_remaining)); + + assert!(STORAGE_READ_OPCODE.can_have_src0_from_mem(SUPPORTED_ISA_VERSION) == false); + assert!(STORAGE_READ_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) == false); + + diffs_accumulator + .sponge_candidates_to_run + .push((false, false, should_apply, relations)); +} + +use crate::base_structures::vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH; +use crate::main_vm::state_diffs::MAX_SPONGES_PER_CYCLE; +use arrayvec::ArrayVec; + +fn construct_hash_relations_for_log_and_new_queue_states< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + forward_packed_log: &[Variable; LOG_QUERY_PACKED_WIDTH], + forward_rollback_log: &[Variable; LOG_QUERY_PACKED_WIDTH], + forward_queue_tail: &[Num; 4], + claimed_rollback_head: &[Num; 4], + current_rollback_head: &[Num; 4], + should_execute_either: &Boolean, + should_execute_rollback: &Boolean, + _round_function: &R, +) -> ( + [Num; 4], + [Num; 4], + ArrayVec< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >, +) { + // we should be clever and simultaneously produce 2 relations: + // - 2 common sponges for forward/rollback that only touch the encodings + // - 1 unique sponge for forward + // - 1 unique sponge for rollback + + // check that we only differ at the very end + for (a, b) in forward_packed_log[..ROLLBACK_PACKING_FLAG_VARIABLE_IDX] + .iter() + .zip(forward_rollback_log[..ROLLBACK_PACKING_FLAG_VARIABLE_IDX].iter()) + { + debug_assert_eq!(a, b); + } + + // we absort with replacement + + let mut current_state = R::create_empty_state(cs); + // TODO: may be decide on length specialization + + // absorb by replacement + let round_0_initial = [ + forward_packed_log[0], + forward_packed_log[1], + forward_packed_log[2], + forward_packed_log[3], + forward_packed_log[4], + forward_packed_log[5], + forward_packed_log[6], + forward_packed_log[7], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + use boojum::gadgets::round_function::simulate_round_function; + + let round_0_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_0_initial, *should_execute_either); + + current_state = round_0_final; + + // absorb by replacement + let round_1_initial = [ + forward_packed_log[8], + forward_packed_log[9], + forward_packed_log[10], + forward_packed_log[11], + forward_packed_log[12], + forward_packed_log[13], + forward_packed_log[14], + forward_packed_log[15], + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let round_1_final = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, round_1_initial, *should_execute_either); + + current_state = round_1_final; + + // absorb by replacement + let round_2_initial_forward = [ + forward_packed_log[16], + forward_packed_log[17], + forward_packed_log[18], + forward_packed_log[19], + forward_queue_tail[0].get_variable(), + forward_queue_tail[1].get_variable(), + forward_queue_tail[2].get_variable(), + forward_queue_tail[3].get_variable(), + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let forward_round_2_final = simulate_round_function::<_, _, 8, 12, 4, R>( + cs, + round_2_initial_forward, + *should_execute_either, + ); + + // absorb by replacement + let round_2_initial_rollback = [ + forward_rollback_log[16], + forward_rollback_log[17], + forward_rollback_log[18], + forward_rollback_log[19], + claimed_rollback_head[0].get_variable(), + claimed_rollback_head[1].get_variable(), + claimed_rollback_head[2].get_variable(), + claimed_rollback_head[3].get_variable(), + current_state[8], + current_state[9], + current_state[10], + current_state[11], + ]; + + let rollback_round_2_final = simulate_round_function::<_, _, 8, 12, 4, R>( + cs, + round_2_initial_rollback, + *should_execute_either, + ); // at the moment we do not mark which sponges are actually used and which are not + // in the opcode, so we properly simulate all of them + + let new_forward_tail_candidate = [ + forward_round_2_final[0], + forward_round_2_final[1], + forward_round_2_final[2], + forward_round_2_final[3], + ]; + + let new_forward_tail_candidate = new_forward_tail_candidate.map(|el| Num::from_variable(el)); + + let simulated_rollback_head = [ + rollback_round_2_final[0], + rollback_round_2_final[1], + rollback_round_2_final[2], + rollback_round_2_final[3], + ]; + + let simulated_rollback_head = simulated_rollback_head.map(|el| Num::from_variable(el)); + + // select forward + + let new_forward_queue_tail = Num::parallel_select( + cs, + *should_execute_either, + &new_forward_tail_candidate, + &forward_queue_tail, + ); + + // select rollback + + let new_rollback_queue_head = Num::parallel_select( + cs, + *should_execute_rollback, + &claimed_rollback_head, + ¤t_rollback_head, + ); + + for (a, b) in simulated_rollback_head + .iter() + .zip(current_rollback_head.iter()) + { + Num::conditionally_enforce_equal(cs, *should_execute_rollback, a, b); + } + + let mut relations = ArrayVec::new(); + relations.push(( + *should_execute_either, + round_0_initial.map(|el| Num::from_variable(el)), + round_0_final.map(|el| Num::from_variable(el)), + )); + + relations.push(( + *should_execute_either, + round_1_initial.map(|el| Num::from_variable(el)), + round_1_final.map(|el| Num::from_variable(el)), + )); + + relations.push(( + *should_execute_either, + round_2_initial_forward.map(|el| Num::from_variable(el)), + forward_round_2_final.map(|el| Num::from_variable(el)), + )); + + relations.push(( + *should_execute_rollback, + round_2_initial_rollback.map(|el| Num::from_variable(el)), + rollback_round_2_final.map(|el| Num::from_variable(el)), + )); + + (new_forward_queue_tail, new_rollback_queue_head, relations) +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/mod.rs b/crates/zkevm_circuits/src/main_vm/opcodes/mod.rs new file mode 100644 index 00000000..48055e9f --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/mod.rs @@ -0,0 +1,180 @@ +use super::*; +use crate::base_structures::vm_state::VmLocalState; +use crate::main_vm::opcode_bitmask::SUPPORTED_ISA_VERSION; +use crate::main_vm::pre_state::AfterDecodingCarryParts; +use crate::main_vm::pre_state::CommonOpcodeState; +use crate::main_vm::state_diffs::{ + StateDiffsAccumulator, MAX_U32_CONDITIONAL_RANGE_CHECKS_PER_CYCLE, +}; +use boojum::cs::gates::U8x4FMAGate; +use zkevm_opcode_defs::*; + +pub mod add_sub; +pub mod binop; +pub mod call_ret; +pub mod context; +pub mod jump; +pub mod log; +pub mod mul_div; +pub mod nop; +pub mod ptr; +pub mod shifts; +pub mod uma; + +pub(crate) mod call_ret_impl; + +pub use self::add_sub::*; +pub use self::binop::*; +pub use self::call_ret::*; +pub use self::context::*; +pub use self::jump::*; +pub use self::log::*; +pub use self::mul_div::*; +pub use self::nop::*; +pub use self::ptr::*; +pub use self::ptr::*; +pub use self::shifts::*; +pub use self::uma::*; + +pub struct AddSubRelation { + pub a: [UInt32; 8], + pub b: [UInt32; 8], + pub c: [UInt32; 8], + pub of: Boolean, +} + +impl Selectable for AddSubRelation { + fn conditionally_select>( + cs: &mut CS, + flag: Boolean, + a: &Self, + b: &Self, + ) -> Self { + let sel_a = UInt32::parallel_select(cs, flag, &a.a, &b.a); + let sel_b = UInt32::parallel_select(cs, flag, &a.b, &b.b); + let c = UInt32::parallel_select(cs, flag, &a.c, &b.c); + let of = Boolean::conditionally_select(cs, flag, &a.of, &b.of); + + Self { + a: sel_a, + b: sel_b, + c, + of, + } + } +} + +pub struct MulDivRelation { + pub a: [UInt32; 8], + pub b: [UInt32; 8], + pub rem: [UInt32; 8], + pub mul_low: [UInt32; 8], + pub mul_high: [UInt32; 8], +} + +impl Selectable for MulDivRelation { + fn conditionally_select>( + cs: &mut CS, + flag: Boolean, + a: &Self, + b: &Self, + ) -> Self { + let sel_a = UInt32::parallel_select(cs, flag, &a.a, &b.a); + let sel_b = UInt32::parallel_select(cs, flag, &a.b, &b.b); + let rem = UInt32::parallel_select(cs, flag, &a.rem, &b.rem); + let mul_low = UInt32::parallel_select(cs, flag, &a.mul_low, &b.mul_low); + let mul_high = UInt32::parallel_select(cs, flag, &a.mul_high, &b.mul_high); + + Self { + a: sel_a, + b: sel_b, + rem, + mul_low, + mul_high, + } + } +} + +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::cs::gates::UIntXAddGate; + +pub(crate) fn enforce_addition_relation>( + cs: &mut CS, + relation: AddSubRelation, +) { + let AddSubRelation { a, b, c, of } = relation; + if cs.gate_is_allowed::>() { + let mut intermediate_of = cs.allocate_constant(F::ZERO); + + for ((a, b), c) in a.iter().zip(b.iter()).zip(c.iter()) { + intermediate_of = UIntXAddGate::<32>::enforce_add_relation_compute_carry( + cs, + a.get_variable(), + b.get_variable(), + intermediate_of, + c.get_variable(), + ); + } + + let intermediate_of = unsafe { Boolean::from_variable_unchecked(intermediate_of) }; + + Boolean::enforce_equal(cs, &intermediate_of, &of); + } else { + unimplemented!() + } +} + +// NOTE: fields `a`, `b` and `rem` will be range checked, and fields `mul_low` and `mul_high` are used +// only for equality check with guaranteed 32-bit results, so they are also range checked +pub(crate) fn enforce_mul_relation>( + cs: &mut CS, + relation: MulDivRelation, +) { + let MulDivRelation { + a, + b, + rem, + mul_low, + mul_high, + } = relation; + + // a * b + rem = mul_low + 2^256 * mul_high + + // in case of multiplication rem == 0, a and b are src0 and src1 + // in case of division a = quotient, b = src1, rem is remainder, mul_low = src0 + + if cs.gate_is_allowed::() { + let mut partial_result = [UInt32::zero(cs); 16]; + partial_result[0..8].copy_from_slice(&rem[0..8]); + for a_idx in 0..8 { + let mut intermidiate_overflow = UInt32::zero(cs); + for b_idx in 0..8 { + let [low_wrapped, high_wrapped] = UInt32::fma_with_carry( + cs, + a[a_idx], + b[b_idx], + partial_result[a_idx + b_idx], + intermidiate_overflow, + ); + partial_result[a_idx + b_idx] = low_wrapped.0; + intermidiate_overflow = high_wrapped.0; + } + // place end of chain + if a_idx + 8 < 16 { + partial_result[a_idx + 8] = + partial_result[a_idx + 8].add_no_overflow(cs, intermidiate_overflow); + } else { + let zero_num = Num::zero(cs); + Num::enforce_equal(cs, &zero_num, &intermidiate_overflow.into_num()); + } + } + for (lhs, rhs) in partial_result + .iter() + .zip(mul_low.iter().chain(mul_high.iter())) + { + Num::enforce_equal(cs, &lhs.into_num(), &rhs.into_num()) + } + } else { + unimplemented!() + } +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/mul_div.rs b/crates/zkevm_circuits/src/main_vm/opcodes/mul_div.rs new file mode 100644 index 00000000..12261ae5 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/mul_div.rs @@ -0,0 +1,417 @@ +use self::ethereum_types::U256; +use super::*; + +use crate::base_structures::register::VMRegister; +use crate::base_structures::vm_state::ArithmeticFlagsPort; +use arrayvec::ArrayVec; +use boojum::gadgets::u256::{decompose_u256_as_u32x8, UInt256}; + +fn u256_from_limbs(limbs: &[F]) -> U256 { + debug_assert_eq!(limbs.len(), 8); + + let mut byte_array = [0u8; 32]; + for (dst, limb) in byte_array.array_chunks_mut::<4>().zip(limbs.iter()) { + *dst = (limb.as_u64_reduced() as u32).to_le_bytes(); + } + + U256::from_little_endian(&byte_array) +} + +pub fn allocate_mul_result_unchecked>( + cs: &mut CS, + a: &[UInt32; 8], + b: &[UInt32; 8], +) -> ([UInt32; 8], [UInt32; 8]) { + let limbs_low = cs.alloc_multiple_variables_without_values::<8>(); + let limbs_high = cs.alloc_multiple_variables_without_values::<8>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: [F; 16]| { + let a = u256_from_limbs(&inputs[0..8]); + let b = u256_from_limbs(&inputs[8..16]); + let mut c_bytes = [0u8; 64]; + a.full_mul(b).to_little_endian(&mut c_bytes[..]); + + let mut outputs = [F::ZERO; 16]; + let mut byte_array = [0u8; 4]; + for (in_chunk, out_elem) in c_bytes.chunks(4).zip(outputs.iter_mut()) { + byte_array.copy_from_slice(in_chunk); + let as_u32 = u32::from_le_bytes(byte_array); + *out_elem = F::from_u64_unchecked(as_u32 as u64); + } + + outputs + }; + + let dependencies = Place::from_variables([ + a[0].get_variable(), + a[1].get_variable(), + a[2].get_variable(), + a[3].get_variable(), + a[4].get_variable(), + a[5].get_variable(), + a[6].get_variable(), + a[7].get_variable(), + b[0].get_variable(), + b[1].get_variable(), + b[2].get_variable(), + b[3].get_variable(), + b[4].get_variable(), + b[5].get_variable(), + b[6].get_variable(), + b[7].get_variable(), + ]); + let outputs = Place::from_variables([ + limbs_low[0], + limbs_low[1], + limbs_low[2], + limbs_low[3], + limbs_low[4], + limbs_low[5], + limbs_low[6], + limbs_low[7], + limbs_high[0], + limbs_high[1], + limbs_high[2], + limbs_high[3], + limbs_high[4], + limbs_high[5], + limbs_high[6], + limbs_high[7], + ]); + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + let limbs_low = limbs_low.map(|el| unsafe { UInt32::from_variable_unchecked(el) }); + let limbs_high = limbs_high.map(|el| unsafe { UInt32::from_variable_unchecked(el) }); + + (limbs_low, limbs_high) +} + +// by convention this function set remainder to the dividend if divisor is 0 to satisfy +// mul-div relation. Later in the code we set remainder to 0 in this case for convention +pub(crate) fn allocate_div_result_unchecked>( + cs: &mut CS, + a: &[UInt32; 8], + b: &[UInt32; 8], +) -> ([UInt32; 8], [UInt32; 8]) { + let quotient = cs.alloc_multiple_variables_without_values::<8>(); + let remainder = cs.alloc_multiple_variables_without_values::<8>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: [F; 16]| { + let a = u256_from_limbs(&inputs[0..8]); + let b = u256_from_limbs(&inputs[8..16]); + + let (quotient, remainder) = if b.is_zero() { + (U256::zero(), a) + } else { + a.div_mod(b) + }; + + let mut outputs = [F::ZERO; 16]; + for (dst, src) in outputs[..8] + .iter_mut() + .zip(decompose_u256_as_u32x8(quotient).into_iter()) + { + *dst = F::from_u64_unchecked(src as u64); + } + for (dst, src) in outputs[8..] + .iter_mut() + .zip(decompose_u256_as_u32x8(remainder).into_iter()) + { + *dst = F::from_u64_unchecked(src as u64); + } + + outputs + }; + + let dependencies = Place::from_variables([ + a[0].get_variable(), + a[1].get_variable(), + a[2].get_variable(), + a[3].get_variable(), + a[4].get_variable(), + a[5].get_variable(), + a[6].get_variable(), + a[7].get_variable(), + b[0].get_variable(), + b[1].get_variable(), + b[2].get_variable(), + b[3].get_variable(), + b[4].get_variable(), + b[5].get_variable(), + b[6].get_variable(), + b[7].get_variable(), + ]); + let outputs = Place::from_variables([ + quotient[0], + quotient[1], + quotient[2], + quotient[3], + quotient[4], + quotient[5], + quotient[6], + quotient[7], + remainder[0], + remainder[1], + remainder[2], + remainder[3], + remainder[4], + remainder[5], + remainder[6], + remainder[7], + ]); + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + let quotient = quotient.map(|el| unsafe { UInt32::from_variable_unchecked(el) }); + let remainder = remainder.map(|el| unsafe { UInt32::from_variable_unchecked(el) }); + + (quotient, remainder) +} + +pub fn all_limbs_are_zero>( + cs: &mut CS, + limbs: &[UInt32; 8], +) -> Boolean { + let limb_is_zero = limbs.map(|el| el.is_zero(cs)); + let result_is_zero = Boolean::multi_and(cs, &limb_is_zero); + + result_is_zero +} + +pub fn all_limbs_are_equal>( + cs: &mut CS, + lhs: &[UInt32; 8], + rhs: &[UInt32; 8], +) -> Boolean { + let boolean_false = Boolean::allocated_constant(cs, false); + let mut flags = [boolean_false; 8]; + for ((lhs_limb, rhs_limb), out) in lhs.iter().zip(rhs.iter()).zip(flags.iter_mut()) { + *out = UInt32::equals(cs, lhs_limb, rhs_limb); + } + let result = Boolean::multi_and(cs, &flags); + + result +} + +pub(crate) fn apply_mul_div>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + const MUL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Mul(zkevm_opcode_defs::MulOpcode); + const DIV_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Div(zkevm_opcode_defs::DivOpcode); + + let should_apply_mul = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(MUL_OPCODE); + let should_apply_div = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(DIV_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (should_apply_mul.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying MUL"); + } + if (should_apply_div.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying DIV"); + } + } + + let should_set_flags = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[SET_FLAGS_FLAG_IDX]; + + let src0_view = &common_opcode_state.src0_view.u32x8_view; + let src1_view = &common_opcode_state.src1_view.u32x8_view; + + let (mul_low_unchecked, mul_high_unchecked) = + allocate_mul_result_unchecked(cs, src0_view, src1_view); + let (quotient_unchecked, remainder_unchecked) = + allocate_div_result_unchecked(cs, src0_view, src1_view); + + // if crate::config::CIRCUIT_VERSOBE { + // if (should_apply_mul.witness_hook(&*cs))().unwrap_or(false) || (should_apply_div.witness_hook(&*cs))().unwrap_or(false) { + // dbg!(mul_low_unchecked.witness_hook(&*cs)().unwrap()); + // dbg!(mul_high_unchecked.witness_hook(&*cs)().unwrap()); + // dbg!(quotient_unchecked.witness_hook(&*cs)().unwrap()); + // dbg!(remainder_unchecked.witness_hook(&*cs)().unwrap()); + // } + // } + + // IMPORTANT: MulDiv relation is later enforced via `enforce_mul_relation` function, that effectively range-checks all the fields, + // so we do NOT need range checkes on anything that will go into MulDiv relation + let result_0 = UInt32::parallel_select( + cs, + should_apply_mul, + &mul_low_unchecked, + "ient_unchecked, + ); + let result_1 = UInt32::parallel_select( + cs, + should_apply_mul, + &mul_high_unchecked, + &remainder_unchecked, + ); + + // see below, but in short: + // - if we apply mul then `mul_low_unchecked` is checked as `mul_low_to_enforce`, and `mul_high_unchecked` as `mul_high_to_enforce` + // - if we apply div then `remainder_unchecked` is checked as `rem_to_enforce`, and `quotient_unchecked` as `a_to_enforce` + + // if we mull: src0 * src1 = mul_low + (mul_high << 256) => rem = 0, a = src0, b = src1, mul_low = mul_low, mul_high = mul_high + // if we divide: src0 = q * src1 + rem => rem = rem, a = quotient, b = src1, mul_low = src0, mul_high = 0 + let uint256_zero = UInt256::zero(cs); + + // note that if we do division, then remainder is range-checked by "result_1" above + let rem_to_enforce = UInt32::parallel_select( + cs, + should_apply_mul, + &uint256_zero.inner, + &remainder_unchecked, + ); + let a_to_enforce = + UInt32::parallel_select(cs, should_apply_mul, src0_view, "ient_unchecked); + let b_to_enforce = src1_view.clone(); + let mul_low_to_enforce = + UInt32::parallel_select(cs, should_apply_mul, &mul_low_unchecked, &src0_view); + let mul_high_to_enforce = UInt32::parallel_select( + cs, + should_apply_mul, + &mul_high_unchecked, + &uint256_zero.inner, + ); + + let mul_relation = MulDivRelation { + a: a_to_enforce, + b: b_to_enforce, + rem: rem_to_enforce, + mul_low: mul_low_to_enforce, + mul_high: mul_high_to_enforce, + }; + + // flags which are set in case of executing mul + let high_is_zero = all_limbs_are_zero(cs, &mul_high_unchecked); + let low_is_zero = all_limbs_are_zero(cs, &mul_low_unchecked); + let of_mul = high_is_zero.negated(cs); + let eq_mul = low_is_zero; + let gt_mul = { + let x = of_mul.negated(cs); + let y = eq_mul.negated(cs); + Boolean::multi_and(cs, &[x, y]) + }; + + // flags which are set in case of executing div + let divisor_is_zero = all_limbs_are_zero(cs, src1_view); + let divisor_is_non_zero = divisor_is_zero.negated(cs); + // check if quotient and remainder are 0 + let quotient_is_zero = all_limbs_are_zero(cs, "ient_unchecked); + let remainder_is_zero = all_limbs_are_zero(cs, &remainder_unchecked); + + // check that remainder is smaller than divisor + + // do remainder - divisor + let (subtraction_result_unchecked, remainder_is_less_than_divisor) = + allocate_subtraction_result_unchecked(cs, &remainder_unchecked, src1_view); + + // if we do division then remainder will be range checked, but not the subtraction result + let conditional_range_checks = subtraction_result_unchecked; + + // relation is a + b == c + of * 2^N, + // but we compute d - e + 2^N * borrow = f + + // so we need to shuffle + let addition_relation = AddSubRelation { + a: *src1_view, + b: subtraction_result_unchecked, + c: remainder_unchecked, + of: remainder_is_less_than_divisor, + }; + + // unless divisor is 0 (that we handle separately), + // we require that remainder is < divisor + remainder_is_less_than_divisor.conditionally_enforce_true(cs, divisor_is_non_zero); + + // if divisor is 0, then we assume quotient is zero + quotient_is_zero.conditionally_enforce_true(cs, divisor_is_zero); + // and by convention we set remainder to 0 if we divide by 0 + let mask_remainder_into_zero = Boolean::multi_and(cs, &[should_apply_div, divisor_is_zero]); + let result_1 = result_1.map(|el| el.mask_negated(cs, mask_remainder_into_zero)); + + let of_div = divisor_is_zero; + let eq_div = { + let x = divisor_is_zero.negated(cs); + Boolean::multi_and(cs, &[x, quotient_is_zero]) + }; + let gt_div = { + let y = divisor_is_zero.negated(cs); + Boolean::multi_and(cs, &[y, remainder_is_zero]) + }; + + let of = Boolean::conditionally_select(cs, should_apply_mul, &of_mul, &of_div); + let eq = Boolean::conditionally_select(cs, should_apply_mul, &eq_mul, &eq_div); + let gt = Boolean::conditionally_select(cs, should_apply_mul, >_mul, >_div); + + let candidate_flags = ArithmeticFlagsPort { + overflow_or_less_than: of, + equal: eq, + greater_than: gt, + }; + + let apply_any = Boolean::multi_or(cs, &[should_apply_mul, should_apply_div]); + let dst0 = VMRegister { + is_pointer: Boolean::allocated_constant(cs, false), + value: UInt256 { inner: result_0 }, + }; + let dst1 = VMRegister { + is_pointer: Boolean::allocated_constant(cs, false), + value: UInt256 { inner: result_1 }, + }; + + // if crate::config::CIRCUIT_VERSOBE { + // if (should_apply_mul.witness_hook(&*cs))().unwrap_or(false) || (should_apply_div.witness_hook(&*cs))().unwrap_or(false) { + // dbg!(result_0.witness_hook(&*cs)().unwrap()); + // dbg!(result_1.witness_hook(&*cs)().unwrap()); + // } + // } + + let can_write_into_memory = MUL_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + debug_assert_eq!( + can_write_into_memory, + DIV_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) + ); + + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, apply_any, dst0)); + diffs_accumulator.dst_1_values.push((apply_any, dst1)); + let set_flags_and_execute = Boolean::multi_and(cs, &[apply_any, should_set_flags]); + diffs_accumulator + .flags + .push((set_flags_and_execute, candidate_flags)); + + // add range check request. Even though it's only needed for division, it's always satisfiable + diffs_accumulator + .u32_conditional_range_checks + .push((apply_any, conditional_range_checks)); + + let mut add_sub_relations = ArrayVec::new(); + add_sub_relations.push(addition_relation); + diffs_accumulator + .add_sub_relations + .push((apply_any, add_sub_relations)); + + let mut mul_div_relations = ArrayVec::new(); + mul_div_relations.push(mul_relation); + diffs_accumulator + .mul_div_relations + .push((apply_any, mul_div_relations)); +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/nop.rs b/crates/zkevm_circuits/src/main_vm/opcodes/nop.rs new file mode 100644 index 00000000..ea0fbd2a --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/nop.rs @@ -0,0 +1,24 @@ +use super::*; + +#[inline(always)] +pub(crate) fn apply_nop>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + _diffs_accumulator: &mut StateDiffsAccumulator, +) { + const NOP_OPCODE: zkevm_opcode_defs::Opcode = Opcode::Nop(NopOpcode); + + // now we need to properly select and enforce + let apply_nop = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(NOP_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (apply_nop.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying NOP"); + } + } +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/ptr.rs b/crates/zkevm_circuits/src/main_vm/opcodes/ptr.rs new file mode 100644 index 00000000..0610bd80 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/ptr.rs @@ -0,0 +1,183 @@ +use crate::base_structures::register::VMRegister; +use boojum::gadgets::u256::UInt256; + +use super::*; + +pub(crate) fn apply_ptr>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + const PTR_ADD_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Ptr(zkevm_opcode_defs::PtrOpcode::Add); + const PTR_SUB_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Ptr(zkevm_opcode_defs::PtrOpcode::Sub); + const PTR_PACK_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Ptr(zkevm_opcode_defs::PtrOpcode::Pack); + const PTR_SHRINK_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Ptr(zkevm_opcode_defs::PtrOpcode::Shrink); + + let should_apply = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(PTR_ADD_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (should_apply.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying PTR"); + } + } + + let ptr_add_variant = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(PTR_ADD_OPCODE); + let ptr_sub_variant = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(PTR_SUB_OPCODE); + let ptr_pack_variant = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(PTR_PACK_OPCODE); + let ptr_shrink_variant = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(PTR_SHRINK_OPCODE); + + let src_0 = &common_opcode_state.src0_view; + let src_1 = &common_opcode_state.src1_view; + + let src1_is_integer = src_1.is_ptr.negated(cs); + + // pointer + non_pointer + let args_have_valid_type = Boolean::multi_and(cs, &[src_0.is_ptr, src1_is_integer]); + let args_types_are_invalid = args_have_valid_type.negated(cs); + // we also want to check that src1 is "small" in case of ptr.add + + let limb_is_zero = common_opcode_state + .src1_view + .u32x8_view + .map(|el| el.is_zero(cs)); + let src1_32_to_256_is_zero = Boolean::multi_and(cs, &limb_is_zero[1..]); + let src1_0_to_128_is_zero = Boolean::multi_and(cs, &limb_is_zero[..4]); + + let src1_32_to_256_is_nonzero = src1_32_to_256_is_zero.negated(cs); + + // if we add we want upper part of src1 to be zero + let ptr_arith_variant = Boolean::multi_or(cs, &[ptr_add_variant, ptr_sub_variant]); + let too_large_offset = Boolean::multi_and(cs, &[src1_32_to_256_is_nonzero, ptr_arith_variant]); + + // if we pack we want lower part of src1 to be zero + let src1_0_to_128_is_nonzero = src1_0_to_128_is_zero.negated(cs); + let dirty_value_for_pack = + Boolean::multi_and(cs, &[src1_0_to_128_is_nonzero, ptr_pack_variant]); + + // now check overflows/underflows + let (result_for_ptr_add, of) = src_0.u32x8_view[0].overflowing_add(cs, src_1.u32x8_view[0]); + let overflow_panic_if_add = Boolean::multi_and(cs, &[ptr_add_variant, of]); + + let (result_for_ptr_sub, uf) = src_0.u32x8_view[0].overflowing_sub(cs, src_1.u32x8_view[0]); + let underflow_panic_if_sub = Boolean::multi_and(cs, &[ptr_sub_variant, uf]); + + let (result_for_ptr_shrink, uf) = src_0.u32x8_view[3].overflowing_sub(cs, src_1.u32x8_view[0]); + let underflow_panic_if_shrink = Boolean::multi_and(cs, &[ptr_shrink_variant, uf]); + + let any_potential_panic = Boolean::multi_or( + cs, + &[ + args_types_are_invalid, + too_large_offset, + dirty_value_for_pack, + overflow_panic_if_add, + underflow_panic_if_sub, + underflow_panic_if_shrink, + ], + ); + + let should_panic = Boolean::multi_and(cs, &[should_apply, any_potential_panic]); + let ok_to_execute = any_potential_panic.negated(cs); + let should_update_register = Boolean::multi_and(cs, &[should_apply, ok_to_execute]); + + // now we just need to select the result + + // low 32 bits from addition or unchanged original values + let low_u32_if_add_or_sub = UInt32::conditionally_select( + cs, + ptr_add_variant, + &result_for_ptr_add, + &src_0.u32x8_view[0], + ); + + // low 32 bits from subtraction + let low_u32_if_add_or_sub = UInt32::conditionally_select( + cs, + ptr_sub_variant, + &result_for_ptr_sub, + &low_u32_if_add_or_sub, + ); + + // higher 32 bits if shrink + let bits_96_to_128_if_shrink = UInt32::conditionally_select( + cs, + ptr_shrink_variant, + &result_for_ptr_shrink, + &src_0.u32x8_view[3], // otherwise keep src_0 bits 96..128 + ); + + let highest_128 = UInt32::parallel_select( + cs, + ptr_pack_variant, + &[ + src_1.u32x8_view[4], + src_1.u32x8_view[5], + src_1.u32x8_view[6], + src_1.u32x8_view[7], + ], + &[ + src_0.u32x8_view[4], + src_0.u32x8_view[5], + src_0.u32x8_view[6], + src_0.u32x8_view[7], + ], + ); + + let lowest32 = UInt32::conditionally_select( + cs, + ptr_pack_variant, + &src_0.u32x8_view[0], + &low_u32_if_add_or_sub, + ); + + let bits_96_to_128 = UInt32::conditionally_select( + cs, + ptr_pack_variant, + &src_0.u32x8_view[3], + &bits_96_to_128_if_shrink, + ); + + let dst0 = VMRegister { + is_pointer: src_0.is_ptr, + value: UInt256 { + inner: [ + lowest32, + src_0.u32x8_view[1], + src_0.u32x8_view[2], + bits_96_to_128, + highest_128[0], + highest_128[1], + highest_128[2], + highest_128[3], + ], + }, + }; + + // only update dst0 and set exception if necessary + let can_write_into_memory = PTR_ADD_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, should_update_register, dst0)); + diffs_accumulator.pending_exceptions.push(should_panic); +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/shifts.rs b/crates/zkevm_circuits/src/main_vm/opcodes/shifts.rs new file mode 100644 index 00000000..a18777b0 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/shifts.rs @@ -0,0 +1,221 @@ +use super::*; +use crate::base_structures::register::VMRegister; +use crate::base_structures::vm_state::ArithmeticFlagsPort; +use crate::tables::bitshift::*; +use arrayvec::ArrayVec; +use boojum::gadgets::u256::UInt256; + +pub(crate) fn apply_shifts>( + cs: &mut CS, + _draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + _opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, +) { + const SHL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Shift(zkevm_opcode_defs::definitions::shift::ShiftOpcode::Shl); + const ROL_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Shift(zkevm_opcode_defs::definitions::shift::ShiftOpcode::Rol); + const SHR_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Shift(zkevm_opcode_defs::definitions::shift::ShiftOpcode::Shr); + const ROR_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::Shift(zkevm_opcode_defs::definitions::shift::ShiftOpcode::Ror); + + let should_apply = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(SHL_OPCODE); + + if crate::config::CIRCUIT_VERSOBE { + if (should_apply.witness_hook(&*cs))().unwrap_or(false) { + println!("Applying SHIFT"); + } + } + + let should_set_flags = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[SET_FLAGS_FLAG_IDX]; + + let is_rol = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(ROL_OPCODE); + let is_ror = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(ROR_OPCODE); + let is_shr = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(SHR_OPCODE); + + let is_cyclic = is_rol.or(cs, is_ror); + let is_right = is_ror.or(cs, is_shr); + + let reg = &common_opcode_state.src0_view.u32x8_view; + let shift = common_opcode_state.src1_view.u8x32_view[0]; + let shift = shift.into_num(); + + // cyclic right rotation x is the same as left cyclic rotation 256 - x + let change_rot = is_ror; + let shift_is_zero = shift.is_zero(cs); + let cnst = Num::allocated_constant(cs, F::from_u64_unchecked(256)); + // no underflow here + let inverted_shift = cnst.sub(cs, &shift); + + let change_flag = { + let x = shift_is_zero.negated(cs); + change_rot.and(cs, x) + }; + let full_shift = Num::conditionally_select(cs, change_flag, &inverted_shift, &shift); + + // and only NOW it's indeed 8-bit, even if we had a subtraction of 256 - 0 above + let full_shift = unsafe { UInt8::from_variable_unchecked(full_shift.get_variable()) }; + + let full_shift_limbs = get_shift_constant(cs, full_shift); + + let is_right_shift = { + let x = is_cyclic.negated(cs); + is_right.and(cs, x) + }; + let (rshift_q, rshift_r) = allocate_div_result_unchecked(cs, ®, &full_shift_limbs); + + let apply_left_shift = { + let x = is_right_shift.negated(cs); + Boolean::multi_and(cs, &[should_apply, x]) + }; + let (lshift_low, lshift_high) = allocate_mul_result_unchecked(cs, ®, &full_shift_limbs); + + // see description of MulDivRelation to range checks in mul_div.rs, but in short: + // - if we shift right then `rshift_q`` is checked as `a_to_enforce`, `rshift_r` is checked as `rem_to_enforce` + // - if we shift left then `lshift_low` is checked as `mul_low_to_enforce` and `lshift_high` as `mul_high_to_enforce` + + // actual enforcement: + // for left_shift: a = reg, b = full_shuft, remainder = 0, high = lshift_high, low = lshift_low + // for right_shift : a = rshift_q, b = full_shift, remainder = rshift_r, high = 0, low = reg + let uint256_zero = UInt256::zero(cs); + + let rem_to_enforce = + UInt32::parallel_select(cs, apply_left_shift, &uint256_zero.inner, &rshift_r); + let a_to_enforce = UInt32::parallel_select(cs, apply_left_shift, reg, &rshift_q); + let b_to_enforce = full_shift_limbs; + let mul_low_to_enforce = UInt32::parallel_select(cs, apply_left_shift, &lshift_low, reg); + let mul_high_to_enforce = + UInt32::parallel_select(cs, apply_left_shift, &lshift_high, &uint256_zero.inner); + + let mul_relation = MulDivRelation { + a: a_to_enforce, + b: b_to_enforce, + rem: rem_to_enforce, + mul_low: mul_low_to_enforce, + mul_high: mul_high_to_enforce, + }; + + // but since we can do division, we need to check that remainder < divisor. We also know that divisor != 0, so no + // extra checks are necessary + let (subtraction_result_unchecked, remainder_is_less_than_divisor) = + allocate_subtraction_result_unchecked(cs, &rshift_r, &full_shift_limbs); + + remainder_is_less_than_divisor.conditionally_enforce_true(cs, is_right_shift); + + // if we do division then remainder will be range checked, but not the subtraction result + let conditional_range_checks = subtraction_result_unchecked; + + // relation is a + b == c + of * 2^N, + // but we compute d - e + 2^N * borrow = f + + // so we need to shuffle + let addition_relation = AddSubRelation { + a: full_shift_limbs, + b: subtraction_result_unchecked, + c: rshift_r, + of: remainder_is_less_than_divisor, + }; + + let temp_result = UInt32::parallel_select(cs, is_right_shift, &rshift_q, &lshift_low); + let overflow = lshift_high; + let mut final_result = UInt256::zero(cs).inner; + + let zipped_iter = (temp_result.iter(), overflow.iter(), final_result.iter_mut()); + for (limb_in, of_in, limb_out) in itertools::multizip(zipped_iter) { + // of * is_cyclic + limb_in + let res = Num::fma( + cs, + &of_in.into_num(), + &is_cyclic.into_num(), + &F::ONE, + &limb_in.into_num(), + &F::ONE, + ); + *limb_out = unsafe { UInt32::from_variable_unchecked(res.get_variable()) }; + } + + // Sets an eq flag if out1 is zero + let res_is_zero = all_limbs_are_zero(cs, &final_result); + let boolean_false = Boolean::allocated_constant(cs, false); + let new_flag_port = ArithmeticFlagsPort { + overflow_or_less_than: boolean_false, + equal: res_is_zero, + greater_than: boolean_false, + }; + + // flags for a case if we do not set flags + let set_flags_and_execute = Boolean::multi_and(cs, &[should_apply, should_set_flags]); + + let dst0 = VMRegister { + is_pointer: boolean_false, + value: UInt256 { + inner: final_result, + }, + }; + + let can_write_into_memory = SHL_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, should_apply, dst0)); + diffs_accumulator + .flags + .push((set_flags_and_execute, new_flag_port)); + + // add range check request + diffs_accumulator + .u32_conditional_range_checks + .push((should_apply, conditional_range_checks)); + + let mut add_sub_relations = ArrayVec::new(); + add_sub_relations.push(addition_relation); + diffs_accumulator + .add_sub_relations + .push((should_apply, add_sub_relations)); + + let mut mul_div_relations = ArrayVec::new(); + mul_div_relations.push(mul_relation); + diffs_accumulator + .mul_div_relations + .push((should_apply, mul_div_relations)); +} + +pub(crate) fn get_shift_constant>( + cs: &mut CS, + shift: UInt8, +) -> [UInt32; 8] { + let shift_table_id = cs + .get_table_id_for_marker::() + .expect("table must exist"); + + let mut full_shift_limbs = [UInt32::zero(cs); 8]; + for (idx, dst) in full_shift_limbs.chunks_mut(2).enumerate() { + // shift + idx << 8 + let summand = Num::allocated_constant(cs, F::from_u64_unchecked((idx << 8) as u64)); + let key = shift.into_num().add(cs, &summand); + let [a, b] = cs.perform_lookup::<1, 2>(shift_table_id, &[key.get_variable()]); + unsafe { + dst[0] = UInt32::from_variable_unchecked(a); + dst[1] = UInt32::from_variable_unchecked(b); + } + } + + full_shift_limbs +} diff --git a/crates/zkevm_circuits/src/main_vm/opcodes/uma.rs b/crates/zkevm_circuits/src/main_vm/opcodes/uma.rs new file mode 100644 index 00000000..d3ee192d --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/opcodes/uma.rs @@ -0,0 +1,1103 @@ +use crate::base_structures::register::VMRegister; +use boojum::gadgets::{traits::castable::WitnessCastable, u256::UInt256}; +use cs_derive::CSAllocatable; + +use super::*; +use crate::base_structures::memory_query::MemoryQueryWitness; +use crate::base_structures::memory_query::MemoryValue; +use crate::main_vm::pre_state::MemoryLocation; +use crate::main_vm::register_input_view::RegisterInputView; +use crate::main_vm::witness_oracle::SynchronizedWitnessOracle; +use crate::main_vm::witness_oracle::WitnessOracle; +use arrayvec::ArrayVec; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::traits::cs::DstBuffer; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +pub(crate) fn apply_uma< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + draft_vm_state: &VmLocalState, + common_opcode_state: &CommonOpcodeState, + opcode_carry_parts: &AfterDecodingCarryParts, + diffs_accumulator: &mut StateDiffsAccumulator, + witness_oracle: &SynchronizedWitnessOracle, + _round_function: &R, +) where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + const UMA_HEAP_READ_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::UMA(UMAOpcode::HeapRead); + const UMA_HEAP_WRITE_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::UMA(UMAOpcode::HeapWrite); + const UMA_AUX_HEAP_READ_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::UMA(UMAOpcode::AuxHeapRead); + const UMA_AUX_HEAP_WRITE_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::UMA(UMAOpcode::AuxHeapWrite); + const UMA_FAT_PTR_READ_OPCODE: zkevm_opcode_defs::Opcode = + zkevm_opcode_defs::Opcode::UMA(UMAOpcode::FatPointerRead); + + let should_apply = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_opcode(UMA_HEAP_READ_OPCODE); + + let is_uma_heap_read = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(UMA_HEAP_READ_OPCODE); + let is_uma_heap_write = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(UMA_HEAP_WRITE_OPCODE); + let is_uma_aux_heap_read = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(UMA_AUX_HEAP_READ_OPCODE); + let is_uma_aux_heap_write = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(UMA_AUX_HEAP_WRITE_OPCODE); + let is_uma_fat_ptr_read = common_opcode_state + .decoded_opcode + .properties_bits + .boolean_for_variant(UMA_FAT_PTR_READ_OPCODE); + + let increment_offset = common_opcode_state + .decoded_opcode + .properties_bits + .flag_booleans[UMA_INCREMENT_FLAG_IDX]; + + let access_heap = Boolean::multi_or(cs, &[is_uma_heap_read, is_uma_heap_write]); + let access_aux_heap = Boolean::multi_or(cs, &[is_uma_aux_heap_read, is_uma_aux_heap_write]); + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap_or(false) { + println!("Applying UMA"); + if is_uma_heap_read.witness_hook(&*cs)().unwrap_or(false) { + println!("Heap read"); + } + if is_uma_heap_write.witness_hook(&*cs)().unwrap_or(false) { + println!("Heap write"); + } + if is_uma_aux_heap_read.witness_hook(&*cs)().unwrap_or(false) { + println!("Aux heap read"); + } + if is_uma_aux_heap_write.witness_hook(&*cs)().unwrap_or(false) { + println!("Aux heap write"); + } + if is_uma_fat_ptr_read.witness_hook(&*cs)().unwrap_or(false) { + println!("Fat ptr read"); + } + } + } + + let src0_is_integer = common_opcode_state.src0_view.is_ptr.negated(cs); + + // perform basic validation + let not_a_ptr_when_expected = + Boolean::multi_and(cs, &[should_apply, is_uma_fat_ptr_read, src0_is_integer]); + + let quasi_fat_ptr = QuasiFatPtrInUMA::parse_and_validate( + cs, + &common_opcode_state.src0_view, + not_a_ptr_when_expected, + is_uma_fat_ptr_read, + ); + + // this one could wrap around, so we account for it. In case if we wrapped we will skip operation anyway + let max_accessed = quasi_fat_ptr.incremented_offset; + + let heap_max_accessed = max_accessed.mask(cs, access_heap); + let heap_bound = draft_vm_state + .callstack + .current_context + .saved_context + .heap_upper_bound; + let (mut heap_growth, uf) = heap_max_accessed.overflowing_sub(cs, heap_bound); + heap_growth = heap_growth.mask_negated(cs, uf); // of we access in bounds then it's 0 + let new_heap_upper_bound = + UInt32::conditionally_select(cs, uf, &heap_bound, &heap_max_accessed); + let grow_heap = Boolean::multi_and(cs, &[access_heap, should_apply]); + + let aux_heap_max_accessed = max_accessed.mask(cs, access_aux_heap); + let aux_heap_bound = draft_vm_state + .callstack + .current_context + .saved_context + .aux_heap_upper_bound; + let (mut aux_heap_growth, uf) = aux_heap_max_accessed.overflowing_sub(cs, aux_heap_bound); + aux_heap_growth = aux_heap_growth.mask_negated(cs, uf); // of we access in bounds then it's 0 + let new_aux_heap_upper_bound = + UInt32::conditionally_select(cs, uf, &aux_heap_bound, &aux_heap_max_accessed); + let grow_aux_heap = Boolean::multi_and(cs, &[access_aux_heap, should_apply]); + + let mut growth_cost = heap_growth.mask(cs, access_heap); + growth_cost = UInt32::conditionally_select(cs, access_aux_heap, &aux_heap_growth, &growth_cost); + + let limbs_to_check = [ + common_opcode_state.src0_view.u32x8_view[1], + common_opcode_state.src0_view.u32x8_view[2], + common_opcode_state.src0_view.u32x8_view[3], + common_opcode_state.src0_view.u32x8_view[4], + common_opcode_state.src0_view.u32x8_view[5], + common_opcode_state.src0_view.u32x8_view[6], + common_opcode_state.src0_view.u32x8_view[7], + ]; + + let limbs_are_zero = limbs_to_check.map(|el| el.is_zero(cs)); + let top_bits_are_clear = Boolean::multi_and(cs, &limbs_are_zero); + let top_bits_are_non_zero = top_bits_are_clear.negated(cs); + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap_or(false) { + dbg!(quasi_fat_ptr.witness_hook(&*cs)().unwrap()); + dbg!(&common_opcode_state.src0_view.u32x8_view.witness_hook(&*cs)().unwrap()[..4]); + dbg!(common_opcode_state.src1.witness_hook(&*cs)().unwrap()); + } + } + + let t: Boolean = Boolean::multi_or( + cs, + &[ + top_bits_are_non_zero, + quasi_fat_ptr.heap_deref_out_of_bounds, + ], + ); + let heap_access_like = Boolean::multi_or(cs, &[access_heap, access_aux_heap]); + let exception_heap_deref_out_of_bounds = Boolean::multi_and(cs, &[heap_access_like, t]); + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap_or(false) { + dbg!(top_bits_are_non_zero.witness_hook(&*cs)().unwrap()); + dbg!(quasi_fat_ptr.heap_deref_out_of_bounds.witness_hook(&*cs)().unwrap()); + dbg!(heap_access_like.witness_hook(&*cs)().unwrap()); + dbg!(exception_heap_deref_out_of_bounds.witness_hook(&*cs)().unwrap()); + } + } + + // penalize for heap out of bounds access + let uint32_max = UInt32::allocated_constant(cs, u32::MAX); + growth_cost = UInt32::conditionally_select( + cs, + exception_heap_deref_out_of_bounds, + &uint32_max, + &growth_cost, + ); + + let (ergs_left_after_growth, uf) = opcode_carry_parts + .preliminary_ergs_left + .overflowing_sub(cs, growth_cost); + + let set_panic = Boolean::multi_or( + cs, + &[ + quasi_fat_ptr.should_set_panic, + uf, + exception_heap_deref_out_of_bounds, + ], + ); + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap_or(false) { + dbg!(set_panic.witness_hook(&*cs)().unwrap()); + } + } + // burn all the ergs if not enough + let ergs_left_after_growth = ergs_left_after_growth.mask_negated(cs, uf); + + let should_skip_memory_ops = + Boolean::multi_or(cs, &[quasi_fat_ptr.skip_memory_access, set_panic]); + + let is_read_access = Boolean::multi_or( + cs, + &[is_uma_heap_read, is_uma_aux_heap_read, is_uma_fat_ptr_read], + ); + let is_write_access = Boolean::multi_or(cs, &[is_uma_heap_write, is_uma_aux_heap_write]); + + // NB: Etherium virtual machine is big endian; + // we need to determine the memory cells' indexes which will be accessed + // every memory cell is 32 bytes long, the first cell to be accesed has idx = offset / 32 + // if rem = offset % 32 is zero than it is the only one cell to be accessed + // 1) cell_idx = offset / cell_length, rem = offset % cell_length => + // offset = cell_idx * cell_length + rem + // we should also enforce that cell_idx /in [0, 2^32-1] - this would require range check + // we should also enforce that 0 <= rem < cell_length = 2^5; + // rem is actually the byte offset in the first touched cell, to compute bitoffset and shifts + // we do bit_offset = rem * 8 and then apply shift computing tables + // flag does_cross_border = rem != 0 + let offset = quasi_fat_ptr.absolute_address; + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap() { + dbg!(offset.witness_hook(&*cs)().unwrap()); + } + } + + let (cell_idx, unalignment) = offset.div_by_constant(cs, 32); + let unalignment_is_zero = unalignment.is_zero(cs); + let access_is_unaligned = unalignment_is_zero.negated(cs); + + // read both memory cells: in what follows we will call the first memory slot A + // and the second memory Slot B + let current_memory_queue_state = draft_vm_state.memory_queue_state; + let current_memory_queue_length = draft_vm_state.memory_queue_length; + + let mut mem_page = quasi_fat_ptr.page_candidate; + mem_page = + UInt32::conditionally_select(cs, access_heap, &opcode_carry_parts.heap_page, &mem_page); + mem_page = UInt32::conditionally_select( + cs, + access_aux_heap, + &opcode_carry_parts.aux_heap_page, + &mem_page, + ); + + let a_cell_idx = cell_idx; + let one_uint32 = UInt32::allocated_constant(cs, 1); + // wrap around + let (b_cell_idx, _of) = a_cell_idx.overflowing_add(cs, one_uint32); + + let a_memory_loc = MemoryLocation { + page: mem_page, + index: a_cell_idx, + }; + let b_memory_loc = MemoryLocation { + page: mem_page, + index: b_cell_idx, + }; + + let mem_read_timestamp = common_opcode_state.timestamp_for_code_or_src_read; + let mem_timestamp_write = common_opcode_state.timestamp_for_dst_write; + + let do_not_skip_memory_access = should_skip_memory_ops.negated(cs); + + let is_unaligned_read = Boolean::multi_and( + cs, + &[should_apply, access_is_unaligned, do_not_skip_memory_access], + ); + + // we yet access the `a` always + let should_read_a_cell = Boolean::multi_and(cs, &[should_apply, do_not_skip_memory_access]); + let should_read_b_cell = is_unaligned_read; + + // we read twice + + let oracle = witness_oracle.clone(); + let mut memory_value_a = MemoryValue::allocate_from_closure_and_dependencies_non_pointer( + cs, + move |inputs: &[F]| { + debug_assert_eq!(inputs.len(), 4); + let timestamp = >::cast_from_source(inputs[0]); + let memory_page = >::cast_from_source(inputs[1]); + let index = >::cast_from_source(inputs[2]); + let should_access = >::cast_from_source(inputs[3]); + + if crate::config::CIRCUIT_VERSOBE { + if should_access { + println!("Will read word A for UMA"); + } + } + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = + guard.get_memory_witness_for_read(timestamp, memory_page, index, should_access); + drop(guard); + + witness + }, + &[ + mem_read_timestamp.get_variable().into(), + a_memory_loc.page.get_variable().into(), + a_memory_loc.index.get_variable().into(), + should_read_a_cell.get_variable().into(), + ], + ); + // if we would not need to read we mask it into 0. We do not care about pointer part as we set constant "false" below + memory_value_a.value = memory_value_a.value.mask(cs, should_read_a_cell); + + let oracle = witness_oracle.clone(); + let mut memory_value_b = MemoryValue::allocate_from_closure_and_dependencies_non_pointer( + cs, + move |inputs: &[F]| { + debug_assert_eq!(inputs.len(), 5); + let timestamp = >::cast_from_source(inputs[0]); + let memory_page = >::cast_from_source(inputs[1]); + let index = >::cast_from_source(inputs[2]); + let should_access = >::cast_from_source(inputs[3]); + + if crate::config::CIRCUIT_VERSOBE { + if should_access { + println!("Will read word B for UMA"); + } + } + + let mut guard = oracle.inner.write().expect("not poisoned"); + let witness = + guard.get_memory_witness_for_read(timestamp, memory_page, index, should_access); + drop(guard); + + witness + }, + &[ + mem_read_timestamp.get_variable().into(), + b_memory_loc.page.get_variable().into(), + b_memory_loc.index.get_variable().into(), + should_read_b_cell.get_variable().into(), + // NOTE: we need to evaluate this closure strictly AFTER we evaluate previous access to witness, + // so we "bias" it here + memory_value_a.value.inner[0].get_variable().into(), + ], + ); + // if we would not need to read we mask it into 0. We do not care about pointer part as we set constant "false" below + memory_value_b.value = memory_value_b.value.mask(cs, should_read_b_cell); + + // now we can update the memory queue state + + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + + let ( + new_memory_queue_tail_after_read, + new_memory_queue_length_after_read, + sponge_candidates_after_read, + ) = { + let mut relations = ArrayVec::new(); + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // dbg!(should_read_a_cell.witness_hook(&*cs)().unwrap()); + // dbg!(should_read_b_cell.witness_hook(&*cs)().unwrap()); + // } + // } + + let query = MemoryQuery { + timestamp: mem_read_timestamp, + memory_page: a_memory_loc.page, + index: a_memory_loc.index, + is_ptr: boolean_false, + value: memory_value_a.value, + rw_flag: boolean_false, + }; + + use boojum::gadgets::traits::encodable::CircuitEncodable; + + let packed_query = query.encode(cs); + + // this is absorb with replacement + let initial_state = [ + packed_query[0], + packed_query[1], + packed_query[2], + packed_query[3], + packed_query[4], + packed_query[5], + packed_query[6], + packed_query[7], + current_memory_queue_state[8].get_variable(), + current_memory_queue_state[9].get_variable(), + current_memory_queue_state[10].get_variable(), + current_memory_queue_state[11].get_variable(), + ]; + + use boojum::gadgets::round_function::simulate_round_function; + + let final_state_candidate = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, initial_state, should_read_a_cell); + let final_state_candidate = final_state_candidate.map(|el| Num::from_variable(el)); + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // if should_read_a_cell.witness_hook(&*cs)().unwrap() { + // dbg!(initial_state.map(|el| Num::from_variable(el)).witness_hook(&*cs)().unwrap()); + // dbg!(final_state_candidate.witness_hook(&*cs)().unwrap()); + // } + // } + // } + + relations.push(( + should_read_a_cell, + initial_state.map(|el| Num::from_variable(el)), + final_state_candidate, + )); + + let mut new_memory_queue_state = Num::parallel_select( + cs, + should_read_a_cell, + &final_state_candidate, + ¤t_memory_queue_state, + ); + + // for all reasonable execution traces it's fine + let new_len_candidate = unsafe { current_memory_queue_length.increment_unchecked(cs) }; + + let new_length = UInt32::conditionally_select( + cs, + should_read_a_cell, + &new_len_candidate, + ¤t_memory_queue_length, + ); + + // now second query + + let query = MemoryQuery { + timestamp: mem_read_timestamp, + memory_page: b_memory_loc.page, + index: b_memory_loc.index, + is_ptr: boolean_false, + value: memory_value_b.value, + rw_flag: boolean_false, + }; + + let packed_query = query.encode(cs); + + // this is absorb with replacement + let initial_state = [ + packed_query[0], + packed_query[1], + packed_query[2], + packed_query[3], + packed_query[4], + packed_query[5], + packed_query[6], + packed_query[7], + new_memory_queue_state[8].get_variable(), + new_memory_queue_state[9].get_variable(), + new_memory_queue_state[10].get_variable(), + new_memory_queue_state[11].get_variable(), + ]; + + let final_state_candidate = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, initial_state, should_read_b_cell); + let final_state_candidate = final_state_candidate.map(|el| Num::from_variable(el)); + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // if should_read_b_cell.witness_hook(&*cs)().unwrap() { + // dbg!(initial_state.map(|el| Num::from_variable(el)).witness_hook(&*cs)().unwrap()); + // dbg!(final_state_candidate.witness_hook(&*cs)().unwrap()); + // } + // } + // } + + relations.push(( + should_read_b_cell, + initial_state.map(|el| Num::from_variable(el)), + final_state_candidate, + )); + + new_memory_queue_state = Num::parallel_select( + cs, + should_read_b_cell, + &final_state_candidate, + &new_memory_queue_state, + ); + + // for all reasonable execution traces it's fine + let new_len_candidate = unsafe { new_length.increment_unchecked(cs) }; + + let new_length = + UInt32::conditionally_select(cs, should_read_b_cell, &new_len_candidate, &new_length); + + (new_memory_queue_state, new_length, relations) + }; + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // dbg!(new_memory_queue_length_after_read.witness_hook(&*cs)().unwrap()); + // } + // } + + // the issue with UMA is that if we cleanup bytes using shifts + // then it's just too heavy in our arithmetization compared to some implementation of shift + // register + + // we have a table that is: + // b1000000.. LSB first if unalignment is 0 + // b0100000.. LSB first if unalignment is 1 + // so it's 32 bits max, and we use parallel select + + let unalignment_bitspread = + uma_shift_into_bitspread(cs, Num::from_variable(unalignment.get_variable())); + let unalignment_bit_mask = unalignment_bitspread.spread_into_bits::<_, 32>(cs); + + // implement shift register + let zero_u8 = UInt8::zero(cs); + let mut bytes_array = [zero_u8; 64]; + + let memory_value_a_bytes = memory_value_a.value.to_be_bytes(cs); + bytes_array[..32].copy_from_slice(&memory_value_a_bytes); + + let memory_value_b_bytes = memory_value_b.value.to_be_bytes(cs); + bytes_array[32..].copy_from_slice(&memory_value_b_bytes); + + // now mask-shift + let mut selected_word = [zero_u8; 32]; + + // idx 0 is unalignment of 0 (aligned), idx 31 is unalignment of 31 + for (idx, mask_bit) in unalignment_bit_mask.iter().enumerate() { + let src = &bytes_array[idx..(idx + 32)]; // source + debug_assert_eq!(src.len(), selected_word.len()); + + for (dst, src) in selected_word + .array_chunks_mut::<4>() + .zip(src.array_chunks::<4>()) + { + *dst = UInt8::parallel_select(cs, *mask_bit, src, &*dst); + } + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // if should_read_a_cell.witness_hook(&*cs)().unwrap() { + // let src: [_; 32] = src.to_vec().try_into().unwrap(); + // dbg!(mask_bit.witness_hook(&*cs)().unwrap()); + // let src_buffer = src.witness_hook(&*cs)().unwrap(); + // dbg!(hex::encode(&src_buffer)); + // let dst_buffer = selected_word.witness_hook(&*cs)().unwrap(); + // dbg!(hex::encode(&dst_buffer)); + // } + // } + // } + } + + // in case of out-of-bounds UMA we should zero-out tail of our array + // now we need to shift it once again to cleanup from out of bounds part. So we just shift right and left on BE machine + use crate::tables::uma_ptr_read_cleanup::UMAPtrReadCleanupTable; + + let table_id = cs + .get_table_id_for_marker::() + .expect("table must exist"); + let bytes_to_cleanup_out_of_bound = quasi_fat_ptr.bytes_to_cleanup_out_of_bounds; + let bytes_to_cleanup_out_of_bound_if_ptr_read = + bytes_to_cleanup_out_of_bound.mask(cs, is_uma_fat_ptr_read); + let [uma_cleanup_bitspread, _] = cs.perform_lookup::<1, 2>( + table_id, + &[bytes_to_cleanup_out_of_bound_if_ptr_read.get_variable()], + ); + let uma_ptr_read_cleanup_mask = + Num::from_variable(uma_cleanup_bitspread).spread_into_bits::<_, 32>(cs); + + for (dst, masking_bit) in selected_word + .iter_mut() + .zip(uma_ptr_read_cleanup_mask.iter().rev()) + { + *dst = dst.mask(cs, *masking_bit); + } + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap() { + if should_read_a_cell.witness_hook(&*cs)().unwrap() { + dbg!(unalignment.witness_hook(&*cs)().unwrap()); + let src_buffer = bytes_array.witness_hook(&*cs)().unwrap(); + dbg!(hex::encode(&src_buffer)); + let result_buffer = selected_word.witness_hook(&*cs)().unwrap(); + dbg!(hex::encode(&result_buffer)); + } + } + } + + // for "write" we have to keep the "leftovers" + // and replace the "inner" part with decomposition of the value from src1 + + let execute_write = Boolean::multi_and( + cs, + &[should_apply, is_write_access, do_not_skip_memory_access], + ); // we do not need set panic here, as it's "inside" of `should_skip_memory_ops` + let execute_unaligned_write = Boolean::multi_and(cs, &[execute_write, access_is_unaligned]); + + // make it BE + let mut written_value_bytes = common_opcode_state.src1_view.u8x32_view; + written_value_bytes.reverse(); + + let mut written_bytes_buffer = bytes_array; + // now it's a little trickier as we have to kind-of transpose + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap() { + if execute_write.witness_hook(&*cs)().unwrap() { + dbg!(unalignment.witness_hook(&*cs)().unwrap()); + let to_write = written_value_bytes.witness_hook(&*cs)().unwrap(); + dbg!(hex::encode(&to_write)); + let original_buffer = bytes_array.witness_hook(&*cs)().unwrap(); + dbg!(hex::encode(&original_buffer)); + } + } + } + + // place back + for (idx, mask_bit) in unalignment_bit_mask.iter().enumerate() { + let dst = &mut written_bytes_buffer[idx..(idx + 32)]; // destination + for (dst, src) in dst + .array_chunks_mut::<4>() + .zip(written_value_bytes.array_chunks::<4>()) + { + *dst = UInt8::parallel_select(cs, *mask_bit, src, &*dst); + } + } + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap() { + if execute_write.witness_hook(&*cs)().unwrap() { + let result_buffer = written_bytes_buffer.witness_hook(&*cs)().unwrap(); + dbg!(hex::encode(&result_buffer)); + } + } + } + + // now we should write both values in corresponding cells + + // update memory queue state again + let ( + new_memory_queue_tail_after_writes, + new_memory_queue_length_after_writes, + sponge_candidates_after_writes, + ) = { + let mut relations = sponge_candidates_after_read; + + if crate::config::CIRCUIT_VERSOBE { + if should_apply.witness_hook(&*cs)().unwrap() { + dbg!(execute_write.witness_hook(&*cs)().unwrap()); + dbg!(execute_unaligned_write.witness_hook(&*cs)().unwrap()); + } + } + + let mut a_new_value = UInt256::zero(cs); + // read value is LE integer, while words are treated as BE + for (dst, src) in a_new_value + .inner + .iter_mut() + .rev() + .zip(written_bytes_buffer[..32].array_chunks::<4>()) + { + let be_bytes = *src; + let u32_word = UInt32::from_be_bytes(cs, be_bytes); + *dst = u32_word; + } + + let mut b_new_value = UInt256::zero(cs); + // read value is LE integer, while words are treated as BE + for (dst, src) in b_new_value + .inner + .iter_mut() + .rev() + .zip(written_bytes_buffer[32..].array_chunks::<4>()) + { + let be_bytes = *src; + let u32_word = UInt32::from_be_bytes(cs, be_bytes); + *dst = u32_word; + } + + let a_query = MemoryQuery { + timestamp: mem_timestamp_write, + memory_page: a_memory_loc.page, + index: a_memory_loc.index, + is_ptr: boolean_false, + value: a_new_value, + rw_flag: boolean_true, + }; + + use boojum::gadgets::traits::encodable::CircuitEncodable; + + let packed_query = a_query.encode(cs); + + // this is absorb with replacement + let initial_state = [ + packed_query[0], + packed_query[1], + packed_query[2], + packed_query[3], + packed_query[4], + packed_query[5], + packed_query[6], + packed_query[7], + new_memory_queue_tail_after_read[8].get_variable(), + new_memory_queue_tail_after_read[9].get_variable(), + new_memory_queue_tail_after_read[10].get_variable(), + new_memory_queue_tail_after_read[11].get_variable(), + ]; + + use boojum::gadgets::round_function::simulate_round_function; + + let final_state_candidate = + simulate_round_function::<_, _, 8, 12, 4, R>(cs, initial_state, execute_write); + let final_state_candidate = final_state_candidate.map(|el| Num::from_variable(el)); + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // if execute_write.witness_hook(&*cs)().unwrap() { + // dbg!(initial_state.map(|el| Num::from_variable(el)).witness_hook(&*cs)().unwrap()); + // dbg!(final_state_candidate.witness_hook(&*cs)().unwrap()); + // } + // } + // } + + relations.push(( + execute_write, + initial_state.map(|el| Num::from_variable(el)), + final_state_candidate, + )); + + let mut new_memory_queue_state = Num::parallel_select( + cs, + execute_write, + &final_state_candidate, + &new_memory_queue_tail_after_read, + ); + + // for all reasonable execution traces it's fine + let new_len_candidate = + unsafe { new_memory_queue_length_after_read.increment_unchecked(cs) }; + + let new_length_after_aligned_write = UInt32::conditionally_select( + cs, + execute_write, + &new_len_candidate, + &new_memory_queue_length_after_read, + ); + + // now second query + + let b_query = MemoryQuery { + timestamp: mem_timestamp_write, + memory_page: b_memory_loc.page, + index: b_memory_loc.index, + is_ptr: boolean_false, + value: b_new_value, + rw_flag: boolean_true, + }; + + let packed_query = b_query.encode(cs); + + // this is absorb with replacement + let initial_state = [ + packed_query[0], + packed_query[1], + packed_query[2], + packed_query[3], + packed_query[4], + packed_query[5], + packed_query[6], + packed_query[7], + new_memory_queue_state[8].get_variable(), + new_memory_queue_state[9].get_variable(), + new_memory_queue_state[10].get_variable(), + new_memory_queue_state[11].get_variable(), + ]; + + let final_state_candidate = simulate_round_function::<_, _, 8, 12, 4, R>( + cs, + initial_state, + execute_unaligned_write, + ); + let final_state_candidate = final_state_candidate.map(|el| Num::from_variable(el)); + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // if execute_unaligned_write.witness_hook(&*cs)().unwrap() { + // dbg!(initial_state.map(|el| Num::from_variable(el)).witness_hook(&*cs)().unwrap()); + // dbg!(final_state_candidate.witness_hook(&*cs)().unwrap()); + // } + // } + // } + + relations.push(( + execute_unaligned_write, + initial_state.map(|el| Num::from_variable(el)), + final_state_candidate, + )); + + new_memory_queue_state = Num::parallel_select( + cs, + execute_unaligned_write, + &final_state_candidate, + &new_memory_queue_state, + ); + + // for all reasonable execution traces it's fine + let new_len_candidate = unsafe { new_length_after_aligned_write.increment_unchecked(cs) }; + + let new_length_after_unaligned_write = UInt32::conditionally_select( + cs, + execute_unaligned_write, + &new_len_candidate, + &new_length_after_aligned_write, + ); + + // push witness updates + { + let oracle = witness_oracle.clone(); + // we should assemble all the dependencies here, and we will use AllocateExt here + let mut dependencies = Vec::with_capacity( + as CSAllocatableExt>::INTERNAL_STRUCT_LEN * 2 + 2, + ); + dependencies.push(execute_write.get_variable().into()); + dependencies.push(execute_unaligned_write.get_variable().into()); + dependencies.extend(Place::from_variables(a_query.flatten_as_variables())); + dependencies.extend(Place::from_variables(b_query.flatten_as_variables())); + + cs.set_values_with_dependencies_vararg( + &dependencies, + &[], + move |inputs: &[F], _buffer: &mut DstBuffer<'_, '_, F>| { + debug_assert_eq!( + inputs.len(), + 2 + 2 * as CSAllocatableExt>::INTERNAL_STRUCT_LEN + ); + + let execute_0 = >::cast_from_source(inputs[0]); + let execute_1 = >::cast_from_source(inputs[1]); + + if crate::config::CIRCUIT_VERSOBE { + if execute_0 { + println!("Will overwrite word A for UMA") + } + } + + let mut query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + query.copy_from_slice( + &inputs + [2..(2 + as CSAllocatableExt>::INTERNAL_STRUCT_LEN)], + ); + let a_query: MemoryQueryWitness = + CSAllocatableExt::witness_from_set_of_values(query); + + let mut guard = oracle.inner.write().expect("not poisoned"); + guard.push_memory_witness(&a_query, execute_0); + + if crate::config::CIRCUIT_VERSOBE { + if execute_1 { + println!("Will overwrite word B for UMA") + } + } + + let mut query = + [F::ZERO; as CSAllocatableExt>::INTERNAL_STRUCT_LEN]; + query.copy_from_slice( + &inputs + [(2 + as CSAllocatableExt>::INTERNAL_STRUCT_LEN)..], + ); + let b_query: MemoryQueryWitness = + CSAllocatableExt::witness_from_set_of_values(query); + guard.push_memory_witness(&b_query, execute_1); + + drop(guard); + }, + ); + } + + ( + new_memory_queue_state, + new_length_after_unaligned_write, + relations, + ) + }; + + // if crate::config::CIRCUIT_VERSOBE { + // if should_apply.witness_hook(&*cs)().unwrap() { + // dbg!(new_memory_queue_length_after_writes.witness_hook(&*cs)().unwrap()); + // } + // } + + let mut read_value_u256 = UInt256::zero(cs); + // read value is LE integer, while words are treated as BE + for (dst, src) in read_value_u256 + .inner + .iter_mut() + .rev() + .zip(selected_word.array_chunks::<4>()) + { + let mut le_bytes = *src; + le_bytes.reverse(); + let u32_word = UInt32::from_le_bytes(cs, le_bytes); + *dst = u32_word; + } + + let read_value_as_register = VMRegister { + is_pointer: boolean_false, + value: read_value_u256, + }; + + // compute incremented dst0 if we increment + let mut incremented_src0_register = common_opcode_state.src0; + incremented_src0_register.value.inner[0] = quasi_fat_ptr.incremented_offset; + + let is_write_access_and_increment = + Boolean::multi_and(cs, &[is_write_access, increment_offset]); + let update_dst0 = Boolean::multi_or(cs, &[is_read_access, is_write_access_and_increment]); + + let no_panic = set_panic.negated(cs); + let apply_any = Boolean::multi_and(cs, &[should_apply, no_panic]); + let should_update_dst0 = Boolean::multi_and(cs, &[apply_any, update_dst0]); + + let dst0_value = VMRegister::conditionally_select( + cs, + is_write_access_and_increment, + &incremented_src0_register, + &read_value_as_register, + ); + + let should_update_dst1 = Boolean::multi_and(cs, &[apply_any, is_read_access, increment_offset]); + + let can_write_into_memory = + UMA_HEAP_READ_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION); + + diffs_accumulator + .dst_0_values + .push((can_write_into_memory, should_update_dst0, dst0_value)); + diffs_accumulator + .dst_1_values + .push((should_update_dst1, incremented_src0_register)); + + // exceptions + let should_panic = Boolean::multi_and(cs, &[should_apply, set_panic]); + diffs_accumulator.pending_exceptions.push(should_panic); + + // and memory related staff + diffs_accumulator + .new_heap_bounds + .push((grow_heap, new_heap_upper_bound)); + diffs_accumulator + .new_aux_heap_bounds + .push((grow_aux_heap, new_aux_heap_upper_bound)); + // pay for growth + diffs_accumulator + .new_ergs_left_candidates + .push((should_apply, ergs_left_after_growth)); + // update sponges and queue states + + assert!(UMA_HEAP_READ_OPCODE.can_have_src0_from_mem(SUPPORTED_ISA_VERSION) == false); + assert!(UMA_HEAP_READ_OPCODE.can_write_dst0_into_memory(SUPPORTED_ISA_VERSION) == false); + + diffs_accumulator.sponge_candidates_to_run.push(( + false, + false, + apply_any, + sponge_candidates_after_writes, + )); + diffs_accumulator.memory_queue_candidates.push(( + should_apply, + new_memory_queue_length_after_writes, + new_memory_queue_tail_after_writes, + )); +} + +use boojum::gadgets::traits::allocatable::CSAllocatable; +use cs_derive::*; + +#[derive(CSAllocatable, WitnessHookable)] +pub struct QuasiFatPtrInUMA { + pub absolute_address: UInt32, + pub page_candidate: UInt32, + pub incremented_offset: UInt32, + pub heap_deref_out_of_bounds: Boolean, + pub skip_memory_access: Boolean, + pub should_set_panic: Boolean, + pub bytes_to_cleanup_out_of_bounds: UInt8, +} + +impl QuasiFatPtrInUMA { + pub(crate) fn parse_and_validate>( + cs: &mut CS, + input: &RegisterInputView, + already_panicked: Boolean, + is_fat_ptr: Boolean, + ) -> Self { + // we can never address a range [2^32 - 32..2^32] this way, but we don't care because + // it's impossible to pay for such memory growth + + let offset = input.u32x8_view[0]; + let page = input.u32x8_view[1]; + let start = input.u32x8_view[2]; + let length = input.u32x8_view[3]; + + // if crate::config::CIRCUIT_VERSOBE { + // dbg!(offset.witness_hook(&*cs)().unwrap()); + // dbg!(start.witness_hook(&*cs)().unwrap()); + // dbg!(length.witness_hook(&*cs)().unwrap()); + // } + + // we need to check whether we will or not deref the fat pointer. + // we only dereference if offset < length (or offset - length < 0) + let (_, offset_is_strictly_in_slice) = offset.overflowing_sub(cs, length); + let offset_is_beyond_the_slice = offset_is_strictly_in_slice.negated(cs); + let skip_if_legitimate_fat_ptr = + Boolean::multi_and(cs, &[offset_is_beyond_the_slice, is_fat_ptr]); + + // 0 of it's heap/aux heap, otherwise use what we have + let formal_start = start.mask(cs, is_fat_ptr); + // by prevalidating fat pointer we know that there is no overflow here, + // so we ignore the information + let (absolute_address, _of) = formal_start.overflowing_add(cs, offset); + + let u32_constant_32 = UInt32::allocated_constant(cs, 32); + + let (incremented_offset, is_non_addressable) = offset.overflowing_add(cs, u32_constant_32); + + // check that we agree in logic with out-of-circuit comparisons + debug_assert_eq!( + zkevm_opcode_defs::uma::MAX_OFFSET_TO_DEREF_LOW_U32 + 32u32, + u32::MAX + ); + let max_offset = UInt32::allocated_constant(cs, u32::MAX); + let is_non_addressable_extra = UInt32::equals(cs, &incremented_offset, &max_offset); + + let is_non_addressable = + Boolean::multi_or(cs, &[is_non_addressable, is_non_addressable_extra]); + + let should_set_panic = Boolean::multi_or(cs, &[already_panicked, is_non_addressable]); + + let skip_memory_access = Boolean::multi_or( + cs, + &[ + already_panicked, + skip_if_legitimate_fat_ptr, + is_non_addressable, + ], + ); + + // only necessary for fat pointer deref: now many bytes we zero-out beyond the end of fat pointer + let (mut bytes_out_of_bound, uf) = incremented_offset.overflowing_sub(cs, length); + + bytes_out_of_bound = bytes_out_of_bound.mask_negated(cs, skip_memory_access); + bytes_out_of_bound = bytes_out_of_bound.mask_negated(cs, uf); + + let (_, bytes_out_of_bound) = bytes_out_of_bound.div_by_constant(cs, 32); + // remainder fits into 8 bits too + let bytes_to_cleanup_out_of_bounds = + unsafe { UInt8::from_variable_unchecked(bytes_out_of_bound.get_variable()) }; + + let new = Self { + absolute_address, + page_candidate: page, + incremented_offset, + heap_deref_out_of_bounds: is_non_addressable, + skip_memory_access: skip_memory_access, + should_set_panic, + bytes_to_cleanup_out_of_bounds, + }; + + new + } +} + +// for integer N returns a field element with value 1 << N +pub fn uma_shift_into_bitspread>( + cs: &mut CS, + integer: Num, +) -> Num { + use crate::tables::integer_to_boolean_mask::UMAShiftToBitmaskTable; + + let table_id = cs + .get_table_id_for_marker::() + .expect("table must be added before"); + + let vals = cs.perform_lookup::<1, 2>(table_id, &[integer.get_variable()]); + let bitspread = vals[0]; + + Num::from_variable(bitspread) +} diff --git a/crates/zkevm_circuits/src/main_vm/pre_state.rs b/crates/zkevm_circuits/src/main_vm/pre_state.rs new file mode 100644 index 00000000..ff7ecc9b --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/pre_state.rs @@ -0,0 +1,489 @@ +use cs_derive::*; + +use super::witness_oracle::{SynchronizedWitnessOracle, WitnessOracle}; +use super::*; + +use crate::base_structures::register::VMRegister; +use crate::base_structures::vm_state::{ArithmeticFlagsPort, FULL_SPONGE_QUEUE_STATE_WIDTH}; +use crate::main_vm::decoded_opcode::OpcodePropertiesDecoding; +use crate::main_vm::register_input_view::RegisterInputView; +use crate::main_vm::utils::*; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::u16::UInt16; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Debug)] +pub struct CommonOpcodeState { + pub reseted_flags: ArithmeticFlagsPort, + pub current_flags: ArithmeticFlagsPort, + pub decoded_opcode: OpcodePropertiesDecoding, + pub src0: VMRegister, + pub src1: VMRegister, + pub src0_view: RegisterInputView, + pub src1_view: RegisterInputView, + pub timestamp_for_code_or_src_read: UInt32, + pub timestamp_for_first_decommit_or_precompile_read: UInt32, + pub timestamp_for_second_decommit_or_precompile_write: UInt32, + pub timestamp_for_dst_write: UInt32, +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct MemoryLocation { + pub page: UInt32, + pub index: UInt32, +} + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Debug)] +pub struct AfterDecodingCarryParts { + pub did_skip_cycle: Boolean, + pub heap_page: UInt32, + pub aux_heap_page: UInt32, + pub next_pc: UInt16, + pub preliminary_ergs_left: UInt32, + pub src0_read_sponge_data: PendingSponge, + pub dst0_memory_location: MemoryLocation, + pub dst0_performs_memory_access: Boolean, +} + +#[derive(Derivative, CSAllocatable, CSSelectable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct PendingSponge { + pub initial_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + pub final_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + pub should_enforce: Boolean, +} + +use crate::base_structures::vm_state::VmLocalState; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +// create a draft candidate for next VM state, as well as all the data required for +// opcodes to proceed +pub fn create_prestate< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + current_state: VmLocalState, + witness_oracle: &SynchronizedWitnessOracle, + round_function: &R, +) -> ( + VmLocalState, + CommonOpcodeState, + AfterDecodingCarryParts, +) { + let mut current_state = current_state; + + let execution_has_ended = current_state.callstack.is_empty(cs); + let should_skip_cycle = execution_has_ended; + let pending_exception = current_state.pending_exception; + let execute_cycle = should_skip_cycle.negated(cs); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(execution_has_ended.witness_hook(&*cs)().unwrap()); + } + + // we should even try to perform a read only if we have something to do this cycle + let should_try_to_read_opcode = execute_cycle.mask_negated(cs, pending_exception); + + let execute_pending_exception_at_this_cycle = pending_exception; + + // take down the flag + current_state.pending_exception = current_state + .pending_exception + .mask_negated(cs, execute_pending_exception_at_this_cycle); + + let current_pc = current_state.callstack.current_context.saved_context.pc; + + let one_u16 = UInt16::allocated_constant(cs, 1); + + let (pc_plus_one, _) = current_pc.overflowing_add(cs, &one_u16); + + let (super_pc, subpc_spread) = split_pc(cs, current_pc); + let previous_super_pc = current_state.previous_super_pc; + + let should_read_for_new_pc = should_read_memory( + cs, + current_state.previous_code_page, + current_state + .callstack + .current_context + .saved_context + .code_page, + super_pc, + previous_super_pc, + ); + + let should_read_opcode = + Boolean::multi_and(cs, &[should_try_to_read_opcode, should_read_for_new_pc]); + + // and in addition if we did finish execution then we never care and cleanup + + let location = MemoryLocation { + page: current_state + .callstack + .current_context + .saved_context + .code_page, + index: unsafe { UInt32::from_variable_unchecked(super_pc.get_variable()) }, + }; + + // precompute timestamps + let timestamp_for_code_or_src_read = current_state.timestamp; + let timestamp_for_first_decommit_or_precompile_read = + unsafe { timestamp_for_code_or_src_read.increment_unchecked(cs) }; + let timestamp_for_second_decommit_or_precompile_write = + unsafe { timestamp_for_first_decommit_or_precompile_read.increment_unchecked(cs) }; + let timestamp_for_dst_write = + unsafe { timestamp_for_second_decommit_or_precompile_write.increment_unchecked(cs) }; + let next_cycle_timestamp = unsafe { timestamp_for_dst_write.increment_unchecked(cs) }; + let next_cycle_timestamp = UInt32::conditionally_select( + cs, + should_skip_cycle, + ¤t_state.timestamp, + &next_cycle_timestamp, + ); + + // we can hardly make a judgement of using or not this sponge + // for optimization purposes, so we will assume that we always run it + + let (mut code_word, (new_memory_queue_state, new_memory_queue_length)) = + may_be_read_memory_for_code( + cs, + should_read_opcode, + timestamp_for_code_or_src_read, + location, + current_state.memory_queue_state, + current_state.memory_queue_length, + round_function, + witness_oracle, + ); + + // update current state + current_state.memory_queue_length = new_memory_queue_length; + current_state.memory_queue_state = new_memory_queue_state; + + code_word = UInt256::conditionally_select( + cs, + should_read_opcode, + &code_word, + ¤t_state.previous_code_word, + ); + + // subpc is 2 bits, so it's a range from 0 to 3. 1..=3 are bitspread via the table + let subpc_bitmask = subpc_spread.spread_into_bits::<_, 3>(cs); + + // default one is one corresponding to the "highest" bytes in 32 byte word in our BE machine + let opcode = [code_word.inner[6], code_word.inner[7]]; + let opcode = <[UInt32; 2]>::conditionally_select( + cs, + subpc_bitmask[0], + &[code_word.inner[4], code_word.inner[5]], + &opcode, + ); + let opcode = <[UInt32; 2]>::conditionally_select( + cs, + subpc_bitmask[1], + &[code_word.inner[2], code_word.inner[3]], + &opcode, + ); + let opcode = <[UInt32; 2]>::conditionally_select( + cs, + subpc_bitmask[2], + &[code_word.inner[0], code_word.inner[1]], + &opcode, + ); + + if crate::config::CIRCUIT_VERSOBE { + if should_skip_cycle.witness_hook(&*cs)().unwrap() { + println!("Skipping cycle"); + } + if execute_pending_exception_at_this_cycle.witness_hook(&*cs)().unwrap() { + println!("Executing pending exception"); + } + } + + // mask if we would be ok with NOPing. This masks a full 8-byte opcode, and not properties bitspread + // We mask if this cycle is just NOPing till the end of circuit + let opcode = mask_into_nop(cs, should_skip_cycle, opcode); + // if we are not pending, and we have an exception to run - run it + let opcode = mask_into_panic(cs, execute_pending_exception_at_this_cycle, opcode); + + // update super_pc and code words if we did read + current_state.previous_code_word = code_word; + // always update code page + current_state.previous_code_page = current_state + .callstack + .current_context + .saved_context + .code_page; + current_state.callstack.current_context.saved_context.pc = UInt16::conditionally_select( + cs, + should_skip_cycle, + ¤t_state.callstack.current_context.saved_context.pc, + &pc_plus_one, + ); + + current_state.previous_super_pc = UInt16::conditionally_select( + cs, + should_skip_cycle, + ¤t_state.previous_super_pc, + &super_pc, + ); // may be it can be unconditional + + // update timestamp + current_state.timestamp = next_cycle_timestamp; + + let is_kernel_mode = current_state + .callstack + .current_context + .saved_context + .is_kernel_mode; + let is_static_context = current_state + .callstack + .current_context + .saved_context + .is_static_execution; + let callstack_is_full = current_state.callstack.is_full(cs); + let ergs_left = current_state + .callstack + .current_context + .saved_context + .ergs_remaining; + + use crate::main_vm::decoded_opcode::encode_flags; + + let encoded_flags = encode_flags(cs, ¤t_state.flags); + + use crate::main_vm::decoded_opcode::perform_initial_decoding; + + let (decoded_opcode, dirty_ergs_left) = perform_initial_decoding( + cs, + opcode, + encoded_flags, + is_kernel_mode, + is_static_context, + callstack_is_full, + ergs_left, + should_skip_cycle, + ); + + // decoded opcode and current (yet dirty) ergs left should be passed into the opcode, + // but by default we set it into context that is true for most of the opcodes + current_state + .callstack + .current_context + .saved_context + .ergs_remaining = dirty_ergs_left; + + // we did all the masking and "INVALID" opcode must never happed + let invalid_opcode_bit = + decoded_opcode + .properties_bits + .boolean_for_opcode(zkevm_opcode_defs::Opcode::Invalid( + zkevm_opcode_defs::InvalidOpcode, + )); + + let boolean_false = Boolean::allocated_constant(cs, false); + Boolean::enforce_equal(cs, &invalid_opcode_bit, &boolean_false); + + // now read source operands + // select low part of the registers + let mut draft_src0 = VMRegister::::zero(cs); + for (mask_bit, register) in decoded_opcode.src_regs_selectors[0] + .iter() + .zip(current_state.registers.iter()) + { + draft_src0 = VMRegister::conditionally_select(cs, *mask_bit, ®ister, &draft_src0); + } + let src0_reg_lowest = draft_src0.value.inner[0].low_u16(cs); + + let mut src1_register = VMRegister::::zero(cs); + for (mask_bit, register) in decoded_opcode.src_regs_selectors[1] + .iter() + .zip(current_state.registers.iter()) + { + src1_register = VMRegister::conditionally_select(cs, *mask_bit, ®ister, &src1_register); + } + + let mut current_dst0_reg_low = UInt32::::zero(cs); + for (mask_bit, register) in decoded_opcode.dst_regs_selectors[0] + .iter() + .zip(current_state.registers.iter()) + { + let reg_low = register.value.inner[0]; + current_dst0_reg_low = + UInt32::conditionally_select(cs, *mask_bit, ®_low, ¤t_dst0_reg_low); + } + let dst0_reg_lowest = current_dst0_reg_low.low_u16(cs); + + let current_sp = current_state.callstack.current_context.saved_context.sp; + let code_page = current_state + .callstack + .current_context + .saved_context + .code_page; + let base_page = current_state + .callstack + .current_context + .saved_context + .base_page; + let stack_page = unsafe { base_page.increment_unchecked(cs) }; + let heap_page = unsafe { stack_page.increment_unchecked(cs) }; + let aux_heap_page = unsafe { heap_page.increment_unchecked(cs) }; + + if crate::config::CIRCUIT_VERSOBE { + dbg!(decoded_opcode.imm0.witness_hook(&*cs)().unwrap()); + dbg!(decoded_opcode.imm1.witness_hook(&*cs)().unwrap()); + } + + let (memory_location_for_src0, new_sp_after_src0, should_read_memory_for_src0) = + resolve_memory_region_and_index_for_source( + cs, + code_page, + stack_page, + src0_reg_lowest, + &decoded_opcode, + current_sp, + ); + + let (memory_location_for_dst0, new_sp, should_write_memory_for_dst0) = + resolve_memory_region_and_index_for_dest( + cs, + stack_page, + dst0_reg_lowest, + &decoded_opcode, + new_sp_after_src0, + ); + + current_state.callstack.current_context.saved_context.sp = new_sp; + + // perform actual read + + let ( + src0_register_from_mem, + ( + initial_state_src0_read_sponge, + final_state_src0_read_sponge, + new_memory_queue_length, + should_use_src0_read_sponge, + ), + ) = may_be_read_memory_for_source_operand( + cs, + should_read_memory_for_src0, + timestamp_for_code_or_src_read, + memory_location_for_src0, + current_state.memory_queue_state, + current_state.memory_queue_length, + round_function, + witness_oracle, + ); + + // update current state + current_state.memory_queue_length = new_memory_queue_length; + current_state.memory_queue_state = final_state_src0_read_sponge; + + // select source0 and source1 + + use zkevm_opcode_defs::ImmMemHandlerFlags; + + // select if it was reg + let use_reg = decoded_opcode + .properties_bits + .boolean_for_src_mem_access(ImmMemHandlerFlags::UseRegOnly); + let src0 = VMRegister::conditionally_select(cs, use_reg, &draft_src0, &src0_register_from_mem); + + // select if it was imm + let imm_as_reg = VMRegister::from_imm(cs, decoded_opcode.imm0); + let use_imm = decoded_opcode + .properties_bits + .boolean_for_src_mem_access(ImmMemHandlerFlags::UseImm16Only); + let src0 = VMRegister::conditionally_select(cs, use_imm, &imm_as_reg, &src0); + + // form an intermediate state to process the opcodes over it + let next_pc = pc_plus_one; + + // swap operands + let swap_operands = { + use zkevm_opcode_defs::*; + + let is_sub = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::Sub(SubOpcode::Sub)); + let is_div = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::Div(DivOpcode)); + let is_shift = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::Shift(ShiftOpcode::Rol)); + + let is_assymmetric = Boolean::multi_or(cs, &[is_sub, is_div, is_shift]); + let swap_flag = + decoded_opcode.properties_bits.flag_booleans[SWAP_OPERANDS_FLAG_IDX_FOR_ARITH_OPCODES]; + + let t0 = Boolean::multi_and(cs, &[is_assymmetric, swap_flag]); + + let is_ptr = decoded_opcode + .properties_bits + .boolean_for_opcode(Opcode::Ptr(PtrOpcode::Add)); + let swap_flag = + decoded_opcode.properties_bits.flag_booleans[SWAP_OPERANDS_FLAG_IDX_FOR_PTR_OPCODE]; + + let t1 = Boolean::multi_and(cs, &[is_ptr, swap_flag]); + + Boolean::multi_or(cs, &[t0, t1]) + }; + + 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 src0_view = RegisterInputView::from_input_value(cs, &src0); + let src1_view = RegisterInputView::from_input_value(cs, &src1); + + let empty_flags = ArithmeticFlagsPort::reseted_flags(cs); + + let common_opcode_state = CommonOpcodeState { + reseted_flags: empty_flags, + current_flags: current_state.flags, + decoded_opcode: decoded_opcode, + src0, + src1, + src0_view, + src1_view, + timestamp_for_code_or_src_read, + timestamp_for_first_decommit_or_precompile_read, + timestamp_for_second_decommit_or_precompile_write, + timestamp_for_dst_write, + }; + + let carry_parts = AfterDecodingCarryParts { + did_skip_cycle: should_skip_cycle, + next_pc, + src0_read_sponge_data: PendingSponge { + initial_state: initial_state_src0_read_sponge, + final_state: final_state_src0_read_sponge, + should_enforce: should_use_src0_read_sponge, + }, + dst0_memory_location: memory_location_for_dst0, + dst0_performs_memory_access: should_write_memory_for_dst0, + preliminary_ergs_left: dirty_ergs_left, + heap_page, + aux_heap_page, + }; + + (current_state, common_opcode_state, carry_parts) +} diff --git a/crates/zkevm_circuits/src/main_vm/register_input_view.rs b/crates/zkevm_circuits/src/main_vm/register_input_view.rs new file mode 100644 index 00000000..5db8a530 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/register_input_view.rs @@ -0,0 +1,54 @@ +use super::*; +use crate::base_structures::register::VMRegister; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::u32::UInt32; +use boojum::serde_utils::BigArraySerde; +use boojum::{field::SmallField, gadgets::u8::UInt8}; +use cs_derive::*; +use std::mem::MaybeUninit; + +// we can decompose register into bytes before passing it into individual opcodes +// because eventually those bytes will go into XOR/AND/OR table as inputs and will be range checked +// anyway + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Debug)] +pub struct RegisterInputView { + // used for bitwise operations and as a shift + pub u8x32_view: [UInt8; 32], + // copied from initial decomposition + pub u32x8_view: [UInt32; 8], + pub is_ptr: Boolean, +} + +impl RegisterInputView { + pub fn from_input_value>( + cs: &mut CS, + register: &VMRegister, + ) -> Self { + let mut u8x32_view: [MaybeUninit>; 32] = [MaybeUninit::uninit(); 32]; + + for (src, dst) in register + .value + .inner + .iter() + .zip(u8x32_view.array_chunks_mut::<4>()) + { + let decomposition = unsafe { src.decompose_into_bytes_unchecked(cs) }; + dst[0].write(decomposition[0]); + dst[1].write(decomposition[1]); + dst[2].write(decomposition[2]); + dst[3].write(decomposition[3]); + } + + let u8x32_view = unsafe { u8x32_view.map(|el| el.assume_init()) }; + + Self { + u8x32_view, + u32x8_view: register.value.inner, + is_ptr: register.is_pointer, + } + } +} diff --git a/crates/zkevm_circuits/src/main_vm/state_diffs.rs b/crates/zkevm_circuits/src/main_vm/state_diffs.rs new file mode 100644 index 00000000..8e05eedc --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/state_diffs.rs @@ -0,0 +1,99 @@ +use arrayvec::ArrayVec; + +use super::*; +use crate::base_structures::vm_state::*; +use crate::base_structures::{ + register::VMRegister, + vm_state::{callstack::Callstack, ArithmeticFlagsPort}, +}; +use boojum::field::SmallField; +use boojum::gadgets::num::Num; +use boojum::gadgets::{boolean::Boolean, u16::UInt16, u32::UInt32}; + +use crate::main_vm::opcodes::{AddSubRelation, MulDivRelation}; + +pub(crate) const MAX_SPONGES_PER_CYCLE: usize = 8; +pub(crate) const MAX_U32_CONDITIONAL_RANGE_CHECKS_PER_CYCLE: usize = 8; +pub(crate) const MAX_ADD_SUB_RELATIONS_PER_CYCLE: usize = 1; +pub(crate) const MAX_MUL_DIV_RELATIONS_PER_CYCLE: usize = 3; + +#[derive(Derivative)] +#[derivative(Default(bound = ""))] +pub struct StateDiffsAccumulator { + // dst0 candidates + pub dst_0_values: Vec<(bool, Boolean, VMRegister)>, + // dst1 candidates + pub dst_1_values: Vec<(Boolean, VMRegister)>, + // flags candidates + pub flags: Vec<(Boolean, ArithmeticFlagsPort)>, + // specific register updates + pub specific_registers_updates: [Vec<(Boolean, VMRegister)>; REGISTERS_COUNT], + // zero out specific registers + pub specific_registers_zeroing: [Vec>; REGISTERS_COUNT], + // remove ptr markers on specific registers + pub remove_ptr_on_specific_registers: [Vec>; REGISTERS_COUNT], + // pending exceptions, to be resolved next cycle. Should be masked by opcode applicability already + pub pending_exceptions: Vec>, + // ergs left, PC + // new ergs left if it's not one available after decoding + pub new_ergs_left_candidates: Vec<(Boolean, UInt32)>, + // new PC in case if it's not just PC+1 + pub new_pc_candidates: Vec<(Boolean, UInt16)>, + // other meta parameters of VM + pub new_tx_number: Option<(Boolean, UInt32)>, + pub new_ergs_per_pubdata: Option<(Boolean, UInt32)>, + // memory bouds + pub new_heap_bounds: Vec<(Boolean, UInt32)>, + pub new_aux_heap_bounds: Vec<(Boolean, UInt32)>, + // u128 special register, one from context, another from call/ret + pub context_u128_candidates: Vec<(Boolean, [UInt32; 4])>, + // internal machinery + pub callstacks: Vec<(Boolean, Callstack)>, + // memory page counter + pub memory_page_counters: Option>, + // decommittment queue + pub decommitment_queue_candidates: Option<( + Boolean, + UInt32, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + )>, + // memory queue + pub memory_queue_candidates: Vec<( + Boolean, + UInt32, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + )>, + // forward piece of log queue + pub log_queue_forward_candidates: Vec<(Boolean, UInt32, [Num; QUEUE_STATE_WIDTH])>, + // rollback piece of log queue + pub log_queue_rollback_candidates: Vec<(Boolean, UInt32, [Num; QUEUE_STATE_WIDTH])>, + // sponges to run. Should not include common sponges for src/dst operands + pub sponge_candidates_to_run: Vec<( + bool, + bool, + Boolean, + ArrayVec< + ( + Boolean, + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + ), + MAX_SPONGES_PER_CYCLE, + >, + )>, + // conditional range checks to enforce + pub u32_conditional_range_checks: Vec<( + Boolean, + [UInt32; MAX_U32_CONDITIONAL_RANGE_CHECKS_PER_CYCLE], // at the moment we only have one + )>, + // add/sub relations to enforce + pub add_sub_relations: Vec<( + Boolean, + ArrayVec, MAX_ADD_SUB_RELATIONS_PER_CYCLE>, + )>, + // mul/div relations to enforce + pub mul_div_relations: Vec<( + Boolean, + ArrayVec, MAX_MUL_DIV_RELATIONS_PER_CYCLE>, + )>, +} diff --git a/crates/zkevm_circuits/src/main_vm/utils.rs b/crates/zkevm_circuits/src/main_vm/utils.rs new file mode 100644 index 00000000..9463d6e3 --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/utils.rs @@ -0,0 +1,521 @@ +use boojum::field::SmallField; + +use super::decoded_opcode::OpcodePropertiesDecoding; +use super::witness_oracle::SynchronizedWitnessOracle; +use super::*; +use crate::base_structures::memory_query::{MemoryQuery, MemoryValue}; +use crate::base_structures::register::VMRegister; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::config::*; +use boojum::cs::gates::ConstantAllocatableCS; +use boojum::gadgets::traits::encodable::CircuitEncodable; +use boojum::gadgets::u256::UInt256; + +pub fn mask_into_nop>( + cs: &mut CS, + should_mask: Boolean, + opcode: [UInt32; 2], +) -> [UInt32; 2] { + use zkevm_opcode_defs::decoding::*; + let nop_encoding = EncodingModeProduction::nop_encoding(); + let low = nop_encoding as u32; + let low = UInt32::allocated_constant(cs, low); + let high = (nop_encoding >> 32) as u32; + let high = UInt32::allocated_constant(cs, high); + + <[UInt32; 2]>::conditionally_select(cs, should_mask, &[low, high], &opcode) +} + +pub fn mask_into_panic>( + cs: &mut CS, + should_mask: Boolean, + opcode: [UInt32; 2], +) -> [UInt32; 2] { + use zkevm_opcode_defs::decoding::*; + let nop_encoding = EncodingModeProduction::exception_revert_encoding(); + let low = nop_encoding as u32; + let low = UInt32::allocated_constant(cs, low); + let high = (nop_encoding >> 32) as u32; + let high = UInt32::allocated_constant(cs, high); + + <[UInt32; 2]>::conditionally_select(cs, should_mask, &[low, high], &opcode) +} + +pub(crate) const SUB_PC_BITS: usize = 2; +pub(crate) const SUB_PC_MASK: u16 = (1u16 << SUB_PC_BITS) - 1; + +pub(crate) fn split_pc>( + cs: &mut CS, + pc: UInt16, +) -> (UInt16, Num) { + let outputs = cs.alloc_multiple_variables_without_values::<2>(); + + if ::WitnessConfig::EVALUATE_WITNESS { + let value_fn = move |inputs: [F; 1]| { + let mut as_u64 = inputs[0].as_u64(); + let sub_pc = as_u64 & (SUB_PC_MASK as u64); + as_u64 >>= SUB_PC_BITS; + let super_pc = as_u64; + + [ + F::from_u64_unchecked(sub_pc), + F::from_u64_unchecked(super_pc), + ] + }; + + let dependencies = Place::from_variables([pc.get_variable()]); + + cs.set_values_with_dependencies(&dependencies, &Place::from_variables(outputs), value_fn); + } + + if ::SetupConfig::KEEP_SETUP { + use boojum::cs::gates::FmaGateInBaseFieldWithoutConstant; + + if cs.gate_is_allowed::>() { + let one = cs.allocate_constant(F::ONE); + let mut gate = FmaGateInBaseFieldWithoutConstant::empty(); + gate.quadratic_part = (one, outputs[0]); + gate.linear_part = outputs[1]; + use boojum::cs::gates::fma_gate_without_constant::FmaGateInBaseWithoutConstantParams; + gate.params = FmaGateInBaseWithoutConstantParams { + coeff_for_quadtaric_part: F::ONE, + linear_term_coeff: F::from_u64_unchecked(1u64 << SUB_PC_BITS), + }; + gate.rhs_part = pc.get_variable(); + + gate.add_to_cs(cs); + } else { + unimplemented!() + } + } + + let super_pc = UInt16::from_variable_checked(cs, outputs[1]); + + use crate::tables::integer_to_boolean_mask::VMSubPCToBitmaskTable; + let table_id = cs + .get_table_id_for_marker::() + .expect("table must be added before"); + + let vals = cs.perform_lookup::<1, 2>(table_id, &[outputs[0]]); + let bitspread = vals[0]; + let bitspread = Num::from_variable(bitspread); + + (super_pc, bitspread) +} + +#[inline] +pub(crate) fn should_read_memory>( + cs: &mut CS, + previous_code_page: UInt32, + current_code_page: UInt32, + super_pc: UInt16, + previous_super_pc: UInt16, +) -> Boolean { + let code_pages_are_equal = UInt32::equals(cs, &previous_code_page, ¤t_code_page); + let super_pc_are_equal = UInt16::equals(cs, &super_pc, &previous_super_pc); + + let can_skip = Boolean::multi_and(cs, &[code_pages_are_equal, super_pc_are_equal]); + + can_skip.negated(cs) +} + +use crate::base_structures::vm_state::FULL_SPONGE_QUEUE_STATE_WIDTH; +use crate::main_vm::pre_state::MemoryLocation; +use crate::main_vm::witness_oracle::WitnessOracle; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +/// NOTE: final state is one if we INDEED READ, so extra care should be taken to select and preserve markers +/// if we ever need it or not +pub(crate) fn may_be_read_memory_for_code< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + should_access: Boolean, + timestamp: UInt32, + location: MemoryLocation, + current_memory_sponge_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + current_memory_sponge_length: UInt32, + _round_function: &R, + witness_oracle: &SynchronizedWitnessOracle, +) -> ( + UInt256, + ([Num; FULL_SPONGE_QUEUE_STATE_WIDTH], UInt32), +) { + if crate::config::CIRCUIT_VERSOBE { + if should_access.witness_hook(&*cs)().unwrap() { + println!("Will read 32-byte word for opcode"); + // dbg!(timestamp.witness_hook(&*cs)().unwrap()); + // dbg!(location.witness_hook(&*cs)().unwrap()); + } + } + + let MemoryLocation { page, index } = location; + + let witness_oracle = witness_oracle.clone(); + let memory_value = MemoryValue::allocate_from_closure_and_dependencies_non_pointer( + cs, + move |inputs: &[F]| { + debug_assert_eq!(inputs.len(), 4); + let timestamp = inputs[0].as_u64() as u32; + let memory_page = inputs[1].as_u64() as u32; + let index = inputs[2].as_u64() as u32; + debug_assert!(inputs[3].as_u64() == 0 || inputs[3].as_u64() == 1); + let should_access = if inputs[3].as_u64() == 0 { false } else { true }; + + let mut guard = witness_oracle.inner.write().expect("not poisoned"); + let witness = + guard.get_memory_witness_for_read(timestamp, memory_page, index, should_access); + drop(guard); + + witness + }, + &[ + timestamp.get_variable().into(), + page.get_variable().into(), + index.get_variable().into(), + should_access.get_variable().into(), + ], + ); + + let boolean_false = Boolean::allocated_constant(cs, false); + + let query = MemoryQuery { + timestamp, + memory_page: page, + index, + is_ptr: memory_value.is_ptr, + value: memory_value.value, + rw_flag: boolean_false, + }; + + let packed_query = query.encode(cs); + + // this is absorb with replacement + let initial_state = [ + packed_query[0], + packed_query[1], + packed_query[2], + packed_query[3], + packed_query[4], + packed_query[5], + packed_query[6], + packed_query[7], + current_memory_sponge_state[8].get_variable(), + current_memory_sponge_state[9].get_variable(), + current_memory_sponge_state[10].get_variable(), + current_memory_sponge_state[11].get_variable(), + ]; + + let final_state_candidate = R::compute_round_function(cs, initial_state); + let final_state_candidate = final_state_candidate.map(|el| Num::from_variable(el)); + + // for all reasonable execution traces it's fine + let new_len_candidate = unsafe { current_memory_sponge_length.increment_unchecked(cs) }; + + let new_length = UInt32::conditionally_select( + cs, + should_access, + &new_len_candidate, + ¤t_memory_sponge_length, + ); + + let final_state = Num::parallel_select( + cs, + should_access, + &final_state_candidate, + ¤t_memory_sponge_state, + ); + + (memory_value.value, (final_state, new_length)) +} + +use zkevm_opcode_defs::ImmMemHandlerFlags; + +pub fn resolve_memory_region_and_index_for_source>( + cs: &mut CS, + code_page: UInt32, + stack_page: UInt32, + register_low_value: UInt16, + opcode_props: &OpcodePropertiesDecoding, + current_sp: UInt16, +) -> (MemoryLocation, UInt16, Boolean) { + // we assume that we did quickly select low part of the register before somehow, so we + + let use_code = opcode_props + .properties_bits + .boolean_for_src_mem_access(ImmMemHandlerFlags::UseCodePage); + let use_stack_absolute = opcode_props + .properties_bits + .boolean_for_src_mem_access(ImmMemHandlerFlags::UseAbsoluteOnStack); + let use_stack_relative = opcode_props + .properties_bits + .boolean_for_src_mem_access(ImmMemHandlerFlags::UseStackWithOffset); + let use_stack_with_push_pop = opcode_props + .properties_bits + .boolean_for_src_mem_access(ImmMemHandlerFlags::UseStackWithPushPop); + + let absolute_mode = Boolean::multi_or(cs, &[use_code, use_stack_absolute]); + let (index_for_absolute, _) = register_low_value.overflowing_add(cs, &opcode_props.imm0); + let (index_for_relative, _) = current_sp.overflowing_sub(cs, &index_for_absolute); + + // if we use absolute addressing then we just access reg + imm mod 2^16 + // if we use relative addressing then we access sp +/- (reg + imm), and if we push/pop then we update sp to such value + + // here we only read + + // manually unrolled selection. We KNOW that either we will not care about this particular value, + // or one of the bits here was set anyway + + let use_stack = Boolean::multi_or( + cs, + &[ + use_stack_absolute, + use_stack_relative, + use_stack_with_push_pop, + ], + ); + let did_read = Boolean::multi_or(cs, &[use_stack, use_code]); + // we have a special rule for NOP opcode: if we NOP then even though we CAN formally address the memory we SHOULD NOT read + let is_nop = opcode_props + .properties_bits + .boolean_for_opcode(zkevm_opcode_defs::Opcode::Nop(zkevm_opcode_defs::NopOpcode)); + + let not_nop = is_nop.negated(cs); + let did_read = Boolean::multi_and(cs, &[did_read, not_nop]); + let page = UInt32::conditionally_select(cs, use_stack, &stack_page, &code_page); + + let index = + UInt16::conditionally_select(cs, absolute_mode, &index_for_absolute, &index_for_relative); + + let new_sp = UInt16::conditionally_select( + cs, + use_stack_with_push_pop, + &index_for_relative, + ¤t_sp, + ); + let location = MemoryLocation { + page, + index: unsafe { UInt32::from_variable_unchecked(index.get_variable()) }, + }; + + (location, new_sp, did_read) +} + +pub fn resolve_memory_region_and_index_for_dest>( + cs: &mut CS, + stack_page: UInt32, + register_low_value: UInt16, + opcode_props: &OpcodePropertiesDecoding, + current_sp: UInt16, +) -> (MemoryLocation, UInt16, Boolean) { + // we assume that we did quickly select low part of the register before somehow, so we + + let use_stack_absolute = opcode_props + .properties_bits + .boolean_for_dst_mem_access(ImmMemHandlerFlags::UseAbsoluteOnStack); + let use_stack_relative = opcode_props + .properties_bits + .boolean_for_dst_mem_access(ImmMemHandlerFlags::UseStackWithOffset); + let use_stack_with_push_pop = opcode_props + .properties_bits + .boolean_for_dst_mem_access(ImmMemHandlerFlags::UseStackWithPushPop); + + let absolute_mode = use_stack_absolute; + let (index_for_absolute, _) = register_low_value.overflowing_add(cs, &opcode_props.imm1); + let (index_for_relative_with_push, _) = current_sp.overflowing_add(cs, &index_for_absolute); + let (index_for_relative, _) = current_sp.overflowing_sub(cs, &index_for_absolute); + + // if we use absolute addressing then we just access reg + imm mod 2^16 + // if we use relative addressing then we access sp +/- (reg + imm), and if we push/pop then we update sp + + // here we only write + + // manually unrolled selection. We KNOW that either we will not care about this particular value, + // or one of the bits here was set anyway + + let page = stack_page; + let did_write = Boolean::multi_or( + cs, + &[ + use_stack_absolute, + use_stack_relative, + use_stack_with_push_pop, + ], + ); + // we have a special rule for NOP opcode: if we NOP then even though we CAN formally address the memory we SHOULD NOT write + let is_nop = opcode_props + .properties_bits + .boolean_for_opcode(zkevm_opcode_defs::Opcode::Nop(zkevm_opcode_defs::NopOpcode)); + + let not_nop = is_nop.negated(cs); + let did_write = Boolean::multi_and(cs, &[did_write, not_nop]); + + let index_with_somewhat_relative_addressing = UInt16::conditionally_select( + cs, + use_stack_with_push_pop, + &index_for_relative_with_push, + &index_for_relative, + ); + + let index = UInt16::conditionally_select( + cs, + absolute_mode, + &index_for_absolute, + &index_with_somewhat_relative_addressing, + ); + + let new_sp = UInt16::conditionally_select( + cs, + use_stack_with_push_pop, + &index_for_relative_with_push, + ¤t_sp, + ); + + let location = MemoryLocation { + page, + index: unsafe { UInt32::from_variable_unchecked(index.get_variable()) }, + }; + + (location, new_sp, did_write) +} + +/// NOTE: final state is one if we INDEED READ, so extra care should be taken to select and preserve markers +/// if we ever need it or not +pub fn may_be_read_memory_for_source_operand< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + W: WitnessOracle, +>( + cs: &mut CS, + should_access: Boolean, + timestamp: UInt32, + location: MemoryLocation, + current_memory_sponge_state: [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + current_memory_sponge_length: UInt32, + _round_function: &R, + witness_oracle: &SynchronizedWitnessOracle, +) -> ( + VMRegister, + ( + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + [Num; FULL_SPONGE_QUEUE_STATE_WIDTH], + UInt32, + Boolean, + ), +) { + if crate::config::CIRCUIT_VERSOBE { + if should_access.witness_hook(&*cs)().unwrap() { + println!("Will read SRC0 from memory"); + dbg!(timestamp.witness_hook(&*cs)().unwrap()); + dbg!(location.witness_hook(&*cs)().unwrap()); + } + } + + let MemoryLocation { page, index } = location; + + let witness_oracle = witness_oracle.clone(); + let memory_value = MemoryValue::allocate_from_closure_and_dependencies( + cs, + move |inputs: &[F]| { + debug_assert_eq!(inputs.len(), 4); + let timestamp = inputs[0].as_u64() as u32; + let memory_page = inputs[1].as_u64() as u32; + let index = inputs[2].as_u64() as u32; + debug_assert!(inputs[3].as_u64() == 0 || inputs[3].as_u64() == 1); + let should_access = if inputs[3].as_u64() == 0 { false } else { true }; + + let mut guard = witness_oracle.inner.write().expect("not poisoned"); + let witness = + guard.get_memory_witness_for_read(timestamp, memory_page, index, should_access); + drop(guard); + + witness + }, + &[ + timestamp.get_variable().into(), + page.get_variable().into(), + index.get_variable().into(), + should_access.get_variable().into(), + ], + ); + + let boolean_false = Boolean::allocated_constant(cs, false); + + let query = MemoryQuery { + timestamp, + memory_page: page, + index, + is_ptr: memory_value.is_ptr, + value: memory_value.value, + rw_flag: boolean_false, + }; + + let packed_query = query.encode(cs); + + use boojum::gadgets::queue::full_state_queue::simulate_new_tail_for_full_state_queue; + + use crate::base_structures::memory_query::MEMORY_QUERY_PACKED_WIDTH; + + let simulated_values = simulate_new_tail_for_full_state_queue::< + F, + 8, + FULL_SPONGE_QUEUE_STATE_WIDTH, + 4, + MEMORY_QUERY_PACKED_WIDTH, + R, + _, + >( + cs, + packed_query, + current_memory_sponge_state.map(|el| el.get_variable()), + should_access, + ); + + let initial_state = [ + Num::from_variable(packed_query[0]), + Num::from_variable(packed_query[1]), + Num::from_variable(packed_query[2]), + Num::from_variable(packed_query[3]), + Num::from_variable(packed_query[4]), + Num::from_variable(packed_query[5]), + Num::from_variable(packed_query[6]), + Num::from_variable(packed_query[7]), + current_memory_sponge_state[8], + current_memory_sponge_state[9], + current_memory_sponge_state[10], + current_memory_sponge_state[11], + ]; + + let simulated_final_state = simulated_values.map(|el| Num::from_variable(el)); + + // for all reasonable execution traces it's fine + let new_len_candidate = unsafe { current_memory_sponge_length.increment_unchecked(cs) }; + + let new_length = UInt32::conditionally_select( + cs, + should_access, + &new_len_candidate, + ¤t_memory_sponge_length, + ); + + let final_state = Num::parallel_select( + cs, + should_access, + &simulated_final_state, + ¤t_memory_sponge_state, + ); + + let as_register = VMRegister { + is_pointer: memory_value.is_ptr, + value: memory_value.value, + }; + + ( + as_register, + (initial_state, final_state, new_length, should_access), + ) +} diff --git a/crates/zkevm_circuits/src/main_vm/witness_oracle.rs b/crates/zkevm_circuits/src/main_vm/witness_oracle.rs new file mode 100644 index 00000000..386e728f --- /dev/null +++ b/crates/zkevm_circuits/src/main_vm/witness_oracle.rs @@ -0,0 +1,168 @@ +use crate::ethereum_types::U256; + +use crate::base_structures::decommit_query::DecommitQueryWitness; +use crate::base_structures::vm_state::saved_context::ExecutionContextRecordWitness; +use boojum::field::SmallField; + +use super::*; + +#[derive(Derivative)] +#[derivative(Clone, Debug, Default)] +pub struct MemoryWitness { + pub value: U256, + pub is_ptr: bool, +} + +use std::sync::{Arc, RwLock}; + +pub struct SynchronizedWitnessOracle> { + pub inner: Arc>, + pub _marker: std::marker::PhantomData, +} + +impl> Clone for SynchronizedWitnessOracle { + fn clone(&self) -> Self { + Self { + inner: Arc::clone(&self.inner), + _marker: std::marker::PhantomData, + } + } +} + +impl> SynchronizedWitnessOracle { + pub fn new(raw_oracle: W) -> Self { + Self { + inner: Arc::new(RwLock::new(raw_oracle)), + _marker: std::marker::PhantomData, + } + } +} + +use crate::base_structures::log_query::LogQueryWitness; + +use crate::base_structures::memory_query::MemoryQueryWitness; + +pub trait WitnessOracle: + 'static + Send + Sync + Default + Clone + serde::Serialize + serde::de::DeserializeOwned +{ + fn get_memory_witness_for_read( + &mut self, + timestamp: u32, + memory_page: u32, + index: u32, + execute: bool, + ) -> MemoryWitness; + fn push_memory_witness(&mut self, memory_query: &MemoryQueryWitness, execute: bool); + fn get_storage_read_witness( + &mut self, + key: &LogQueryWitness, + needs_witness: bool, + execute: bool, + ) -> U256; + fn get_refunds(&mut self, query: &LogQueryWitness, is_write: bool, execute: bool) -> u32; + fn push_storage_witness(&mut self, key: &LogQueryWitness, execute: bool); + fn get_rollback_queue_witness(&mut self, key: &LogQueryWitness, execute: bool) -> [F; 4]; + fn get_rollback_queue_tail_witness_for_call(&mut self, timestamp: u32, execute: bool) + -> [F; 4]; + fn report_new_callstack_frame( + &mut self, + new_record: &ExecutionContextRecordWitness, + new_depth: u32, + is_call: bool, + execute: bool, + ); + fn push_callstack_witness( + &mut self, + current_record: &ExecutionContextRecordWitness, + current_depth: u32, + execute: bool, + ); + fn get_callstack_witness( + &mut self, + execute: bool, + depth: u32, + ) -> (ExecutionContextRecordWitness, [F; 12]); + fn get_decommittment_request_suggested_page( + &mut self, + request: &DecommitQueryWitness, + execute: bool, + ) -> u32; + fn at_completion(self) {} +} + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Copy, Debug, Default(bound = ""))] +pub struct DummyOracle { + pub _marker: std::marker::PhantomData, +} + +impl WitnessOracle for DummyOracle { + fn get_memory_witness_for_read( + &mut self, + _timestamp: u32, + _memory_page: u32, + _index: u32, + _execute: bool, + ) -> MemoryWitness { + todo!() + } + fn push_memory_witness(&mut self, _memory_query: &MemoryQueryWitness, _execute: bool) { + todo!() + } + fn get_storage_read_witness( + &mut self, + _key: &LogQueryWitness, + _needs_witness: bool, + _execute: bool, + ) -> U256 { + todo!() + } + fn get_refunds(&mut self, _query: &LogQueryWitness, _is_write: bool, _execute: bool) -> u32 { + todo!() + } + fn push_storage_witness(&mut self, _key: &LogQueryWitness, _execute: bool) { + todo!() + } + fn get_rollback_queue_witness(&mut self, _key: &LogQueryWitness, _execute: bool) -> [F; 4] { + todo!() + } + fn get_rollback_queue_tail_witness_for_call( + &mut self, + _timestamp: u32, + _execute: bool, + ) -> [F; 4] { + todo!() + } + fn report_new_callstack_frame( + &mut self, + _current_record: &ExecutionContextRecordWitness, + _new_depth: u32, + _is_call: bool, + _execute: bool, + ) { + todo!() + } + fn push_callstack_witness( + &mut self, + _current_record: &ExecutionContextRecordWitness, + _current_depth: u32, + _execute: bool, + ) { + todo!() + } + fn get_callstack_witness( + &mut self, + _execute: bool, + _depth: u32, + ) -> (ExecutionContextRecordWitness, [F; 12]) { + todo!() + } + fn get_decommittment_request_suggested_page( + &mut self, + _request: &DecommitQueryWitness, + _execute: bool, + ) -> u32 { + todo!() + } + fn at_completion(self) {} +} diff --git a/crates/zkevm_circuits/src/ram_permutation/input.rs b/crates/zkevm_circuits/src/ram_permutation/input.rs new file mode 100644 index 00000000..099c413e --- /dev/null +++ b/crates/zkevm_circuits/src/ram_permutation/input.rs @@ -0,0 +1,120 @@ +use crate::base_structures::{ + memory_query::{MemoryQuery, MEMORY_QUERY_PACKED_WIDTH}, + vm_state::*, +}; +use crate::boojum::gadgets::traits::auxiliary::PrettyComparison; +use crate::DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::full_state_queue::*, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, + u256::UInt256, + u32::UInt32, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Debug)] +pub struct RamPermutationInputData { + pub unsorted_queue_initial_state: QueueState, + pub sorted_queue_initial_state: QueueState, + pub non_deterministic_bootloader_memory_snapshot_length: UInt32, +} + +impl CSPlaceholder for RamPermutationInputData { + fn placeholder>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + let empty_state = QueueState::placeholder(cs); + + Self { + unsorted_queue_initial_state: empty_state, + sorted_queue_initial_state: empty_state, + non_deterministic_bootloader_memory_snapshot_length: zero_u32, + } + } +} + +pub const RAM_SORTING_KEY_LENGTH: usize = 3; +pub const RAM_FULL_KEY_LENGTH: usize = 2; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct RamPermutationFSMInputOutput { + pub lhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub rhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub current_unsorted_queue_state: QueueState, + pub current_sorted_queue_state: QueueState, + pub previous_sorting_key: [UInt32; RAM_SORTING_KEY_LENGTH], + pub previous_full_key: [UInt32; RAM_FULL_KEY_LENGTH], + pub previous_value: UInt256, + pub previous_is_ptr: Boolean, + pub num_nondeterministic_writes: UInt32, +} + +impl CSPlaceholder for RamPermutationFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + let zero_num = Num::zero(cs); + let zero_u32 = UInt32::zero(cs); + let zero_u256 = UInt256::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + let empty_state = QueueState::placeholder(cs); + + Self { + lhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + rhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + current_unsorted_queue_state: empty_state, + current_sorted_queue_state: empty_state, + previous_sorting_key: [zero_u32; RAM_SORTING_KEY_LENGTH], + previous_full_key: [zero_u32; RAM_FULL_KEY_LENGTH], + previous_value: zero_u256, + previous_is_ptr: boolean_false, + num_nondeterministic_writes: zero_u32, + } + } +} + +pub type RamPermutationCycleInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + RamPermutationFSMInputOutput, + RamPermutationInputData, + (), +>; +pub type RamPermutationCycleInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + RamPermutationFSMInputOutput, + RamPermutationInputData, + (), +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct RamPermutationCircuitInstanceWitness { + pub closed_form_input: RamPermutationCycleInputOutputWitness, + + pub unsorted_queue_witness: FullStateCircuitQueueRawWitness< + F, + MemoryQuery, + FULL_SPONGE_QUEUE_STATE_WIDTH, + MEMORY_QUERY_PACKED_WIDTH, + >, + pub sorted_queue_witness: FullStateCircuitQueueRawWitness< + F, + MemoryQuery, + FULL_SPONGE_QUEUE_STATE_WIDTH, + MEMORY_QUERY_PACKED_WIDTH, + >, +} + +pub type MemoryQueriesQueue = + FullStateCircuitQueue, 8, 12, 4, MEMORY_QUERY_PACKED_WIDTH, R>; diff --git a/crates/zkevm_circuits/src/ram_permutation/mod.rs b/crates/zkevm_circuits/src/ram_permutation/mod.rs new file mode 100644 index 00000000..7e3c13ae --- /dev/null +++ b/crates/zkevm_circuits/src/ram_permutation/mod.rs @@ -0,0 +1,633 @@ +use super::*; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use std::sync::Arc; + +use crate::base_structures::memory_query::MemoryQuery; +use crate::base_structures::memory_query::MEMORY_QUERY_PACKED_WIDTH; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use crate::fsm_input_output::commit_variable_length_encodable_item; +use crate::fsm_input_output::ClosedFormInputCompactForm; +use crate::storage_validity_by_grand_product::unpacked_long_comparison; +use crate::utils::accumulate_grand_products; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::gates::PublicInputGate; +use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::traits::allocatable::CSAllocatableExt; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +use zkevm_opcode_defs::BOOTLOADER_HEAP_PAGE; + +pub mod input; +use input::*; + +pub fn ram_permutation_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + closed_form_input_witness: RamPermutationCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let RamPermutationCircuitInstanceWitness { + closed_form_input, + unsorted_queue_witness, + sorted_queue_witness, + } = closed_form_input_witness; + + let mut structured_input = + RamPermutationCycleInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + + let start_flag = structured_input.start_flag; + let observable_input = structured_input.observable_input.clone(); + let hidden_fsm_input = structured_input.hidden_fsm_input.clone(); + + // passthrought must be trivial + observable_input + .unsorted_queue_initial_state + .enforce_trivial_head(cs); + + let unsorted_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &observable_input.unsorted_queue_initial_state, + &hidden_fsm_input.current_unsorted_queue_state, + ); + + use crate::boojum::gadgets::queue::full_state_queue::FullStateCircuitQueue; + let mut unsorted_queue: FullStateCircuitQueue< + F, + MemoryQuery, + 8, + 12, + 4, + MEMORY_QUERY_PACKED_WIDTH, + R, + > = MemoryQueriesQueue::from_state(cs, unsorted_queue_state); + + unsorted_queue.witness = Arc::new(FullStateCircuitQueueWitness::from_inner_witness( + unsorted_queue_witness, + )); + + // passthrought must be trivial + observable_input + .sorted_queue_initial_state + .enforce_trivial_head(cs); + + let sorted_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &observable_input.sorted_queue_initial_state, + &hidden_fsm_input.current_sorted_queue_state, + ); + + let mut sorted_queue: FullStateCircuitQueue< + F, + MemoryQuery, + 8, + 12, + 4, + MEMORY_QUERY_PACKED_WIDTH, + R, + > = MemoryQueriesQueue::from_state(cs, sorted_queue_state); + + sorted_queue.witness = Arc::new(FullStateCircuitQueueWitness::from_inner_witness( + sorted_queue_witness, + )); + + // get challenges for permutation argument + let fs_challenges = crate::utils::produce_fs_challenges( + cs, + observable_input.unsorted_queue_initial_state.tail, + observable_input.sorted_queue_initial_state.tail, + round_function, + ); + + let num_one = Num::allocated_constant(cs, F::ONE); + let mut lhs = <[Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]>::conditionally_select( + cs, + start_flag, + &[num_one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &hidden_fsm_input.lhs_accumulator, + ); + let mut rhs = <[Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]>::conditionally_select( + cs, + start_flag, + &[num_one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &hidden_fsm_input.rhs_accumulator, + ); + + let uint32_zero = UInt32::zero(cs); + let mut num_nondeterministic_writes = UInt32::conditionally_select( + cs, + start_flag, + &uint32_zero, + &hidden_fsm_input.num_nondeterministic_writes, + ); + + let mut previous_sorting_key = hidden_fsm_input.previous_sorting_key; + let mut previous_full_key = hidden_fsm_input.previous_full_key; + let mut previous_value = hidden_fsm_input.previous_value; + let mut previous_is_ptr = hidden_fsm_input.previous_is_ptr; + + partial_accumulate_inner::( + cs, + &mut unsorted_queue, + &mut sorted_queue, + &fs_challenges, + start_flag, + &mut lhs, + &mut rhs, + &mut previous_sorting_key, + &mut previous_full_key, + &mut previous_value, + &mut previous_is_ptr, + &mut num_nondeterministic_writes, + limit, + ); + + unsorted_queue.enforce_consistency(cs); + sorted_queue.enforce_consistency(cs); + + let completed = unsorted_queue.length.is_zero(cs); + + for (lhs, rhs) in lhs.iter().zip(rhs.iter()) { + Num::conditionally_enforce_equal(cs, completed, lhs, rhs); + } + + let num_nondeterministic_writes_equal = UInt32::equals( + cs, + &num_nondeterministic_writes, + &observable_input.non_deterministic_bootloader_memory_snapshot_length, + ); + num_nondeterministic_writes_equal.conditionally_enforce_true(cs, completed); + + // form the final state + structured_input + .hidden_fsm_output + .num_nondeterministic_writes = num_nondeterministic_writes; + structured_input + .hidden_fsm_output + .current_unsorted_queue_state = unsorted_queue.into_state(); + structured_input + .hidden_fsm_output + .current_sorted_queue_state = sorted_queue.into_state(); + + structured_input.hidden_fsm_output.lhs_accumulator = lhs; + structured_input.hidden_fsm_output.rhs_accumulator = rhs; + + structured_input.hidden_fsm_output.previous_sorting_key = previous_sorting_key; + structured_input.hidden_fsm_output.previous_full_key = previous_full_key; + structured_input.hidden_fsm_output.previous_value = previous_value; + structured_input.hidden_fsm_output.previous_is_ptr = previous_is_ptr; + + structured_input.completion_flag = completed; + + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +pub fn partial_accumulate_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + unsorted_queue: &mut MemoryQueriesQueue, + sorted_queue: &mut MemoryQueriesQueue, + fs_challenges: &[[Num; MEMORY_QUERY_PACKED_WIDTH + 1]; + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + is_start: Boolean, + lhs: &mut [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + rhs: &mut [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + previous_sorting_key: &mut [UInt32; RAM_SORTING_KEY_LENGTH], + previous_comparison_key: &mut [UInt32; RAM_FULL_KEY_LENGTH], + previous_element_value: &mut UInt256, + previous_is_ptr: &mut Boolean, + num_nondeterministic_writes: &mut UInt32, + limit: usize, +) where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); MEMORY_QUERY_PACKED_WIDTH]:, + [(); MEMORY_QUERY_PACKED_WIDTH + 1]:, +{ + let not_start = is_start.negated(cs); + Num::enforce_equal( + cs, + &unsorted_queue.length.into_num(), + &sorted_queue.length.into_num(), + ); + + let bootloader_heap_page = UInt32::allocated_constant(cs, BOOTLOADER_HEAP_PAGE); + let uint256_zero = UInt256::zero(cs); + + for _cycle in 0..limit { + let unsorted_is_empty = unsorted_queue.is_empty(cs); + let sorted_is_empty = sorted_queue.is_empty(cs); + + // this is an exotic way so synchronize popping from both queues + // in asynchronous resolution + Boolean::enforce_equal(cs, &unsorted_is_empty, &sorted_is_empty); + let can_pop = unsorted_is_empty.negated(cs); + + // we do not need any information about unsorted element other than it's encoding + let (_, unsorted_item_encoding) = unsorted_queue.pop_front(cs, can_pop); + let (sorted_item, sorted_item_encoding) = sorted_queue.pop_front(cs, can_pop); + + // check non-deterministic writes + { + let ts_is_zero = sorted_item.timestamp.is_zero(cs); + + let page_is_bootloader_heap = + UInt32::equals(cs, &sorted_item.memory_page, &bootloader_heap_page); + + let is_write = sorted_item.rw_flag; + let is_ptr = sorted_item.is_ptr; + let not_ptr = is_ptr.negated(cs); + + let is_nondeterministic_write = Boolean::multi_and( + cs, + &[ + can_pop, + ts_is_zero, + page_is_bootloader_heap, + is_write, + not_ptr, + ], + ); + + let num_nondeterministic_writes_incremented = + unsafe { UInt32::increment_unchecked(&num_nondeterministic_writes, cs) }; + + *num_nondeterministic_writes = UInt32::conditionally_select( + cs, + is_nondeterministic_write, + &num_nondeterministic_writes_incremented, + &num_nondeterministic_writes, + ); + } + + // check RAM ordering + { + // either continue the argument or do nothing + + let sorting_key = [ + sorted_item.timestamp, + sorted_item.index, + sorted_item.memory_page, + ]; + let comparison_key = [sorted_item.index, sorted_item.memory_page]; + + // ensure sorting + let (_keys_are_equal, previous_key_is_smaller) = + unpacked_long_comparison(cs, &sorting_key, previous_sorting_key); + + // we can not have previous sorting key even to be >= than our current key + + let keys_are_in_ascending_order = previous_key_is_smaller; + + if _cycle != 0 { + keys_are_in_ascending_order.conditionally_enforce_true(cs, can_pop); + } else { + let should_enforce = can_pop.and(cs, not_start); + keys_are_in_ascending_order.conditionally_enforce_true(cs, should_enforce); + } + + let same_memory_cell = long_equals(cs, &comparison_key, previous_comparison_key); + let value_equal = UInt256::equals(cs, &sorted_item.value, &previous_element_value); + + let not_same_cell = same_memory_cell.negated(cs); + let rw_flag = sorted_item.rw_flag; + let not_rw_flag = rw_flag.negated(cs); + + // check uninit read + let value_is_zero = UInt256::equals(cs, &sorted_item.value, &uint256_zero); + let is_ptr = sorted_item.is_ptr; + let not_ptr = is_ptr.negated(cs); + let is_zero = value_is_zero.and(cs, not_ptr); + let ptr_equality = Num::equals(cs, &previous_is_ptr.into_num(), &is_ptr.into_num()); + let value_and_ptr_equal = value_equal.and(cs, ptr_equality); + + // we only have a difference in these flags at the first step + if _cycle != 0 { + let read_uninitialized = not_same_cell.and(cs, not_rw_flag); + is_zero.conditionally_enforce_true(cs, read_uninitialized); + + // check standard RW validity + let check_equality = same_memory_cell.and(cs, not_rw_flag); + value_and_ptr_equal.conditionally_enforce_true(cs, check_equality); + } else { + // see if we continue the argument then all our checks should be valid, + // otherwise only read uninit should be enforced + + // if we start a fresh argument then our comparison + let read_uninitialized_if_continue = + Boolean::multi_and(cs, &[not_start, not_same_cell, not_rw_flag]); + let read_uninit_if_at_the_start = is_start.and(cs, not_rw_flag); + let should_enforce = + read_uninitialized_if_continue.or(cs, read_uninit_if_at_the_start); + is_zero.conditionally_enforce_true(cs, should_enforce); + + // check standard RW validity, but it can break if we are at the very start + let check_equality = + Boolean::multi_and(cs, &[same_memory_cell, not_rw_flag, not_start]); + value_and_ptr_equal.conditionally_enforce_true(cs, check_equality); + } + + *previous_sorting_key = sorting_key; + *previous_comparison_key = comparison_key; + *previous_element_value = sorted_item.value; + *previous_is_ptr = sorted_item.is_ptr; + } + + // if we did pop then accumulate to grand product + accumulate_grand_products::< + F, + CS, + MEMORY_QUERY_PACKED_WIDTH, + { MEMORY_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + lhs, + rhs, + fs_challenges, + &unsorted_item_encoding, + &sorted_item_encoding, + can_pop, + ); + } +} + +pub(crate) fn long_equals, const N: usize>( + cs: &mut CS, + a: &[UInt32; N], + b: &[UInt32; N], +) -> Boolean { + let equals: [_; N] = std::array::from_fn(|i| UInt32::equals(cs, &a[i], &b[i])); + + Boolean::multi_and(cs, &equals) +} + +#[cfg(test)] +mod tests { + use super::*; + use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; + use boojum::cs::gates::*; + use boojum::cs::implementations::reference_cs::CSDevelopmentAssembly; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::tables::*; + use boojum::gadgets::traits::allocatable::CSPlaceholder; + use boojum::gadgets::u160::UInt160; + use boojum::gadgets::u256::UInt256; + use boojum::gadgets::u8::UInt8; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::worker::Worker; + use ethereum_types::{Address, U256}; + type F = GoldilocksField; + type P = GoldilocksField; + + #[test] + fn test_ram_permutation_inner() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + use boojum::cs::cs_builder::*; + + 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 = 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 = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 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(()); + + // add tables + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let cs = &mut owned_cs; + + let execute = Boolean::allocated_constant(cs, true); + let mut original_queue = MemoryQueriesQueue::::empty(cs); + let unsorted_input = witness_input_unsorted(cs); + for el in unsorted_input { + original_queue.push(cs, el, execute); + } + let mut sorted_queue = MemoryQueriesQueue::::empty(cs); + let sorted_input = witness_input_sorted(cs); + for el in sorted_input { + sorted_queue.push(cs, el, execute); + } + + let mut lhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let mut rhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let is_start = Boolean::allocated_constant(cs, true); + let round_function = Poseidon2Goldilocks; + let fs_challenges = crate::utils::produce_fs_challenges( + cs, + original_queue.into_state().tail, + sorted_queue.into_state().tail, + &round_function, + ); + let limit = 16; + let mut previous_sorting_key = [UInt32::allocated_constant(cs, 0); RAM_SORTING_KEY_LENGTH]; + let mut previous_comparison_key = [UInt32::allocated_constant(cs, 0); RAM_FULL_KEY_LENGTH]; + let mut previous_element_value = + UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()); + let mut previous_is_ptr = Boolean::allocated_constant(cs, false); + let mut num_nondeterministic_writes = UInt32::allocated_constant(cs, 1); + partial_accumulate_inner( + cs, + &mut original_queue, + &mut sorted_queue, + &fs_challenges, + is_start, + &mut lhs, + &mut rhs, + &mut previous_sorting_key, + &mut previous_comparison_key, + &mut previous_element_value, + &mut previous_is_ptr, + &mut num_nondeterministic_writes, + limit, + ); + + cs.pad_and_shrink(); + let worker = Worker::new(); + let mut owned_cs = owned_cs.into_assembly(); + owned_cs.print_gate_stats(); + assert!(owned_cs.check_if_satisfied(&worker)); + } + + fn witness_input_unsorted>(cs: &mut CS) -> Vec> { + let mut unsorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + + let q = MemoryQuery:: { + timestamp: UInt32::allocated_constant(cs, 1025), + memory_page: UInt32::allocated_constant(cs, 30), + index: UInt32::allocated_constant(cs, 0), + rw_flag: bool_false, + is_ptr: bool_false, + value: UInt256::allocated_constant(cs, U256::from_dec_str("1125899906842626").unwrap()), + }; + unsorted_querie.push(q); + + let q = MemoryQuery:: { + timestamp: UInt32::allocated_constant(cs, 1024), + memory_page: UInt32::allocated_constant(cs, 30), + index: UInt32::allocated_constant(cs, 0), + rw_flag: bool_true, + is_ptr: bool_false, + value: UInt256::allocated_constant(cs, U256::from_dec_str("1125899906842626").unwrap()), + }; + + unsorted_querie.push(q); + + let q = MemoryQuery:: { + timestamp: UInt32::allocated_constant(cs, 0), + memory_page: UInt32::allocated_constant(cs, BOOTLOADER_HEAP_PAGE), + index: UInt32::allocated_constant(cs, 695), + rw_flag: bool_true, + is_ptr: bool_false, + value: UInt256::allocated_constant(cs, U256::from_dec_str("12345678").unwrap()), + }; + unsorted_querie.push(q); + + unsorted_querie + } + + fn witness_input_sorted>(cs: &mut CS) -> Vec> { + let mut sorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + + let q = MemoryQuery:: { + timestamp: UInt32::allocated_constant(cs, 0), + memory_page: UInt32::allocated_constant(cs, BOOTLOADER_HEAP_PAGE), + index: UInt32::allocated_constant(cs, 695), + rw_flag: bool_true, + is_ptr: bool_false, + value: UInt256::allocated_constant(cs, U256::from_dec_str("12345678").unwrap()), + }; + sorted_querie.push(q); + + let q = MemoryQuery:: { + timestamp: UInt32::allocated_constant(cs, 1024), + memory_page: UInt32::allocated_constant(cs, 30), + index: UInt32::allocated_constant(cs, 0), + rw_flag: bool_true, + is_ptr: bool_false, + value: UInt256::allocated_constant(cs, U256::from_dec_str("1125899906842626").unwrap()), + }; + sorted_querie.push(q); + + let q = MemoryQuery:: { + timestamp: UInt32::allocated_constant(cs, 1025), + memory_page: UInt32::allocated_constant(cs, 30), + index: UInt32::allocated_constant(cs, 0), + rw_flag: bool_false, + is_ptr: bool_false, + value: UInt256::allocated_constant(cs, U256::from_dec_str("1125899906842626").unwrap()), + }; + sorted_querie.push(q); + + sorted_querie + } +} diff --git a/crates/zkevm_circuits/src/recursion/compression/input.rs b/crates/zkevm_circuits/src/recursion/compression/input.rs new file mode 100644 index 00000000..b9cb60e9 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/compression/input.rs @@ -0,0 +1,23 @@ +use super::*; + +use boojum::cs::implementations::proof::Proof; +use boojum::field::FieldExtension; +use boojum::field::SmallField; +use boojum::gadgets::num::Num; +use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use std::collections::VecDeque; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default(bound = ""))] +#[serde( + bound = ">::Witness: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct CompressionCircuitInstanceWitness< + F: SmallField, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, +> { + #[derivative(Debug = "ignore")] + pub proof_witness: Option>, +} diff --git a/crates/zkevm_circuits/src/recursion/compression/mod.rs b/crates/zkevm_circuits/src/recursion/compression/mod.rs new file mode 100644 index 00000000..0bd8c1b9 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/compression/mod.rs @@ -0,0 +1,121 @@ +use super::*; + +pub mod input; +pub use self::input::*; + +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::config::*; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::prover::ProofConfig; +use boojum::cs::implementations::verifier::VerificationKey; +use boojum::cs::oracle::TreeHasher; +use boojum::cs::traits::circuit::ErasedBuilderForRecursiveVerifier; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::FieldExtension; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::recursion::allocated_proof::AllocatedProof; +use boojum::gadgets::recursion::allocated_vk::AllocatedVerificationKey; +use boojum::gadgets::recursion::circuit_pow::RecursivePoWRunner; +use boojum::gadgets::recursion::recursive_transcript::*; +use boojum::gadgets::recursion::recursive_tree_hasher::*; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +// We recursively verify SINGLE proofs over FIXED VK and output it's inputs + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug)] +#[serde(bound = "H::Output: serde::Serialize + serde::de::DeserializeOwned")] +pub struct CompressionRecursionConfig< + F: SmallField, + H: TreeHasher, + EXT: FieldExtension<2, BaseField = F>, +> { + pub proof_config: ProofConfig, + pub verification_key: VerificationKey, + pub _marker: std::marker::PhantomData<(F, H, EXT)>, +} + +pub fn proof_compression_function< + F: SmallField, + CS: ConstraintSystem + 'static, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, + TR: RecursiveTranscript< + F, + CompatibleCap = >::Output, + CircuitReflection = CTR, + >, + CTR: CircuitTranscript< + F, + CircuitCompatibleCap = >>::CircuitOutput, + TransciptParameters = TR::TransciptParameters, + >, + POW: RecursivePoWRunner, +>( + cs: &mut CS, + witness: CompressionCircuitInstanceWitness, + config: CompressionRecursionConfig, + verifier_builder: Box>, + transcript_params: TR::TransciptParameters, +) { + let CompressionCircuitInstanceWitness { proof_witness } = witness; + + // as usual - create verifier for FIXED VK, verify, aggregate inputs, output inputs + + let CompressionRecursionConfig { + proof_config, + verification_key, + .. + } = config; + + // use this and deal with borrow checker + + let r = cs as *mut CS; + + assert_eq!( + verification_key.fixed_parameters.parameters, + verifier_builder.geometry() + ); + + let fixed_parameters = verification_key.fixed_parameters.clone(); + + let verifier = verifier_builder.create_recursive_verifier(cs); + + let cs = unsafe { &mut *r }; + + let vk = AllocatedVerificationKey::allocate_constant(cs, verification_key); + + let proof = AllocatedProof::allocate_from_witness( + cs, + proof_witness, + &verifier, + &fixed_parameters, + &proof_config, + ); + + // verify the proof + let (is_valid, public_inputs) = verifier.verify::( + cs, + transcript_params.clone(), + &proof, + &fixed_parameters, + &proof_config, + &vk, + ); + + let boolean_true = Boolean::allocated_constant(cs, true); + Boolean::enforce_equal(cs, &is_valid, &boolean_true); + + assert_eq!(public_inputs.len(), INPUT_OUTPUT_COMMITMENT_LENGTH); + assert_eq!(public_inputs.len(), fixed_parameters.num_public_inputs()); + + for el in public_inputs.into_iter() { + use boojum::cs::gates::PublicInputGate; + let gate = PublicInputGate::new(el.get_variable()); + gate.add_to_cs(cs); + } +} diff --git a/crates/zkevm_circuits/src/recursion/interblock/input.rs b/crates/zkevm_circuits/src/recursion/interblock/input.rs new file mode 100644 index 00000000..9c24e613 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/interblock/input.rs @@ -0,0 +1,23 @@ +use super::*; + +use boojum::cs::implementations::proof::Proof; +use boojum::field::FieldExtension; +use boojum::field::SmallField; +use boojum::gadgets::num::Num; +use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use std::collections::VecDeque; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default(bound = ""))] +#[serde( + bound = ">::Witness: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct InterblockRecursionCircuitInstanceWitness< + F: SmallField, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, +> { + #[derivative(Debug = "ignore")] + pub proof_witnesses: VecDeque>, +} diff --git a/crates/zkevm_circuits/src/recursion/interblock/keccak_aggregator.rs b/crates/zkevm_circuits/src/recursion/interblock/keccak_aggregator.rs new file mode 100644 index 00000000..9adc5cf3 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/interblock/keccak_aggregator.rs @@ -0,0 +1,96 @@ +use boojum::gadgets::{traits::selectable::Selectable, u8::UInt8}; + +use super::*; + +pub struct KeccakPublicInputAggregator< + F: SmallField, + const N: usize, + const IS_BE: bool, + const NUM_OUTS: usize, +> { + pub masking_value: u8, + _marker: std::marker::PhantomData, +} + +impl + InputAggregationFunction for KeccakPublicInputAggregator +{ + type Params = u8; + + fn new>(_cs: &mut CS, params: Self::Params) -> Self { + Self { + masking_value: params, + _marker: std::marker::PhantomData, + } + } + fn aggregate_inputs>( + &self, + cs: &mut CS, + inputs: &[Vec>], + validity_flags: &[Boolean], + ) -> Vec> { + assert_eq!(inputs.len(), N); + assert_eq!(validity_flags.len(), N); + + let masking_value = UInt8::allocated_constant(cs, self.masking_value); + + let mut input_flattened_bytes = Vec::with_capacity(32 * N); + let zero_u8 = UInt8::zero(cs); + let take_by = F::CAPACITY_BITS / 8; + + let mut total_byte_len = take_by; + if F::CAPACITY_BITS % 8 != 0 { + total_byte_len += 1; + } + + for (validity_flag, input) in validity_flags.iter().zip(inputs.iter()) { + assert_eq!(input.len(), INPUT_OUTPUT_COMMITMENT_LENGTH); + + // transform to bytes + for src in input.iter() { + let mut bytes: arrayvec::ArrayVec, 8> = + src.constraint_bit_length_as_bytes(cs, total_byte_len); // le + if F::CAPACITY_BITS % 8 != 0 { + for el in bytes[take_by..].iter() { + // assert byte is 0 + Num::conditionally_enforce_equal( + cs, + *validity_flag, + &el.into_num(), + &zero_u8.into_num(), + ); + } + } + // mask if necessary + for el in bytes[..take_by].iter_mut() { + *el = UInt8::conditionally_select(cs, *validity_flag, &*el, &masking_value); + } + + if IS_BE { + input_flattened_bytes.extend(bytes[..take_by].iter().copied().rev()); + } else { + input_flattened_bytes.extend_from_slice(&bytes[..take_by]); + } + } + } + + // run keccak over it + use boojum::gadgets::keccak256; + let aggregated_keccak_hash = keccak256::keccak256(cs, &input_flattened_bytes); + + let mut result = Vec::with_capacity(NUM_OUTS); + + // and make it our publid input + for chunk in aggregated_keccak_hash.chunks_exact(take_by).take(NUM_OUTS) { + let mut lc = Vec::with_capacity(chunk.len()); + // treat as BE + for (idx, el) in chunk.iter().rev().enumerate() { + lc.push((el.get_variable(), F::SHIFTS[idx * 8])); + } + let as_num = Num::linear_combination(cs, &lc); + result.push(as_num); + } + + result + } +} diff --git a/crates/zkevm_circuits/src/recursion/interblock/mod.rs b/crates/zkevm_circuits/src/recursion/interblock/mod.rs new file mode 100644 index 00000000..d0e45e20 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/interblock/mod.rs @@ -0,0 +1,154 @@ +use super::*; + +pub mod input; +pub use self::input::*; + +pub mod keccak_aggregator; + +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::config::*; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::prover::ProofConfig; +use boojum::cs::implementations::verifier::VerificationKey; +use boojum::cs::oracle::TreeHasher; +use boojum::cs::traits::circuit::ErasedBuilderForRecursiveVerifier; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::FieldExtension; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::num::Num; +use boojum::gadgets::recursion::allocated_proof::AllocatedProof; +use boojum::gadgets::recursion::allocated_vk::AllocatedVerificationKey; +use boojum::gadgets::recursion::circuit_pow::RecursivePoWRunner; +use boojum::gadgets::recursion::recursive_transcript::*; +use boojum::gadgets::recursion::recursive_tree_hasher::*; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; + +// performs recursion between "independent" units for FIXED verification key + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug)] +#[serde(bound = "H::Output: serde::Serialize + serde::de::DeserializeOwned")] +pub struct InterblockRecursionConfig< + F: SmallField, + H: TreeHasher, + EXT: FieldExtension<2, BaseField = F>, +> { + pub proof_config: ProofConfig, + pub verification_key: VerificationKey, + pub capacity: usize, + pub _marker: std::marker::PhantomData<(F, H, EXT)>, +} + +pub trait InputAggregationFunction { + type Params; + + fn new>(cs: &mut CS, params: Self::Params) -> Self; + fn aggregate_inputs>( + &self, + cs: &mut CS, + inputs: &[Vec>], + validity_flags: &[Boolean], + ) -> Vec>; +} + +pub fn interblock_recursion_function< + F: SmallField, + CS: ConstraintSystem + 'static, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, + TR: RecursiveTranscript< + F, + CompatibleCap = >::Output, + CircuitReflection = CTR, + >, + CTR: CircuitTranscript< + F, + CircuitCompatibleCap = >>::CircuitOutput, + TransciptParameters = TR::TransciptParameters, + >, + POW: RecursivePoWRunner, + AGG: InputAggregationFunction, +>( + cs: &mut CS, + witness: InterblockRecursionCircuitInstanceWitness, + config: InterblockRecursionConfig, + verifier_builder: Box>, + transcript_params: TR::TransciptParameters, + aggregation_params: AGG::Params, +) { + let InterblockRecursionCircuitInstanceWitness { proof_witnesses } = witness; + let mut proof_witnesses = proof_witnesses; + + // as usual - create verifier for FIXED VK, verify, aggregate inputs, output inputs + + let InterblockRecursionConfig { + proof_config, + verification_key, + capacity, + .. + } = config; + + // use this and deal with borrow checker + + let r = cs as *mut CS; + + assert_eq!( + verification_key.fixed_parameters.parameters, + verifier_builder.geometry() + ); + + let fixed_parameters = verification_key.fixed_parameters.clone(); + + let verifier = verifier_builder.create_recursive_verifier(cs); + + let cs = unsafe { &mut *r }; + + let mut validity_flags = Vec::with_capacity(capacity); + let mut inputs = Vec::with_capacity(capacity); + + let vk = AllocatedVerificationKey::allocate_constant(cs, verification_key); + + for _ in 0..capacity { + let proof_witness = proof_witnesses.pop_front(); + + let proof = AllocatedProof::allocate_from_witness( + cs, + proof_witness, + &verifier, + &fixed_parameters, + &proof_config, + ); + + // verify the proof + let (is_valid, public_inputs) = verifier.verify::( + cs, + transcript_params.clone(), + &proof, + &fixed_parameters, + &proof_config, + &vk, + ); + + assert_eq!(public_inputs.len(), INPUT_OUTPUT_COMMITMENT_LENGTH); + assert_eq!(public_inputs.len(), fixed_parameters.num_public_inputs()); + + validity_flags.push(is_valid); + inputs.push(public_inputs); + } + + // now actually aggregate + + let aggregator = AGG::new(cs, aggregation_params); + let aggregated_input = aggregator.aggregate_inputs(cs, &inputs, &validity_flags); + + assert_eq!(aggregated_input.len(), INPUT_OUTPUT_COMMITMENT_LENGTH); + + for el in aggregated_input.into_iter() { + use boojum::cs::gates::PublicInputGate; + let gate = PublicInputGate::new(el.get_variable()); + gate.add_to_cs(cs); + } +} diff --git a/crates/zkevm_circuits/src/recursion/leaf_layer/input.rs b/crates/zkevm_circuits/src/recursion/leaf_layer/input.rs new file mode 100644 index 00000000..6f4153aa --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/leaf_layer/input.rs @@ -0,0 +1,83 @@ +use super::*; +use crate::base_structures::recursion_query::*; +use crate::base_structures::vm_state::*; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::verifier::VerificationKey; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::num::Num; +use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueueRawWitness; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::{ + boolean::Boolean, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use cs_derive::*; + +use boojum::field::FieldExtension; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct RecursionLeafParameters { + pub circuit_type: Num, + pub basic_circuit_vk_commitment: [Num; VK_COMMITMENT_LENGTH], + pub leaf_layer_vk_commitment: [Num; VK_COMMITMENT_LENGTH], +} + +impl CSPlaceholder for RecursionLeafParameters { + fn placeholder>(cs: &mut CS) -> Self { + let zero = Num::zero(cs); + Self { + circuit_type: zero, + basic_circuit_vk_commitment: [zero; VK_COMMITMENT_LENGTH], + leaf_layer_vk_commitment: [zero; VK_COMMITMENT_LENGTH], + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct RecursionLeafInput { + pub params: RecursionLeafParameters, + pub queue_state: QueueState, +} + +impl CSPlaceholder for RecursionLeafInput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + params: RecursionLeafParameters::placeholder(cs), + queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative( + Clone, + Debug(bound = ""), + Default(bound = "RecursionLeafInputWitness: Default") +)] +#[serde( + bound = ">::Witness: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct RecursionLeafInstanceWitness< + F: SmallField, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, +> { + pub input: RecursionLeafInputWitness, + pub vk_witness: VerificationKey, + pub queue_witness: FullStateCircuitQueueRawWitness< + F, + RecursionQuery, + FULL_SPONGE_QUEUE_STATE_WIDTH, + RECURSION_QUERY_PACKED_WIDTH, + >, + pub proof_witnesses: VecDeque>, +} diff --git a/crates/zkevm_circuits/src/recursion/leaf_layer/mod.rs b/crates/zkevm_circuits/src/recursion/leaf_layer/mod.rs new file mode 100644 index 00000000..645f2d61 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/leaf_layer/mod.rs @@ -0,0 +1,214 @@ +use crate::base_structures::recursion_query::{RecursionQuery, RecursionQueue}; +use crate::fsm_input_output::commit_variable_length_encodable_item; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::prover::ProofConfig; +use boojum::gadgets::recursion::allocated_proof::AllocatedProof; +use boojum::gadgets::recursion::allocated_vk::AllocatedVerificationKey; +use boojum::gadgets::recursion::recursive_transcript::RecursiveTranscript; +use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; + +use std::collections::VecDeque; +use std::sync::Arc; + +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::config::*; +use boojum::cs::traits::circuit::ErasedBuilderForRecursiveVerifier; +use boojum::cs::{gates::*, traits::cs::ConstraintSystem}; +use boojum::field::SmallField; +use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueueWitness; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{allocatable::CSAllocatable, allocatable::CSAllocatableExt}, +}; + +use super::*; + +pub mod input; + +use self::input::*; + +use boojum::cs::implementations::verifier::VerificationKeyCircuitGeometry; +use boojum::cs::oracle::TreeHasher; +use boojum::field::FieldExtension; +use boojum::gadgets::recursion::circuit_pow::RecursivePoWRunner; +use boojum::gadgets::recursion::recursive_transcript::CircuitTranscript; +use boojum::gadgets::recursion::recursive_tree_hasher::CircuitTreeHasher; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug(bound = ""))] +#[serde(bound = "H::Output: serde::Serialize + serde::de::DeserializeOwned")] +pub struct LeafLayerRecursionConfig< + F: SmallField, + H: TreeHasher, + EXT: FieldExtension<2, BaseField = F>, +> { + pub proof_config: ProofConfig, + pub vk_fixed_parameters: VerificationKeyCircuitGeometry, + pub capacity: usize, + pub _marker: std::marker::PhantomData<(F, H, EXT)>, +} + +// NOTE: does NOT allocate public inputs! we will deal with locations of public inputs being the same at the "outer" stage +pub fn leaf_layer_recursion_entry_point< + F: SmallField, + CS: ConstraintSystem + 'static, + R: CircuitRoundFunction + AlgebraicRoundFunction, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, + TR: RecursiveTranscript< + F, + CompatibleCap = >::Output, + CircuitReflection = CTR, + >, + CTR: CircuitTranscript< + F, + CircuitCompatibleCap = >>::CircuitOutput, + TransciptParameters = TR::TransciptParameters, + >, + POW: RecursivePoWRunner, +>( + cs: &mut CS, + witness: RecursionLeafInstanceWitness, + round_function: &R, + config: LeafLayerRecursionConfig, + verifier_builder: Box>, + transcript_params: TR::TransciptParameters, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let RecursionLeafInstanceWitness { + input, + vk_witness, + queue_witness, + proof_witnesses, + } = witness; + + let input = RecursionLeafInput::allocate(cs, input); + let RecursionLeafInput { + params, + queue_state, + } = input; + let mut queue = RecursionQueue::::from_state(cs, queue_state); + + let RecursionLeafParameters { + circuit_type, + leaf_layer_vk_commitment: _, + basic_circuit_vk_commitment, + } = params; + + queue.witness = Arc::new(FullStateCircuitQueueWitness::from_inner_witness( + queue_witness, + )); + + queue.enforce_consistency(cs); + + // small trick to simplify setup. If we have nothing to verify, we do not care about VK + // being one that we want + let is_meaningful = queue.is_empty(cs).negated(cs); + + let vk = AllocatedVerificationKey::::allocate(cs, vk_witness); + assert_eq!( + vk.setup_merkle_tree_cap.len(), + config.vk_fixed_parameters.cap_size + ); + let vk_commitment_computed: [_; VK_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &vk, round_function); + + for (a, b) in basic_circuit_vk_commitment + .iter() + .zip(vk_commitment_computed.iter()) + { + Num::conditionally_enforce_equal(cs, is_meaningful, a, b); + } + + let mut proof_witnesses = proof_witnesses; + + let LeafLayerRecursionConfig { + proof_config, + vk_fixed_parameters, + capacity, + .. + } = config; + + // use this and deal with borrow checker + + let r = cs as *mut CS; + + assert_eq!(vk_fixed_parameters.parameters, verifier_builder.geometry()); + + let verifier = verifier_builder.create_recursive_verifier(cs); + + drop(cs); + + let cs = unsafe { &mut *r }; + + for _ in 0..capacity { + let proof_witness = proof_witnesses.pop_front(); + + let proof = AllocatedProof::allocate_from_witness( + cs, + proof_witness, + &verifier, + &vk_fixed_parameters, + &proof_config, + ); + + let queue_is_empty = queue.is_empty(cs); + let can_pop = queue_is_empty.negated(cs); + + let (recursive_request, _) = queue.pop_front(cs, can_pop); + + // ensure that it's an expected type + Num::conditionally_enforce_equal( + cs, + can_pop, + &recursive_request.circuit_type, + &circuit_type, + ); + + // verify the proof + let (is_valid, public_inputs) = verifier.verify::( + cs, + transcript_params.clone(), + &proof, + &vk_fixed_parameters, + &proof_config, + &vk, + ); + + assert_eq!(public_inputs.len(), INPUT_OUTPUT_COMMITMENT_LENGTH); + + // expected proof should be valid + is_valid.conditionally_enforce_true(cs, can_pop); + + // enforce publici inputs + + for (a, b) in recursive_request + .input_commitment + .iter() + .zip(public_inputs.iter()) + { + Num::conditionally_enforce_equal(cs, can_pop, a, b); + } + } + + queue.enforce_consistency(cs); + + let queue_is_empty = queue.is_empty(cs); + let boolean_true = Boolean::allocated_constant(cs, true); + Boolean::enforce_equal(cs, &queue_is_empty, &boolean_true); + + let input_commitment: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &input, round_function); + // for el in input_commitment.iter() { + // let gate = PublicInputGate::new(el.get_variable()); + // gate.add_to_cs(cs); + // } + + input_commitment +} diff --git a/crates/zkevm_circuits/src/recursion/mod.rs b/crates/zkevm_circuits/src/recursion/mod.rs new file mode 100644 index 00000000..7923239c --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/mod.rs @@ -0,0 +1,9 @@ +use super::*; + +pub mod compression; +pub mod interblock; +pub mod leaf_layer; +pub mod node_layer; + +pub const VK_COMMITMENT_LENGTH: usize = 4; +pub const NUM_BASE_LAYER_CIRCUITS: usize = 13; diff --git a/crates/zkevm_circuits/src/recursion/node_layer/input.rs b/crates/zkevm_circuits/src/recursion/node_layer/input.rs new file mode 100644 index 00000000..77db03b9 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/node_layer/input.rs @@ -0,0 +1,61 @@ +use super::*; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::verifier::VerificationKey; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; + +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::{ + boolean::Boolean, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use cs_derive::*; + +use crate::base_structures::vm_state::*; +use boojum::gadgets::num::Num; + +use crate::recursion::leaf_layer::input::RecursionLeafParameters; +use boojum::field::FieldExtension; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct RecursionNodeInput { + pub branch_circuit_type: Num, + pub leaf_layer_parameters: [RecursionLeafParameters; NUM_BASE_LAYER_CIRCUITS], + pub node_layer_vk_commitment: [Num; VK_COMMITMENT_LENGTH], + pub queue_state: QueueState, +} + +impl CSPlaceholder for RecursionNodeInput { + fn placeholder>(cs: &mut CS) -> Self { + let zero = Num::zero(cs); + let leaf_layer_param = RecursionLeafParameters::placeholder(cs); + Self { + branch_circuit_type: zero, + leaf_layer_parameters: [leaf_layer_param; NUM_BASE_LAYER_CIRCUITS], + node_layer_vk_commitment: [zero; VK_COMMITMENT_LENGTH], + queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default(bound = "RecursionNodeInputWitness: Default"))] +#[serde( + bound = ">::Witness: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct RecursionNodeInstanceWitness< + F: SmallField, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, +> { + pub input: RecursionNodeInputWitness, + pub vk_witness: VerificationKey, + pub split_points: VecDeque>, + pub proof_witnesses: VecDeque>, +} diff --git a/crates/zkevm_circuits/src/recursion/node_layer/mod.rs b/crates/zkevm_circuits/src/recursion/node_layer/mod.rs new file mode 100644 index 00000000..35f44548 --- /dev/null +++ b/crates/zkevm_circuits/src/recursion/node_layer/mod.rs @@ -0,0 +1,316 @@ +use crate::base_structures::recursion_query::RecursionQuery; +use crate::fsm_input_output::commit_variable_length_encodable_item; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::prover::ProofConfig; + +use crate::base_structures::recursion_query::RecursionQueue; +use boojum::gadgets::recursion::allocated_proof::AllocatedProof; +use boojum::gadgets::recursion::allocated_vk::AllocatedVerificationKey; +use boojum::gadgets::recursion::recursive_transcript::RecursiveTranscript; +use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; +use boojum::gadgets::traits::witnessable::WitnessHookable; + +use std::collections::VecDeque; + +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::{gates::*, traits::cs::ConstraintSystem}; +use boojum::field::SmallField; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{allocatable::CSAllocatable, allocatable::CSAllocatableExt, selectable::Selectable}, +}; + +use boojum::config::*; +use boojum::gadgets::u32::UInt32; + +use super::*; + +pub mod input; + +use self::input::*; + +use boojum::cs::implementations::verifier::VerificationKeyCircuitGeometry; +use boojum::cs::oracle::TreeHasher; +use boojum::field::FieldExtension; +use boojum::gadgets::recursion::circuit_pow::RecursivePoWRunner; +use boojum::gadgets::recursion::recursive_transcript::CircuitTranscript; +use boojum::gadgets::recursion::recursive_tree_hasher::CircuitTreeHasher; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug(bound = ""))] +#[serde(bound = "H::Output: serde::Serialize + serde::de::DeserializeOwned")] +pub struct NodeLayerRecursionConfig< + F: SmallField, + H: TreeHasher, + EXT: FieldExtension<2, BaseField = F>, +> { + pub proof_config: ProofConfig, + pub vk_fixed_parameters: VerificationKeyCircuitGeometry, + pub leaf_layer_capacity: usize, + pub node_layer_capacity: usize, + pub _marker: std::marker::PhantomData<(F, H, EXT)>, +} + +use boojum::cs::traits::circuit::*; + +// NOTE: does NOT allocate public inputs! we will deal with locations of public inputs being the same at the "outer" stage +pub fn node_layer_recursion_entry_point< + F: SmallField, + CS: ConstraintSystem + 'static, + R: CircuitRoundFunction + AlgebraicRoundFunction, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, + TR: RecursiveTranscript< + F, + CompatibleCap = >::Output, + CircuitReflection = CTR, + >, + CTR: CircuitTranscript< + F, + CircuitCompatibleCap = >>::CircuitOutput, + TransciptParameters = TR::TransciptParameters, + >, + POW: RecursivePoWRunner, +>( + cs: &mut CS, + witness: RecursionNodeInstanceWitness, + round_function: &R, + config: NodeLayerRecursionConfig, + verifier_builder: Box>, + transcript_params: TR::TransciptParameters, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let RecursionNodeInstanceWitness { + input, + vk_witness, + split_points, + proof_witnesses, + } = witness; + + let input = RecursionNodeInput::allocate(cs, input); + let RecursionNodeInput { + branch_circuit_type, + leaf_layer_parameters, + node_layer_vk_commitment, + queue_state, + } = input; + + assert_eq!(config.vk_fixed_parameters, vk_witness.fixed_parameters,); + + let vk = AllocatedVerificationKey::::allocate(cs, vk_witness); + assert_eq!( + vk.setup_merkle_tree_cap.len(), + config.vk_fixed_parameters.cap_size + ); + let vk_commitment_computed: [_; VK_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &vk, round_function); + + // select over which branch we work + use crate::recursion::leaf_layer::input::RecursionLeafParameters; + use boojum::gadgets::traits::allocatable::CSPlaceholder; + let mut leaf_params = RecursionLeafParameters::placeholder(cs); + + for el in leaf_layer_parameters.iter() { + let this_type = Num::equals(cs, &branch_circuit_type, &el.circuit_type); + leaf_params = + RecursionLeafParameters::conditionally_select(cs, this_type, el, &leaf_params); + } + + // now we need to try to split the circuit + + let NodeLayerRecursionConfig { + proof_config, + vk_fixed_parameters, + leaf_layer_capacity, + node_layer_capacity, + .. + } = config; + + let max_length_if_leafs = leaf_layer_capacity * node_layer_capacity; + let max_length_if_leafs = UInt32::allocated_constant(cs, max_length_if_leafs as u32); + // if queue length is <= max_length_if_leafs then next layer we aggregate leafs, or aggregate nodes otherwise + let (_, uf) = max_length_if_leafs.overflowing_sub(cs, queue_state.tail.length); + let next_layer_aggregates_nodes = uf; + let next_layer_aggregates_leafs = next_layer_aggregates_nodes.negated(cs); + + let mut vk_commitment = leaf_params.leaf_layer_vk_commitment; + + vk_commitment = <[Num; VK_COMMITMENT_LENGTH]>::conditionally_select( + cs, + next_layer_aggregates_nodes, + &node_layer_vk_commitment, + &vk_commitment, + ); + + // small trick to simplify setup. If we have nothing to verify, we do not care about VK + // being one that we want + let is_meaningful = RecursionQueue::::from_state(cs, queue_state) + .is_empty(cs) + .negated(cs); + + for (a, b) in vk_commitment.iter().zip(vk_commitment_computed.iter()) { + Num::conditionally_enforce_equal(cs, is_meaningful, a, b); + } + + // split the original queue into "node_layer_capacity" elements, regardless if next layer + // down will aggregate leafs or nodes + + let mut proof_witnesses = proof_witnesses; + + // use this and deal with borrow checker + + let r = cs as *mut CS; + + assert_eq!(vk_fixed_parameters.parameters, verifier_builder.geometry()); + + let verifier = verifier_builder.create_recursive_verifier(cs); + + drop(cs); + + let cs = unsafe { &mut *r }; + + let subqueues = split_queue_state_into_n(cs, queue_state, node_layer_capacity, split_points); + + let leaf_layer_capacity = UInt32::allocated_constant(cs, leaf_layer_capacity as u32); + for el in subqueues.iter() { + // if we aggregate leafs, then we ensure length to be small enough. + // It's not mandatory, but nevertheless + + // check len <= leaf capacity + + let (_, uf) = leaf_layer_capacity.overflowing_sub(cs, el.tail.length); + uf.conditionally_enforce_false(cs, next_layer_aggregates_leafs); + } + + assert_eq!(subqueues.len(), node_layer_capacity); + + for subqueue in subqueues.into_iter() { + let proof_witness = proof_witnesses.pop_front(); + + let proof = AllocatedProof::allocate_from_witness( + cs, + proof_witness, + &verifier, + &vk_fixed_parameters, + &proof_config, + ); + + let chunk_is_empty = subqueue.tail.length.is_zero(cs); + let chunk_is_meaningful = chunk_is_empty.negated(cs); + + // verify the proof + let (is_valid, public_inputs) = verifier.verify::( + cs, + transcript_params.clone(), + &proof, + &vk_fixed_parameters, + &proof_config, + &vk, + ); + + is_valid.conditionally_enforce_true(cs, chunk_is_meaningful); + + // if it's a meaningful proof we should also check that it indeed proofs a subqueue + + let next_layer_input_if_node = RecursionNodeInput { + branch_circuit_type: branch_circuit_type, + leaf_layer_parameters: leaf_layer_parameters, + node_layer_vk_commitment: node_layer_vk_commitment, + queue_state: subqueue, + }; + let input_commitment_if_node: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &next_layer_input_if_node, round_function); + + use crate::recursion::leaf_layer::input::RecursionLeafInput; + let next_layer_input_if_leaf = RecursionLeafInput { + params: leaf_params, + queue_state: subqueue, + }; + let input_commitment_if_leaf: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &next_layer_input_if_leaf, round_function); + + let input_commitment = <[Num; INPUT_OUTPUT_COMMITMENT_LENGTH]>::conditionally_select( + cs, + next_layer_aggregates_nodes, + &input_commitment_if_node, + &input_commitment_if_leaf, + ); + + assert_eq!(public_inputs.len(), INPUT_OUTPUT_COMMITMENT_LENGTH); + for (a, b) in input_commitment.iter().zip(public_inputs.into_iter()) { + Num::conditionally_enforce_equal(cs, chunk_is_meaningful, a, &b); + } + } + + let input_commitment: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &input, round_function); + // for el in input_commitment.iter() { + // let gate = PublicInputGate::new(el.get_variable()); + // gate.add_to_cs(cs); + // } + + input_commitment +} + +pub(crate) fn split_queue_state_into_n, const N: usize>( + cs: &mut CS, + queue_state: QueueState, + split_into: usize, + mut split_point_witnesses: VecDeque>, +) -> Vec> { + assert!(split_into <= u32::MAX as usize); + assert!(split_into >= 2); + if ::WitnessConfig::EVALUATE_WITNESS { + assert_eq!(split_point_witnesses.len() + 1, split_into); + } + + // our logic is that external caller provides splitting witness, and + // we just need to ensure that total length matches, and glue intermediate points. + + // We also ensure consistency of split points + + let mut total_len = UInt32::zero(cs); + + let mut current_head = queue_state.head; + let mut result = Vec::with_capacity(split_into); + + for _ in 0..(split_into - 1) { + let witness = split_point_witnesses + .pop_front() + .unwrap_or(QueueTailState::placeholder_witness()); + let current_tail = QueueTailState::allocate(cs, witness); + let first = QueueState { + head: current_head, + tail: current_tail, + }; + + current_head = current_tail.tail; + // add length + total_len = total_len.add_no_overflow(cs, current_tail.length); + // ensure consistency + first.enforce_consistency(cs); + + result.push(first); + } + // push the last one + let last_len = queue_state.tail.length.sub_no_overflow(cs, total_len); + let last = QueueState { + head: current_head, + tail: QueueTailState { + tail: queue_state.tail.tail, + length: last_len, + }, + }; + last.enforce_consistency(cs); + result.push(last); + + assert_eq!(result.len(), split_into); + + result +} diff --git a/crates/zkevm_circuits/src/scheduler/auxiliary.rs b/crates/zkevm_circuits/src/scheduler/auxiliary.rs new file mode 100644 index 00000000..08c66b29 --- /dev/null +++ b/crates/zkevm_circuits/src/scheduler/auxiliary.rs @@ -0,0 +1,302 @@ +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; + +use crate::fsm_input_output::commit_variable_length_encodable_item; + +use crate::base_structures::vm_state::*; +use crate::fsm_input_output::*; +use crate::linear_hasher::input::LinearHasherInputData; +use boojum::gadgets::u32::UInt32; + +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; +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::log_sorter::input::*; +use crate::storage_application::input::*; +use boojum::gadgets::u8::UInt8; + +use super::*; + +pub const NUM_CIRCUIT_TYPES_TO_SCHEDULE: usize = crate::recursion::NUM_BASE_LAYER_CIRCUITS; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum BaseLayerCircuitType { + None = 0, + VM = 1, + DecommitmentsFilter = 2, + Decommiter = 3, + LogDemultiplexer = 4, + KeccakPrecompile = 5, + Sha256Precompile = 6, + EcrecoverPrecompile = 7, + RamValidation = 8, + StorageFilter = 9, + StorageApplicator = 10, + EventsRevertsFilter = 11, + L1MessagesRevertsFilter = 12, + L1MessagesHasher = 13, +} + +impl BaseLayerCircuitType { + pub fn from_numeric_value(value: u8) -> Self { + match value { + a if a == Self::VM as u8 => Self::VM, + a if a == Self::DecommitmentsFilter as u8 => Self::DecommitmentsFilter, + a if a == Self::Decommiter as u8 => Self::Decommiter, + a if a == Self::LogDemultiplexer as u8 => Self::LogDemultiplexer, + a if a == Self::KeccakPrecompile as u8 => Self::KeccakPrecompile, + a if a == Self::Sha256Precompile as u8 => Self::Sha256Precompile, + a if a == Self::EcrecoverPrecompile as u8 => Self::EcrecoverPrecompile, + a if a == Self::RamValidation as u8 => Self::RamValidation, + a if a == Self::StorageFilter as u8 => Self::StorageFilter, + a if a == Self::StorageApplicator as u8 => Self::StorageApplicator, + a if a == Self::EventsRevertsFilter as u8 => Self::EventsRevertsFilter, + a if a == Self::L1MessagesRevertsFilter as u8 => Self::L1MessagesRevertsFilter, + a if a == Self::L1MessagesHasher as u8 => Self::L1MessagesHasher, + _ => { + panic!("unknown circuit type {}", value) + } + } + } +} + +#[track_caller] +pub(crate) fn compute_precompile_commitment< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + precompile_queue_state: &QueueState, + mem_queue_state_before: &QueueState, + mem_queue_state_after: &QueueState, + round_function: &R, +) -> ( + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], +) { + let input_data = PrecompileFunctionInputData { + initial_log_queue_state: precompile_queue_state.clone(), + initial_memory_queue_state: mem_queue_state_before.clone(), + }; + let input_data_commitment = + commit_variable_length_encodable_item(cs, &input_data, round_function); + + let output_data = PrecompileFunctionOutputData { + final_memory_state: mem_queue_state_after.clone(), + }; + let output_data_commitment = + commit_variable_length_encodable_item(cs, &output_data, round_function); + + (input_data_commitment, output_data_commitment) +} + +#[track_caller] +pub(crate) fn compute_storage_sorter_circuit_commitment< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + shard_id: UInt8, + queue_state_before: &QueueState, + intermediate_queue_state: &QueueTailState, + queue_state_after: &QueueState, + round_function: &R, +) -> ( + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], +) { + // We use here the naming events_deduplicator but the function is applicable for + // storage deduplicator is well - may be we should make this fact more observable + let mut full_state = QueueState::empty(cs); + full_state.tail = *intermediate_queue_state; + let input_data = StorageDeduplicatorInputData { + shard_id_to_process: shard_id, + unsorted_log_queue_state: queue_state_before.clone(), + intermediate_sorted_queue_state: full_state, + }; + let input_data_commitment = + commit_variable_length_encodable_item(cs, &input_data, round_function); + + let output_data = StorageDeduplicatorOutputData { + final_sorted_queue_state: queue_state_after.clone(), + }; + let output_data_commitment = + commit_variable_length_encodable_item(cs, &output_data, round_function); + + (input_data_commitment, output_data_commitment) +} + +#[track_caller] +pub(crate) fn compute_filter_circuit_commitment< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + queue_state_before: &QueueState, + intermediate_queue_state: &QueueTailState, + queue_state_after: &QueueState, + round_function: &R, +) -> ( + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], +) { + // We use here the naming events_deduplicator but the function is applicable for + // storage deduplicator is well - may be we should make this fact more observable + let mut full_state = QueueState::empty(cs); + full_state.tail = *intermediate_queue_state; + let input_data = EventsDeduplicatorInputData { + initial_log_queue_state: queue_state_before.clone(), + intermediate_sorted_queue_state: full_state, + }; + let input_data_commitment = + commit_variable_length_encodable_item(cs, &input_data, round_function); + + let output_data = EventsDeduplicatorOutputData { + final_queue_state: queue_state_after.clone(), + }; + let output_data_commitment = + commit_variable_length_encodable_item(cs, &output_data, round_function); + + (input_data_commitment, output_data_commitment) +} + +#[track_caller] +pub(crate) fn compute_storage_applicator_circuit_commitment< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + storage_queue_state: &QueueState, + initial_root: &[UInt8; 32], + initial_enumeration_counter: &[UInt32; 2], + final_root: &[UInt8; 32], + final_enumeration_counter: &[UInt32; 2], + rollup_state_diff_for_compression: &[UInt8; 32], + shard_id: u8, + round_function: &R, +) -> ( + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], +) { + let shard_id = UInt8::allocated_constant(cs, shard_id); + + let input_data = StorageApplicationInputData { + initial_next_enumeration_counter: *initial_enumeration_counter, + shard: shard_id, + initial_root_hash: *initial_root, + storage_application_log_state: storage_queue_state.clone(), + }; + let input_data_commitment = + commit_variable_length_encodable_item(cs, &input_data, round_function); + + let output_data = StorageApplicationOutputData { + new_root_hash: *final_root, + new_next_enumeration_counter: *final_enumeration_counter, + state_diffs_keccak256_hash: *rollup_state_diff_for_compression, + }; + let output_data_commitment = + commit_variable_length_encodable_item(cs, &output_data, round_function); + + (input_data_commitment, output_data_commitment) +} + +#[track_caller] +pub(crate) fn compute_hasher_circuit_commitment< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + input_queue_state: &QueueState, + pubdata_hash: &[UInt8; 32], + round_function: &R, +) -> ( + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], + [Num; CLOSED_FORM_COMMITTMENT_LENGTH], +) { + let input_data = LinearHasherInputData { + queue_state: input_queue_state.clone(), + }; + let input_data_commitment = + commit_variable_length_encodable_item(cs, &input_data, round_function); + + let output_data = LinearHasherOutputData { + keccak256_hash: *pubdata_hash, + }; + let output_data_commitment = + commit_variable_length_encodable_item(cs, &output_data, round_function); + + (input_data_commitment, output_data_commitment) +} + +#[track_caller] +pub(crate) fn conditionally_enforce_circuit_commitment>( + cs: &mut CS, + should_validate: Boolean, + actual_commitment: &[Num; INPUT_OUTPUT_COMMITMENT_LENGTH], + sample_commitment: &[Num; INPUT_OUTPUT_COMMITMENT_LENGTH], +) { + for (a, b) in actual_commitment.iter().zip(sample_commitment.iter()) { + Num::conditionally_enforce_equal(cs, should_validate, a, b); + } +} + +#[track_caller] +pub(crate) fn conditionally_select_queue_tail< + F: SmallField, + CS: ConstraintSystem, + const N: usize, +>( + cs: &mut CS, + flag: Boolean, + a: &QueueTailState, + b: &QueueTailState, +) -> QueueTailState { + let tail = Num::parallel_select(cs, flag, &a.tail, &b.tail); + let length = UInt32::conditionally_select(cs, flag, &a.length, &b.length); + + QueueTailState { tail, length } +} + +pub(crate) fn finalize_queue_state< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction, + const N: usize, + const M: usize, +>( + cs: &mut CS, + state: &QueueTailState, + _round_function: &R, +) -> [Num; M] { + let mut to_absorb = vec![]; + to_absorb.extend(state.tail); + let one_num = Num::allocated_constant(cs, F::ONE); + let zero_num = Num::zero(cs); + // we do rescue prime padding and absorb + to_absorb.push(one_num); + let mut multiple = to_absorb.len() / 8; + if to_absorb.len() % 8 != 0 { + multiple += 1; + } + to_absorb.resize(multiple * 8, zero_num); + let mut state = [zero_num; 12]; + for chunk in to_absorb.array_chunks::<8>() { + let els_to_keep = R::split_capacity_elements(&state.map(|el| el.get_variable())) + .map(|el| Num::from_variable(el)); + state = R::absorb_with_replacement_over_nums(cs, *chunk, els_to_keep); + state = R::compute_round_function_over_nums(cs, state); + } + + R::state_into_commitment::(&state.map(|el| el.get_variable())) + .map(|el| Num::from_variable(el)) +} diff --git a/crates/zkevm_circuits/src/scheduler/block_header/mod.rs b/crates/zkevm_circuits/src/scheduler/block_header/mod.rs new file mode 100644 index 00000000..c54d2a69 --- /dev/null +++ b/crates/zkevm_circuits/src/scheduler/block_header/mod.rs @@ -0,0 +1,178 @@ +use super::*; + +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; + +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::gadgets::{ + boolean::Boolean, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use cs_derive::*; + +use boojum::serde_utils::BigArraySerde; + +use boojum::gadgets::keccak256; + +pub const NUM_SHARDS: usize = 2; + +// Data that represents a pure state +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct PerShardState { + pub enumeration_counter: [UInt32; 2], + pub state_root: [UInt8; 32], +} + +// Data that is something like STF(BlockPassthroughData, BlockMetaParameters) -> (BlockPassthroughData, BlockAuxilaryOutput) +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct BlockPassthroughData { + pub per_shard_states: [PerShardState; NUM_SHARDS], +} + +// Defining some system parameters that are configurable +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct BlockMetaParameters { + pub zkporter_is_available: Boolean, + pub bootloader_code_hash: UInt256, + pub default_aa_code_hash: UInt256, +} + +// This is the information that represents artifacts only meaningful for this block, that will not be used for any +// next block +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct BlockAuxilaryOutput { + pub l1_messages_linear_hash: [UInt8; 32], + pub rollup_state_diff_for_compression: [UInt8; 32], + pub bootloader_heap_initial_content: [UInt8; 32], + pub events_queue_state: [UInt8; 32], +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct BlockHeader { + pub previous_block_content_hash: [UInt8; 32], + pub new_block_content_hash: [UInt8; 32], +} + +// only contains information about this block (or any one block in general), +// without anything about the previous one +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +pub struct BlockContentHeader { + pub block_data: BlockPassthroughData, + pub block_meta: BlockMetaParameters, + pub auxilary_output: BlockAuxilaryOutput, +} + +impl PerShardState { + pub fn into_flattened_bytes>(&self, cs: &mut CS) -> Vec> { + // everything is BE + let mut result = vec![]; + for el in self.enumeration_counter.iter().rev() { + let be_bytes = el.to_be_bytes(cs); + result.extend(be_bytes); + } + result.extend_from_slice(&self.state_root); + + result + } +} + +impl BlockPassthroughData { + pub fn into_flattened_bytes>(&self, cs: &mut CS) -> Vec> { + // everything is BE + let mut result = vec![]; + for el in self.per_shard_states.iter() { + let be_bytes = el.into_flattened_bytes(cs); + result.extend(be_bytes); + } + + result + } +} + +impl BlockMetaParameters { + pub fn into_flattened_bytes>(&self, cs: &mut CS) -> Vec> { + // everything is BE + let mut result = vec![]; + let zk_porter_byte = + unsafe { UInt8::from_variable_unchecked(self.zkporter_is_available.get_variable()) }; + result.push(zk_porter_byte); + + result.extend_from_slice(&self.bootloader_code_hash.to_be_bytes(cs)); + result.extend_from_slice(&self.default_aa_code_hash.to_be_bytes(cs)); + + result + } +} + +impl BlockAuxilaryOutput { + pub fn into_flattened_bytes>(&self, _cs: &mut CS) -> Vec> { + // everything is BE + let mut result = vec![]; + result.extend_from_slice(&self.l1_messages_linear_hash); + 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); + + result + } +} + +impl BlockContentHeader { + pub fn into_formal_block_hash>( + self, + cs: &mut CS, + ) -> ( + [UInt8; 32], + ([UInt8; 32], [UInt8; 32], [UInt8; 32]), + ) { + // everything is BE + let block_data = self.block_data.into_flattened_bytes(cs); + let block_meta = self.block_meta.into_flattened_bytes(cs); + let auxilary_output = self.auxilary_output.into_flattened_bytes(cs); + + let block_data_hash = keccak256::keccak256(cs, &block_data); + + let block_meta_hash = keccak256::keccak256(cs, &block_meta); + + let auxilary_output_hash = keccak256::keccak256(cs, &auxilary_output); + + let block_hash = Self::formal_block_hash_from_partial_hashes( + cs, + block_data_hash, + block_meta_hash, + auxilary_output_hash, + ); + + ( + block_hash, + (block_data_hash, block_meta_hash, auxilary_output_hash), + ) + } + + pub fn formal_block_hash_from_partial_hashes>( + cs: &mut CS, + block_data_hash: [UInt8; 32], + block_meta_hash: [UInt8; 32], + auxilary_output_hash: [UInt8; 32], + ) -> [UInt8; 32] { + let mut concatenated = vec![]; + concatenated.extend(block_data_hash); + concatenated.extend(block_meta_hash); + concatenated.extend(auxilary_output_hash); + + let block_header_hash = keccak256::keccak256(cs, &concatenated); + + block_header_hash + } +} diff --git a/crates/zkevm_circuits/src/scheduler/input.rs b/crates/zkevm_circuits/src/scheduler/input.rs new file mode 100644 index 00000000..1263b63a --- /dev/null +++ b/crates/zkevm_circuits/src/scheduler/input.rs @@ -0,0 +1,132 @@ +use super::*; +use boojum::cs::implementations::proof::Proof; +use boojum::cs::implementations::verifier::VerificationKey; + +use boojum::field::SmallField; + +use boojum::gadgets::{queue::*, traits::allocatable::*}; + +use crate::base_structures::precompile_input_outputs::PrecompileFunctionOutputDataWitness; + +use crate::base_structures::vm_state::*; +use crate::code_unpacker_sha256::input::CodeDecommitterOutputDataWitness; + +use crate::fsm_input_output::circuit_inputs::main_vm::VmOutputDataWitness; +use crate::linear_hasher::input::LinearHasherOutputDataWitness; +use crate::log_sorter::input::EventsDeduplicatorOutputDataWitness; + +use crate::fsm_input_output::ClosedFormInputCompactFormWitness; +use crate::storage_application::input::StorageApplicationOutputDataWitness; +use crate::storage_validity_by_grand_product::input::StorageDeduplicatorOutputDataWitness; +use boojum::gadgets::num::Num; +use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; +use std::collections::VecDeque; + +use crate::recursion::leaf_layer::input::*; +use crate::recursion::*; +use boojum::field::FieldExtension; + +// This structure only keeps witness, but there is a lot of in unfortunately +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug)] +#[serde( + bound = ">::Witness: serde::Serialize + serde::de::DeserializeOwned, + [RecursionLeafParametersWitness; NUM_BASE_LAYER_CIRCUITS]: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct SchedulerCircuitInstanceWitness< + F: SmallField, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, +> { + pub prev_block_data: BlockPassthroughDataWitness, + pub block_meta_parameters: BlockMetaParametersWitness, + + // passthrough outputs for all the circuits that produce such + pub vm_end_of_execution_observable_output: VmOutputDataWitness, + pub decommits_sorter_observable_output: CodeDecommittmentsDeduplicatorOutputDataWitness, + pub code_decommitter_observable_output: CodeDecommitterOutputDataWitness, + pub log_demuxer_observable_output: LogDemuxerOutputDataWitness, + pub keccak256_observable_output: PrecompileFunctionOutputDataWitness, + pub sha256_observable_output: PrecompileFunctionOutputDataWitness, + pub ecrecover_observable_output: PrecompileFunctionOutputDataWitness, + // RAM permutation doesn't produce anything + pub storage_sorter_observable_output: StorageDeduplicatorOutputDataWitness, + pub storage_application_observable_output: StorageApplicationOutputDataWitness, + pub events_sorter_observable_output: EventsDeduplicatorOutputDataWitness, + pub l1messages_sorter_observable_output: EventsDeduplicatorOutputDataWitness, + pub l1messages_linear_hasher_observable_output: LinearHasherOutputDataWitness, + + // very few things that we need to properly produce this block + pub storage_log_tail: [F; QUEUE_STATE_WIDTH], + pub per_circuit_closed_form_inputs: VecDeque>, + + pub bootloader_heap_memory_state: QueueTailStateWitness, + pub ram_sorted_queue_state: QueueTailStateWitness, + pub decommits_sorter_intermediate_queue_state: + QueueTailStateWitness, + + // all multi-circuits responsible for sorting + pub rollup_storage_sorter_intermediate_queue_state: QueueTailStateWitness, + pub events_sorter_intermediate_queue_state: QueueTailStateWitness, + pub l1messages_sorter_intermediate_queue_state: QueueTailStateWitness, + + // extra information about the previous block + pub previous_block_meta_hash: [u8; 32], + pub previous_block_aux_hash: [u8; 32], + + // proofs for every individual circuit type's aggregation subtree + #[derivative(Debug = "ignore")] + pub proof_witnesses: VecDeque>, + #[derivative(Debug = "ignore")] + pub node_layer_vk_witness: VerificationKey, + #[derivative(Debug = "ignore")] + pub leaf_layer_parameters: [RecursionLeafParametersWitness; NUM_BASE_LAYER_CIRCUITS], +} + +impl>, EXT: FieldExtension<2, BaseField = F>> + SchedulerCircuitInstanceWitness +{ + pub fn placeholder() -> Self { + Self { + prev_block_data: BlockPassthroughData::placeholder_witness(), + block_meta_parameters: BlockMetaParameters::placeholder_witness(), + + vm_end_of_execution_observable_output: VmOutputData::placeholder_witness(), + decommits_sorter_observable_output: + CodeDecommittmentsDeduplicatorOutputData::placeholder_witness(), + code_decommitter_observable_output: CodeDecommitterOutputData::placeholder_witness(), + log_demuxer_observable_output: LogDemuxerOutputData::placeholder_witness(), + keccak256_observable_output: PrecompileFunctionOutputData::placeholder_witness(), + sha256_observable_output: PrecompileFunctionOutputData::placeholder_witness(), + ecrecover_observable_output: PrecompileFunctionOutputData::placeholder_witness(), + storage_sorter_observable_output: StorageDeduplicatorOutputData::placeholder_witness(), + storage_application_observable_output: + StorageApplicationOutputData::placeholder_witness(), + events_sorter_observable_output: EventsDeduplicatorOutputData::placeholder_witness(), + l1messages_sorter_observable_output: EventsDeduplicatorOutputData::placeholder_witness( + ), + l1messages_linear_hasher_observable_output: LinearHasherOutputData::placeholder_witness( + ), + + storage_log_tail: [F::ZERO; QUEUE_STATE_WIDTH], + per_circuit_closed_form_inputs: VecDeque::new(), + + bootloader_heap_memory_state: QueueTailState::placeholder_witness(), + ram_sorted_queue_state: QueueTailState::placeholder_witness(), + decommits_sorter_intermediate_queue_state: QueueTailState::placeholder_witness(), + + rollup_storage_sorter_intermediate_queue_state: QueueTailState::placeholder_witness(), + events_sorter_intermediate_queue_state: QueueTailState::placeholder_witness(), + l1messages_sorter_intermediate_queue_state: QueueTailState::placeholder_witness(), + + previous_block_meta_hash: [0u8; 32], + previous_block_aux_hash: [0u8; 32], + + proof_witnesses: VecDeque::new(), + node_layer_vk_witness: VerificationKey::default(), + leaf_layer_parameters: std::array::from_fn(|_| { + RecursionLeafParameters::placeholder_witness() + }), + } + } +} diff --git a/crates/zkevm_circuits/src/scheduler/mod.rs b/crates/zkevm_circuits/src/scheduler/mod.rs new file mode 100644 index 00000000..817bf822 --- /dev/null +++ b/crates/zkevm_circuits/src/scheduler/mod.rs @@ -0,0 +1,1059 @@ +use super::*; + +pub mod block_header; +use self::block_header::*; + +pub mod input; +use self::input::*; + +pub mod auxiliary; +pub use auxiliary as aux; + +use boojum::cs::implementations::proof::Proof; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; + +use boojum::gadgets::recursion::allocated_proof::AllocatedProof; +use boojum::gadgets::recursion::allocated_vk::AllocatedVerificationKey; + +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{allocatable::*, selectable::Selectable}, +}; + +use crate::base_structures::decommit_query::DecommitQuery; +use crate::base_structures::decommit_query::DecommitQueue; +use crate::base_structures::memory_query::MemoryQuery; +use crate::base_structures::memory_query::MemoryQueue; + +use crate::base_structures::recursion_query::*; +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 boojum::gadgets::num::Num; +use boojum::gadgets::recursion::recursive_tree_hasher::RecursiveTreeHasher; + +use crate::base_structures::precompile_input_outputs::*; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::implementations::prover::ProofConfig; +use boojum::cs::implementations::verifier::VerificationKeyCircuitGeometry; +use boojum::cs::oracle::TreeHasher; +use boojum::cs::traits::circuit::*; +use boojum::field::FieldExtension; +use boojum::gadgets::keccak256; +use boojum::gadgets::recursion::circuit_pow::RecursivePoWRunner; +use boojum::gadgets::recursion::recursive_transcript::*; +use boojum::gadgets::recursion::recursive_tree_hasher::*; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use std::collections::HashMap; + +use crate::base_structures::vm_state::*; +use crate::code_unpacker_sha256::input::*; +use crate::demux_log_queue::input::*; +use crate::fsm_input_output::circuit_inputs::main_vm::*; +use crate::fsm_input_output::*; +use crate::log_sorter::input::*; +use crate::ram_permutation::input::*; +use crate::recursion::leaf_layer::input::*; +use crate::recursion::node_layer::input::*; +use crate::scheduler::auxiliary::*; +use crate::sort_decommittment_requests::input::*; +use crate::storage_application::input::*; +use crate::storage_validity_by_grand_product::input::*; + +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 SEQUENCE_OF_CIRCUIT_TYPES: [BaseLayerCircuitType; NUM_CIRCUIT_TYPES_TO_SCHEDULE] = [ + BaseLayerCircuitType::VM, + BaseLayerCircuitType::DecommitmentsFilter, + BaseLayerCircuitType::Decommiter, + BaseLayerCircuitType::LogDemultiplexer, + BaseLayerCircuitType::KeccakPrecompile, + BaseLayerCircuitType::Sha256Precompile, + BaseLayerCircuitType::EcrecoverPrecompile, + BaseLayerCircuitType::RamValidation, + BaseLayerCircuitType::StorageFilter, + BaseLayerCircuitType::StorageApplicator, + BaseLayerCircuitType::EventsRevertsFilter, + BaseLayerCircuitType::L1MessagesRevertsFilter, + BaseLayerCircuitType::L1MessagesHasher, +]; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug)] +#[serde(bound = "H::Output: serde::Serialize + serde::de::DeserializeOwned")] +pub struct SchedulerConfig, EXT: FieldExtension<2, BaseField = F>> { + pub proof_config: ProofConfig, + pub vk_fixed_parameters: VerificationKeyCircuitGeometry, + pub capacity: usize, + pub _marker: std::marker::PhantomData<(F, H, EXT)>, +} + +pub fn scheduler_function< + F: SmallField, + CS: ConstraintSystem + 'static, + R: CircuitRoundFunction + AlgebraicRoundFunction, + H: RecursiveTreeHasher>, + EXT: FieldExtension<2, BaseField = F>, + TR: RecursiveTranscript< + F, + CompatibleCap = >::Output, + CircuitReflection = CTR, + >, + CTR: CircuitTranscript< + F, + CircuitCompatibleCap = >>::CircuitOutput, + TransciptParameters = TR::TransciptParameters, + >, + POW: RecursivePoWRunner, +>( + cs: &mut CS, + mut witness: SchedulerCircuitInstanceWitness, + round_function: &R, + config: SchedulerConfig, + verifier_builder: Box>, + transcript_params: TR::TransciptParameters, +) where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let prev_block_data = BlockPassthroughData::allocate(cs, witness.prev_block_data.clone()); + let block_meta_parameters = + BlockMetaParameters::allocate(cs, witness.block_meta_parameters.clone()); + + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + + Boolean::enforce_equal( + cs, + &block_meta_parameters.zkporter_is_available, + &boolean_false, + ); + + // create initial queues + let bootloader_heap_memory_state = + QueueTailState::allocate(cs, witness.bootloader_heap_memory_state.clone()); + + let mut initial_memory_queue_state = MemoryQueue::::empty(cs); + initial_memory_queue_state.tail = bootloader_heap_memory_state.tail; + initial_memory_queue_state.length = bootloader_heap_memory_state.length; + + let mut decommittments_queue = DecommitQueue::::empty(cs); + let bootloader_code_hash = block_meta_parameters.bootloader_code_hash; + let bootloader_code_page = + UInt32::allocated_constant(cs, zkevm_opcode_defs::BOOTLOADER_CODE_PAGE); + let scheduler_timestamp = UInt32::allocated_constant(cs, SCHEDULER_TIMESTAMP); + let bootloader_decommittment_query = crate::base_structures::decommit_query::DecommitQuery { + code_hash: bootloader_code_hash, + page: bootloader_code_page, + is_first: boolean_true, + timestamp: scheduler_timestamp, + }; + + let _ = decommittments_queue.push(cs, bootloader_decommittment_query, boolean_true); + + // create all the intermediate output data in uncommitted form to later check for equality + + let vm_end_of_execution_observable_output = + VmOutputData::allocate(cs, witness.vm_end_of_execution_observable_output.clone()); + + let decommits_sorter_observable_output = CodeDecommittmentsDeduplicatorOutputData::allocate( + cs, + witness.decommits_sorter_observable_output.clone(), + ); + + let code_decommitter_observable_output = + CodeDecommitterOutputData::allocate(cs, witness.code_decommitter_observable_output.clone()); + + let log_demuxer_observable_output = + LogDemuxerOutputData::allocate(cs, witness.log_demuxer_observable_output.clone()); + + let keccak256_observable_output = + PrecompileFunctionOutputData::allocate(cs, witness.keccak256_observable_output.clone()); + + let sha256_observable_output = + PrecompileFunctionOutputData::allocate(cs, witness.sha256_observable_output.clone()); + + let ecrecover_observable_output = + PrecompileFunctionOutputData::allocate(cs, witness.ecrecover_observable_output.clone()); + + let storage_sorter_observable_output = StorageDeduplicatorOutputData::allocate( + cs, + witness.storage_sorter_observable_output.clone(), + ); + + let storage_application_observable_output = StorageApplicationOutputData::allocate( + cs, + witness.storage_application_observable_output.clone(), + ); + + let events_sorter_observable_output = + EventsDeduplicatorOutputData::allocate(cs, witness.events_sorter_observable_output.clone()); + + let l1messages_sorter_observable_output = EventsDeduplicatorOutputData::allocate( + cs, + witness.l1messages_sorter_observable_output.clone(), + ); + + let l1messages_linear_hasher_observable_output = + LinearHasherOutputData::allocate(cs, witness.l1messages_linear_hasher_observable_output); + + // auxilary intermediate states + let rollup_storage_sorter_intermediate_queue_state = QueueTailState::allocate( + cs, + witness + .rollup_storage_sorter_intermediate_queue_state + .clone(), + ); + + let events_sorter_intermediate_queue_state = + QueueTailState::allocate(cs, witness.events_sorter_intermediate_queue_state.clone()); + + let l1messages_sorter_intermediate_queue_state = QueueTailState::allocate( + cs, + witness.l1messages_sorter_intermediate_queue_state.clone(), + ); + + // final VM storage log state for our construction + let storage_log_tail = <[Num; QUEUE_STATE_WIDTH]>::allocate(cs, witness.storage_log_tail); + + // form the VM input + let default_aa_code_hash = block_meta_parameters.default_aa_code_hash; + + let global_context = GlobalContext { + zkporter_is_available: block_meta_parameters.zkporter_is_available, + default_aa_code_hash, + }; + + // we can form all the observable inputs already as those are just functions of observable outputs + + let vm_observable_input = VmInputData { + rollback_queue_tail_for_block: storage_log_tail, + memory_queue_initial_state: initial_memory_queue_state.into_state().tail, + decommitment_queue_initial_state: decommittments_queue.into_state().tail, + per_block_context: global_context, + }; + let vm_observable_input_commitment = + commit_variable_length_encodable_item(cs, &vm_observable_input, round_function); + let vm_observable_output_commitment = commit_variable_length_encodable_item( + cs, + &vm_end_of_execution_observable_output, + round_function, + ); + + let mut decommittments_sorted_queue_state = QueueState::empty(cs); + decommittments_sorted_queue_state.tail = QueueTailState::allocate( + cs, + witness.decommits_sorter_intermediate_queue_state.clone(), + ); + + let decommittments_sorter_circuit_input = CodeDecommittmentsDeduplicatorInputData { + initial_queue_state: vm_end_of_execution_observable_output.decommitment_queue_final_state, + sorted_queue_initial_state: decommittments_sorted_queue_state, + }; + let decommittments_sorter_circuit_input_commitment = commit_variable_length_encodable_item( + cs, + &decommittments_sorter_circuit_input, + round_function, + ); + let decommittments_sorter_observable_output_commitment = commit_variable_length_encodable_item( + cs, + &decommits_sorter_observable_output, + round_function, + ); + + // code decommiments: + let code_decommitter_circuit_input = CodeDecommitterInputData { + memory_queue_initial_state: vm_end_of_execution_observable_output.memory_queue_final_state, + sorted_requests_queue_initial_state: decommits_sorter_observable_output.final_queue_state, + }; + let code_decommitter_circuit_input_commitment = + commit_variable_length_encodable_item(cs, &code_decommitter_circuit_input, round_function); + let code_decommitter_observable_output_commitment = commit_variable_length_encodable_item( + cs, + &code_decommitter_observable_output, + round_function, + ); + + // log demultiplexer + let log_demux_circuit_input = LogDemuxerInputData { + initial_log_queue_state: vm_end_of_execution_observable_output.log_queue_final_state, + }; + let log_demux_circuit_input_commitment = + commit_variable_length_encodable_item(cs, &log_demux_circuit_input, round_function); + let log_demuxer_observable_output_commitment = + commit_variable_length_encodable_item(cs, &log_demuxer_observable_output, round_function); + + // all intermediate queues for sorters + + // precompiles: keccak, sha256 and ecrecover + let (keccak_circuit_observable_input_commitment, keccak_circuit_observable_output_commitment) = + compute_precompile_commitment( + cs, + &log_demuxer_observable_output.keccak256_access_queue_state, + &code_decommitter_observable_output.memory_queue_final_state, + &keccak256_observable_output.final_memory_state, + round_function, + ); + let (sha256_circuit_observable_input_commitment, sha256_circuit_observable_output_commitment) = + compute_precompile_commitment( + cs, + &log_demuxer_observable_output.sha256_access_queue_state, + &keccak256_observable_output.final_memory_state, + &sha256_observable_output.final_memory_state, + round_function, + ); + let ( + ecrecover_circuit_observable_input_commitment, + ecrecover_circuit_observable_output_commitment, + ) = compute_precompile_commitment( + cs, + &log_demuxer_observable_output.ecrecover_access_queue_state, + &sha256_observable_output.final_memory_state, + &ecrecover_observable_output.final_memory_state, + round_function, + ); + + // ram permutation and validation + // NBL this circuit is terminal - it has no actual output + + let mut ram_sorted_queue_state = QueueState::empty(cs); + ram_sorted_queue_state.tail = + QueueTailState::allocate(cs, witness.ram_sorted_queue_state.clone()); + + let ram_validation_circuit_input = RamPermutationInputData { + unsorted_queue_initial_state: ecrecover_observable_output.final_memory_state, + sorted_queue_initial_state: ram_sorted_queue_state, + non_deterministic_bootloader_memory_snapshot_length: bootloader_heap_memory_state.length, + }; + let ram_validation_circuit_input_commitment = + commit_variable_length_encodable_item(cs, &ram_validation_circuit_input, round_function); + + // events reverts filter and merkelization + let (events_filter_input_com, events_filter_output_com) = compute_filter_circuit_commitment( + cs, + &log_demuxer_observable_output.events_access_queue_state, + &events_sorter_intermediate_queue_state, + &events_sorter_observable_output.final_queue_state, + round_function, + ); + + // let (events_merkelizer_input_com, events_merkelizer_output_com) = compute_merkelization_circuit_commitment( + // cs, + // &filtered_events_queue_state, + // &events_linear_hash_as_bytes32, + // &events_root_as_bytes32, + // round_function + // ); + + // l1 messages reverts filter and merkelization + let (l1_messages_filter_input_com, l1_messages_filter_output_com) = + compute_filter_circuit_commitment( + cs, + &log_demuxer_observable_output.l1messages_access_queue_state, + &l1messages_sorter_intermediate_queue_state, + &l1messages_sorter_observable_output.final_queue_state, + round_function, + ); + + let (l1_messages_hasher_input_com, l1_messages_hasher_output_com) = + compute_hasher_circuit_commitment( + cs, + &l1messages_sorter_observable_output.final_queue_state, + &l1messages_linear_hasher_observable_output.keccak256_hash, + round_function, + ); + + const NUM_PROCESSABLE_SHARDS: usize = 1; + + let zero_num = Num::zero(cs); + let empty_input_output_commitment = [zero_num; CLOSED_FORM_COMMITTMENT_LENGTH]; + + let mut storage_filter_input_commitments = + [empty_input_output_commitment; NUM_PROCESSABLE_SHARDS]; + let mut storage_filter_output_commitments = + [empty_input_output_commitment; NUM_PROCESSABLE_SHARDS]; + let mut storage_applicator_input_commitments = + [empty_input_output_commitment; NUM_PROCESSABLE_SHARDS]; + let mut storage_applicator_output_commitments = + [empty_input_output_commitment; NUM_PROCESSABLE_SHARDS]; + + let storage_queues_state = [log_demuxer_observable_output.storage_access_queue_state]; + + let filtered_storage_queues_state = [storage_sorter_observable_output.final_sorted_queue_state]; + + let initial_enumeration_counters = [prev_block_data.per_shard_states[0].enumeration_counter]; + + let initial_state_roots = [prev_block_data.per_shard_states[0].state_root]; + + let final_enumeration_counters = + [storage_application_observable_output.new_next_enumeration_counter]; + + let final_state_roots = [storage_application_observable_output.new_root_hash]; + + let storage_intermediate_sorted_queue_state = [rollup_storage_sorter_intermediate_queue_state]; + + let storage_diffs_for_compression = + [storage_application_observable_output.state_diffs_keccak256_hash]; + + assert_eq!(NUM_PROCESSABLE_SHARDS, 1); // no support of porter as of yet + + for shard_id in 0..NUM_PROCESSABLE_SHARDS { + assert!(shard_id <= u8::MAX as usize); + + let shard_id_uint8 = UInt8::allocated_constant(cs, shard_id as u8); + // storage acesses filter + let (storage_filter_input_com, storage_filter_output_com) = + compute_storage_sorter_circuit_commitment( + cs, + shard_id_uint8, + &storage_queues_state[shard_id], + &storage_intermediate_sorted_queue_state[shard_id], + &filtered_storage_queues_state[shard_id], + round_function, + ); + storage_filter_input_commitments[shard_id] = storage_filter_input_com; + storage_filter_output_commitments[shard_id] = storage_filter_output_com; + + // storage applicator for rollup subtree (porter subtree is shut down globally currently) + let (storage_applicator_input_com, storage_applicator_output_com) = + compute_storage_applicator_circuit_commitment( + cs, + &filtered_storage_queues_state[shard_id], + &initial_state_roots[shard_id], + &initial_enumeration_counters[shard_id], + &final_state_roots[shard_id], + &final_enumeration_counters[shard_id], + &storage_diffs_for_compression[shard_id], + shard_id as u8, + round_function, + ); + storage_applicator_input_commitments[shard_id] = storage_applicator_input_com; + storage_applicator_output_commitments[shard_id] = storage_applicator_output_com; + } + + // now we can run all the cirucits in sequence + + // now let's map it for convenience, and later on walk over it + + let input_commitments_as_map = + HashMap::; CLOSED_FORM_COMMITTMENT_LENGTH]>::from_iter( + [ + (BaseLayerCircuitType::VM, vm_observable_input_commitment), + ( + BaseLayerCircuitType::DecommitmentsFilter, + decommittments_sorter_circuit_input_commitment, + ), + ( + BaseLayerCircuitType::Decommiter, + code_decommitter_circuit_input_commitment, + ), + ( + BaseLayerCircuitType::LogDemultiplexer, + log_demux_circuit_input_commitment, + ), + ( + BaseLayerCircuitType::KeccakPrecompile, + keccak_circuit_observable_input_commitment, + ), + ( + BaseLayerCircuitType::Sha256Precompile, + sha256_circuit_observable_input_commitment, + ), + ( + BaseLayerCircuitType::EcrecoverPrecompile, + ecrecover_circuit_observable_input_commitment, + ), + ( + BaseLayerCircuitType::RamValidation, + ram_validation_circuit_input_commitment, + ), + ( + BaseLayerCircuitType::EventsRevertsFilter, + events_filter_input_com, + ), + ( + BaseLayerCircuitType::L1MessagesRevertsFilter, + l1_messages_filter_input_com, + ), + ( + BaseLayerCircuitType::StorageFilter, + storage_filter_input_commitments[0], + ), + ( + BaseLayerCircuitType::StorageApplicator, + storage_applicator_input_commitments[0], + ), + ( + BaseLayerCircuitType::L1MessagesHasher, + l1_messages_hasher_input_com, + ), + ] + .into_iter(), + ); + + let output_commitments_as_map = + HashMap::; CLOSED_FORM_COMMITTMENT_LENGTH]>::from_iter( + [ + (BaseLayerCircuitType::VM, vm_observable_output_commitment), + ( + BaseLayerCircuitType::DecommitmentsFilter, + decommittments_sorter_observable_output_commitment, + ), + ( + BaseLayerCircuitType::Decommiter, + code_decommitter_observable_output_commitment, + ), + ( + BaseLayerCircuitType::LogDemultiplexer, + log_demuxer_observable_output_commitment, + ), + ( + BaseLayerCircuitType::KeccakPrecompile, + keccak_circuit_observable_output_commitment, + ), + ( + BaseLayerCircuitType::Sha256Precompile, + sha256_circuit_observable_output_commitment, + ), + ( + BaseLayerCircuitType::EcrecoverPrecompile, + ecrecover_circuit_observable_output_commitment, + ), + ( + BaseLayerCircuitType::RamValidation, + [zero_num; CLOSED_FORM_COMMITTMENT_LENGTH], // formally set here + ), + ( + BaseLayerCircuitType::EventsRevertsFilter, + events_filter_output_com, + ), + ( + BaseLayerCircuitType::L1MessagesRevertsFilter, + l1_messages_filter_output_com, + ), + ( + BaseLayerCircuitType::StorageFilter, + storage_filter_output_commitments[0], + ), + ( + BaseLayerCircuitType::StorageApplicator, + storage_applicator_output_commitments[0], + ), + ( + BaseLayerCircuitType::L1MessagesHasher, + l1_messages_hasher_output_com, + ), + ] + .into_iter(), + ); + + // self-check + for pair in SEQUENCE_OF_CIRCUIT_TYPES.windows(2) { + assert_eq!((pair[0] as u8) + 1, pair[1] as u8); + } + + // 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 + .initial_queue_state + .tail + .length + .is_zero(cs), + ); + skip_flags[(BaseLayerCircuitType::Decommiter as u8 as usize) - 1] = Some( + 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 + .initial_log_queue_state + .tail + .length + .is_zero(cs), + ); + skip_flags[(BaseLayerCircuitType::KeccakPrecompile as u8 as usize) - 1] = Some( + 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 + .sha256_access_queue_state + .tail + .length + .is_zero(cs), + ); + skip_flags[(BaseLayerCircuitType::EcrecoverPrecompile as u8 as usize) - 1] = Some( + log_demuxer_observable_output + .ecrecover_access_queue_state + .tail + .length + .is_zero(cs), + ); + skip_flags[(BaseLayerCircuitType::RamValidation as u8 as usize) - 1] = Some( + ram_validation_circuit_input + .unsorted_queue_initial_state + .tail + .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 + .events_access_queue_state + .tail + .length + .is_zero(cs), + ); + skip_flags[(BaseLayerCircuitType::L1MessagesRevertsFilter as u8 as usize) - 1] = Some( + log_demuxer_observable_output + .l1messages_access_queue_state + .tail + .length + .is_zero(cs), + ); + + // for (idx, el) in skip_flags.iter().enumerate() { + // if let Some(el) = el { + // let circuit_type = BaseLayerCircuitType::from_numeric_value((idx+1) as u8); + // println!("Skip for {:?} = {:?}", circuit_type, el.witness_hook(cs)()); + // } + // } + + // In practice we do NOT skip it + // skip_flags[(BaseLayerCircuitType::L1MessagesHasher as u8 as usize) - 1] = Some( + // l1messages_sorter_observable_output.final_queue_state.tail.length.is_zero(cs) + // ); + + // now we just walk one by one + + let mut execution_stage_bitmask = [boolean_false; NUM_CIRCUIT_TYPES_TO_SCHEDULE]; + execution_stage_bitmask[0] = boolean_true; // VM + + assert_eq!( + SEQUENCE_OF_CIRCUIT_TYPES.len(), + execution_stage_bitmask.len() + ); + + let mut execution_flag = boolean_true; + let mut previous_completion_flag = boolean_true; + + let empty_recursive_queue_state_tail = QueueTailState::empty(cs); + let mut recursive_queue_state_tails = + [empty_recursive_queue_state_tail; NUM_CIRCUIT_TYPES_TO_SCHEDULE]; + + let mut hidden_fsm_input_to_use = [zero_num; CLOSED_FORM_COMMITTMENT_LENGTH]; + + for _idx in 0..config.capacity { + let mut next_mask = [boolean_false; NUM_CIRCUIT_TYPES_TO_SCHEDULE]; + + let closed_form_input_witness = witness + .per_circuit_closed_form_inputs + .pop_front() + .unwrap_or(ClosedFormInputCompactForm::placeholder_witness()); + let closed_form_input = ClosedFormInputCompactForm::allocate(cs, closed_form_input_witness); + + // we believe that prover gives us valid compact forms, + // so we check equality + let start_of_next_when_previous_is_finished = + Boolean::equals(cs, &closed_form_input.start_flag, &previous_completion_flag); + start_of_next_when_previous_is_finished.conditionally_enforce_true(cs, execution_flag); + + let mut computed_applicability_flags = [boolean_false; NUM_CIRCUIT_TYPES_TO_SCHEDULE]; + let mut circuit_type_to_use = Num::zero(cs); + + for (idx, ((circuit_type, stage_flag), skip_flag)) in SEQUENCE_OF_CIRCUIT_TYPES + .iter() + .zip(execution_stage_bitmask.iter()) + .zip(skip_flags.iter()) + .enumerate() + { + let sample_circuit_commitment = input_commitments_as_map + .get(circuit_type) + .cloned() + .expect(&format!( + "circuit input commitment for type {:?}", + circuit_type + )); + // .unwrap_or([zero_num; CLOSED_FORM_COMMITTMENT_LENGTH]); + + let validate = if let Some(skip_flag) = skip_flag { + let not_skip = skip_flag.negated(cs); // this is memoized + Boolean::multi_and(cs, &[*stage_flag, execution_flag, not_skip]) + } else { + Boolean::multi_and(cs, &[*stage_flag, execution_flag]) + }; + + let validate_observable_input = validate; // input commitment is ALWAYS the same for all the circuits of some type + conditionally_enforce_circuit_commitment( + cs, + validate_observable_input, + &closed_form_input.observable_input_committment, + &sample_circuit_commitment, + ); + + let validate_observable_output = if let Some(skip_flag) = skip_flag { + let not_skip = skip_flag.negated(cs); // this is memoized + Boolean::multi_and( + cs, + &[closed_form_input.completion_flag, not_skip, *stage_flag], + ) + } else { + Boolean::multi_and(cs, &[closed_form_input.completion_flag, *stage_flag]) + }; + + let sample_circuit_commitment = output_commitments_as_map + .get(circuit_type) + .cloned() + .expect(&format!( + "circuit output commitment for type {:?}", + circuit_type + )); + // .unwrap_or([zero_num; CLOSED_FORM_COMMITTMENT_LENGTH]); + + conditionally_enforce_circuit_commitment( + cs, + validate_observable_output, + &closed_form_input.observable_output_committment, + &sample_circuit_commitment, + ); + + let should_start_next = if let Some(skip_flag) = skip_flag { + Boolean::multi_or(cs, &[closed_form_input.completion_flag, *skip_flag]) + } else { + closed_form_input.completion_flag + }; + + let stage_just_finished = + Boolean::multi_and(cs, &[should_start_next, execution_flag, *stage_flag]); + next_mask[idx] = stage_just_finished; + + let circuit_type = UInt8::allocated_constant(cs, *circuit_type as u8).into_num(); + + circuit_type_to_use = + Num::conditionally_select(cs, validate, &circuit_type, &circuit_type_to_use); + + computed_applicability_flags[idx] = validate; + } + + // now we can use a proper circuit type and manyally add it into single queue + let mut tail_to_use = QueueTailState::empty(cs); + for (_idx, (flag, state)) in computed_applicability_flags + .iter() + .zip(recursive_queue_state_tails.iter()) + .enumerate() + { + tail_to_use = conditionally_select_queue_tail(cs, *flag, &state, &tail_to_use); + } + + let push_to_any = Boolean::multi_or(cs, &computed_applicability_flags); + + // for any circuit that is NOT start, but is added to recursion queue we validate that previous hidden FSM output + // is given to this circuit as hidden FSM input + + // NOTE: we use `start_flag` from witness because we validated it's logic in the lines around + // `start_of_next_when_previous_is_finished` above, so it correctly represents continuation + + let continue_same_type = closed_form_input.start_flag.negated(cs); + let validate_hidden_input = Boolean::multi_and(cs, &[push_to_any, continue_same_type]); + conditionally_enforce_circuit_commitment( + cs, + validate_hidden_input, + &closed_form_input.hidden_fsm_input_committment, + &hidden_fsm_input_to_use, + ); + + // and here we can just update it for the next step + hidden_fsm_input_to_use = closed_form_input.hidden_fsm_output_committment; + + let closed_form_input_comm = + commit_variable_length_encodable_item(cs, &closed_form_input, round_function); + let query = RecursionQuery { + circuit_type: circuit_type_to_use, + input_commitment: closed_form_input_comm, + }; + // push + let mut tmp_queue = RecursionQueue::::empty(cs); + tmp_queue.tail = tail_to_use.tail; + tmp_queue.length = tail_to_use.length; + + let _ = tmp_queue.push(cs, query, push_to_any); + let tail_to_use_for_update = tmp_queue.into_state().tail; + + for (_idx, (flag, state)) in computed_applicability_flags + .iter() + .zip(recursive_queue_state_tails.iter_mut()) + .enumerate() + { + // if flag.witness_hook(cs)().unwrap_or(false) { + // let circuit_type = BaseLayerCircuitType::from_numeric_value((_idx+1) as u8); + // println!( + // "Pushing for circuit type {:?}, old state = {:?}, new state = {:?}", + // circuit_type, + // state.witness_hook(cs)(), + // tail_to_use_for_update.witness_hook(cs)(), + // ); + // } + *state = conditionally_select_queue_tail(cs, *flag, &tail_to_use_for_update, &*state); + } + + previous_completion_flag = Boolean::multi_or(cs, &next_mask); + // for the next stage we do shifted AND + let mut tmp = [boolean_false; NUM_CIRCUIT_TYPES_TO_SCHEDULE]; + // note skip(1) + for (idx, start_next) in next_mask.iter().enumerate() { + let finished_this_stage = *start_next; + let not_finished = finished_this_stage.negated(cs); + let proceed_current = + Boolean::multi_and(cs, &[execution_stage_bitmask[idx], not_finished]); + // update + let start_as_next = tmp[idx]; + let do_this_stage = Boolean::multi_or(cs, &[start_as_next, proceed_current]); + execution_stage_bitmask[idx] = do_this_stage; + if idx + 1 < NUM_CIRCUIT_TYPES_TO_SCHEDULE { + tmp[idx + 1] = finished_this_stage; + } + } + + // and check if we are done + let just_finished = *next_mask.last().unwrap(); + let should_continue = just_finished.negated(cs); + + execution_flag = Boolean::multi_and(cs, &[execution_flag, should_continue]); + } + + // so we are done! + Boolean::enforce_equal(cs, &execution_flag, &boolean_false); + + // actually perform verification + let leaf_layer_parameters = witness + .leaf_layer_parameters + .clone() + .map(|el| RecursionLeafParameters::allocate(cs, el)); + + let leaf_layer_parameters_commitment: [_; LEAF_LAYER_PARAMETERS_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &leaf_layer_parameters, round_function); + + let node_layer_vk = + AllocatedVerificationKey::::allocate(cs, witness.node_layer_vk_witness.clone()); + let node_layer_vk_commitment: [_; VK_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &node_layer_vk, round_function); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(leaf_layer_parameters_commitment.witness_hook(cs)()); + dbg!(node_layer_vk_commitment.witness_hook(cs)()); + } + + let mut proof_witnesses = witness.proof_witnesses; + + // create verifier + let r = cs as *mut CS; + + assert_eq!( + config.vk_fixed_parameters.parameters, + verifier_builder.geometry() + ); + + let verifier = verifier_builder.create_recursive_verifier(cs); + + drop(cs); + + let cs = unsafe { &mut *r }; + + for (_idx, (circuit_type, state)) in SEQUENCE_OF_CIRCUIT_TYPES + .iter() + .zip(recursive_queue_state_tails.into_iter()) + .enumerate() + { + println!("Verifying circuit type {:?}", circuit_type); + + let should_skip = state.length.is_zero(cs); + let should_verify = should_skip.negated(cs); + + let circuit_type = UInt8::allocated_constant(cs, *circuit_type as u8).into_num(); + + let mut queue_state = QueueState::empty(cs); + queue_state.tail = state; + + let input: RecursionNodeInput = RecursionNodeInput { + branch_circuit_type: circuit_type, + leaf_layer_parameters: leaf_layer_parameters, + node_layer_vk_commitment: node_layer_vk_commitment, + queue_state: queue_state, + }; + + let expected_input_commitment: [_; INPUT_OUTPUT_COMMITMENT_LENGTH] = + commit_variable_length_encodable_item(cs, &input, round_function); + + let proof_witness = proof_witnesses.pop_front(); + + let proof = AllocatedProof::allocate_from_witness( + cs, + proof_witness, + &verifier, + &config.vk_fixed_parameters, + &config.proof_config, + ); + + let (is_valid, inputs) = verifier.verify::( + cs, + transcript_params.clone(), + &proof, + &config.vk_fixed_parameters, + &config.proof_config, + &node_layer_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); + } + } + + // now we can collapse queues + let bootloader_heap_snapshot: [_; QUEUE_FINAL_STATE_COMMITMENT_LENGTH] = + finalize_queue_state(cs, &bootloader_heap_memory_state, round_function); + + let events_snapshot: [_; QUEUE_FINAL_STATE_COMMITMENT_LENGTH] = finalize_queue_state( + cs, + &events_sorter_observable_output.final_queue_state.tail, + round_function, + ); + + // Form a public block header + let mut this_block_data = prev_block_data.clone(); + + for ((dst, counter), root) in this_block_data + .per_shard_states + .iter_mut() + .zip(final_enumeration_counters.iter()) + .zip(final_state_roots.iter()) + { + dst.enumeration_counter = *counter; + dst.state_root = *root; + } + + let zero_u8 = UInt8::zero(cs); + + let mut bootloader_heap_initial_content = [zero_u8; 32]; + for (dst, src) in bootloader_heap_initial_content + .array_chunks_mut::<8>() + .zip(bootloader_heap_snapshot.iter()) + { + let le_bytes = src.constraint_bit_length_as_bytes(cs, 64); + dst.copy_from_slice(&le_bytes[..]); + dst.reverse(); + } + + let mut events_queue_state = [zero_u8; 32]; + for (dst, src) in events_queue_state + .array_chunks_mut::<8>() + .zip(events_snapshot.iter()) + { + let le_bytes = src.constraint_bit_length_as_bytes(cs, 64); + dst.copy_from_slice(&le_bytes[..]); + dst.reverse(); + } + + 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, + }; + + let block_content_header = BlockContentHeader { + block_data: this_block_data, + block_meta: block_meta_parameters, + auxilary_output: aux_data, + }; + + let (this_block_content_hash, _) = block_content_header.clone().into_formal_block_hash(cs); + + // we are done with this block, process the previous one + let previous_block_passthrough_data = prev_block_data.into_flattened_bytes(cs); + let previous_block_passthrough_hash = + keccak256::keccak256(cs, &previous_block_passthrough_data); + + let previous_block_meta_hash = <[UInt8; 32]>::allocate(cs, witness.previous_block_meta_hash); + let previous_block_aux_hash = <[UInt8; 32]>::allocate(cs, witness.previous_block_aux_hash); + + let previous_block_content_hash = BlockContentHeader::formal_block_hash_from_partial_hashes( + cs, + previous_block_passthrough_hash, + previous_block_meta_hash, + previous_block_aux_hash, + ); + + // form full block hash + + let mut flattened_public_input = vec![]; + flattened_public_input.extend(previous_block_content_hash); + flattened_public_input.extend(this_block_content_hash); + // recursion parameters + + let mut recursion_node_verification_key_hash = [zero_u8; 32]; + for (dst, src) in recursion_node_verification_key_hash + .array_chunks_mut::<8>() + .zip(node_layer_vk_commitment.iter()) + { + let le_bytes = src.constraint_bit_length_as_bytes(cs, 64); + dst.copy_from_slice(&le_bytes[..]); + dst.reverse(); + } + + let mut leaf_layer_parameters_hash = [zero_u8; 32]; + for (dst, src) in leaf_layer_parameters_hash + .array_chunks_mut::<8>() + .zip(leaf_layer_parameters_commitment.iter()) + { + let le_bytes = src.constraint_bit_length_as_bytes(cs, 64); + dst.copy_from_slice(&le_bytes[..]); + dst.reverse(); + } + + flattened_public_input.extend(recursion_node_verification_key_hash); + flattened_public_input.extend(leaf_layer_parameters_hash); + + let input_keccak_hash = keccak256::keccak256(cs, &flattened_public_input); + let take_by = F::CAPACITY_BITS / 8; + + for chunk in input_keccak_hash + .chunks_exact(take_by) + .take(NUM_SCHEDULER_PUBLIC_INPUTS) + { + let mut lc = Vec::with_capacity(chunk.len()); + // treat as BE + for (idx, el) in chunk.iter().rev().enumerate() { + lc.push((el.get_variable(), F::SHIFTS[idx * 8])); + } + let as_num = Num::linear_combination(cs, &lc); + use boojum::cs::gates::PublicInputGate; + let gate = PublicInputGate::new(as_num.get_variable()); + gate.add_to_cs(cs); + } +} diff --git a/crates/zkevm_circuits/src/sha256_round_function/input.rs b/crates/zkevm_circuits/src/sha256_round_function/input.rs new file mode 100644 index 00000000..b6f81c5a --- /dev/null +++ b/crates/zkevm_circuits/src/sha256_round_function/input.rs @@ -0,0 +1,89 @@ +use std::collections::VecDeque; + +use super::*; + +use crate::base_structures::precompile_input_outputs::*; +use crate::base_structures::vm_state::*; +use boojum::cs::Variable; +use boojum::gadgets::queue::*; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::SmallField; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::serde_utils::BigArraySerde; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct Sha256RoundFunctionFSM { + pub read_precompile_call: Boolean, + pub read_words_for_round: Boolean, + pub completed: Boolean, + pub sha256_inner_state: [UInt32; 8], + pub timestamp_to_use_for_read: UInt32, + pub timestamp_to_use_for_write: UInt32, + pub precompile_call_params: Sha256PrecompileCallParams, +} + +impl CSPlaceholder for Sha256RoundFunctionFSM { + fn placeholder>(cs: &mut CS) -> Self { + let boolean_false = Boolean::allocated_constant(cs, false); + let zero_u32 = UInt32::zero(cs); + Self { + read_precompile_call: boolean_false, + read_words_for_round: boolean_false, + completed: boolean_false, + sha256_inner_state: boojum::gadgets::sha256::ivs_as_uint32(cs), + timestamp_to_use_for_read: zero_u32, + timestamp_to_use_for_write: zero_u32, + precompile_call_params: Sha256PrecompileCallParams::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct Sha256RoundFunctionFSMInputOutput { + pub internal_fsm: Sha256RoundFunctionFSM, + pub log_queue_state: QueueState, + pub memory_queue_state: QueueState, +} + +impl CSPlaceholder for Sha256RoundFunctionFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + internal_fsm: Sha256RoundFunctionFSM::placeholder(cs), + log_queue_state: QueueState::::placeholder(cs), + memory_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type Sha256RoundFunctionCircuitInputOutput = ClosedFormInput< + F, + Sha256RoundFunctionFSMInputOutput, + PrecompileFunctionInputData, + PrecompileFunctionOutputData, +>; +pub type Sha256RoundFunctionCircuitInputOutputWitness = ClosedFormInputWitness< + F, + Sha256RoundFunctionFSMInputOutput, + PrecompileFunctionInputData, + PrecompileFunctionOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct Sha256RoundFunctionCircuitInstanceWitness { + pub closed_form_input: Sha256RoundFunctionCircuitInputOutputWitness, + pub requests_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, + pub memory_reads_witness: VecDeque, +} diff --git a/crates/zkevm_circuits/src/sha256_round_function/mod.rs b/crates/zkevm_circuits/src/sha256_round_function/mod.rs new file mode 100644 index 00000000..400ec6d4 --- /dev/null +++ b/crates/zkevm_circuits/src/sha256_round_function/mod.rs @@ -0,0 +1,464 @@ +use super::*; + +use boojum::field::SmallField; + +use boojum::gadgets::traits::witnessable::WitnessHookable; + +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use cs_derive::*; + +use crate::ethereum_types::U256; +use crate::fsm_input_output::circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH; +use boojum::gadgets::num::Num; +use zkevm_opcode_defs::system_params::PRECOMPILE_AUX_BYTE; + +use crate::base_structures::log_query::*; +use crate::base_structures::memory_query::*; +use crate::base_structures::precompile_input_outputs::PrecompileFunctionOutputData; +use crate::demux_log_queue::StorageLogQueue; +use crate::fsm_input_output::*; +use crate::storage_application::ConditionalWitnessAllocator; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::Variable; +use boojum::gadgets::queue::CircuitQueueWitness; +use boojum::gadgets::queue::QueueState; +use boojum::gadgets::sha256::{self}; +use boojum::gadgets::traits::allocatable::CSAllocatable; +use boojum::gadgets::traits::allocatable::{CSAllocatableExt, CSPlaceholder}; +use boojum::gadgets::traits::encodable::CircuitVarLengthEncodable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::u160::UInt160; +use boojum::gadgets::u8::UInt8; +use std::sync::{Arc, RwLock}; + +pub mod input; +use self::input::*; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +// #[DerivePrettyComparison("true")] +pub struct Sha256PrecompileCallParams { + pub input_page: UInt32, + pub input_offset: UInt32, + pub output_page: UInt32, + pub output_offset: UInt32, + pub num_rounds: UInt32, +} + +impl CSPlaceholder for Sha256PrecompileCallParams { + fn placeholder>(cs: &mut CS) -> Self { + let zero_u32 = UInt32::zero(cs); + Self { + input_page: zero_u32, + input_offset: zero_u32, + output_page: zero_u32, + output_offset: zero_u32, + num_rounds: zero_u32, + } + } +} + +impl Sha256PrecompileCallParams { + pub fn from_encoding>(_cs: &mut CS, encoding: UInt256) -> Self { + let input_offset = encoding.inner[0]; + let output_offset = encoding.inner[2]; + let input_page = encoding.inner[4]; + let output_page = encoding.inner[5]; + + let num_rounds = encoding.inner[6]; + + let new = Self { + input_page, + input_offset, + output_page, + output_offset, + num_rounds, + }; + + new + } +} + +pub const MEMORY_READ_QUERIES_PER_CYCLE: usize = 2; + +pub fn sha256_precompile_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + memory_queue: &mut MemoryQueue, + precompile_calls_queue: &mut StorageLogQueue, + memory_read_witness: ConditionalWitnessAllocator>, + mut state: Sha256RoundFunctionFSM, + _round_function: &R, + limit: usize, +) -> Sha256RoundFunctionFSM +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + assert!(limit <= u32::MAX as usize); + + let precompile_address = UInt160::allocated_constant( + cs, + *zkevm_opcode_defs::system_params::SHA256_ROUND_FUNCTION_PRECOMPILE_FORMAL_ADDRESS, + ); + let aux_byte_for_precompile = UInt8::allocated_constant(cs, PRECOMPILE_AUX_BYTE); + + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + let zero_u32 = UInt32::zero(cs); + let zero_u256 = UInt256::zero(cs); + + // 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; + let input_queue_is_empty = precompile_calls_queue.is_empty(cs); + // we can only skip the full circuit if we are not in any form of progress + let can_finish_immediatelly = + Boolean::multi_and(cs, &[state.read_precompile_call, input_queue_is_empty]); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(can_finish_immediatelly.witness_hook(cs)()); + dbg!(state.witness_hook(cs)()); + } + + state.read_precompile_call = state + .read_precompile_call + .mask_negated(cs, can_finish_immediatelly); + state.read_words_for_round = state + .read_words_for_round + .mask_negated(cs, can_finish_immediatelly); + state.completed = Boolean::multi_or(cs, &[state.completed, can_finish_immediatelly]); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(state.witness_hook(cs)()); + dbg!(precompile_calls_queue.into_state().witness_hook(cs)()); + memory_read_witness.print_debug_info(); + } + // main work cycle + for _cycle in 0..limit { + if crate::config::CIRCUIT_VERSOBE { + dbg!(_cycle); + dbg!(state.witness_hook(cs)()); + dbg!(precompile_calls_queue.into_state().witness_hook(cs)()); + } + // 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); + + Num::conditionally_enforce_equal( + cs, + state.read_precompile_call, + &Num::from_variable(precompile_call.aux_byte.get_variable()), + &Num::from_variable(aux_byte_for_precompile.get_variable()), + ); + for (a, b) in precompile_call + .address + .inner + .iter() + .zip(precompile_address.inner.iter()) + { + Num::conditionally_enforce_equal( + cs, + state.read_precompile_call, + &Num::from_variable(a.get_variable()), + &Num::from_variable(b.get_variable()), + ); + } + + // now compute some parameters that describe the call itself + + let params_encoding = precompile_call.key; + let call_params = Sha256PrecompileCallParams::from_encoding(cs, params_encoding); + + state.precompile_call_params = Sha256PrecompileCallParams::conditionally_select( + cs, + state.read_precompile_call, + &call_params, + &state.precompile_call_params, + ); + // also set timestamps + state.timestamp_to_use_for_read = UInt32::conditionally_select( + cs, + state.read_precompile_call, + &precompile_call.timestamp, + &state.timestamp_to_use_for_read, + ); + + // timestamps have large space, so this can be expected + let timestamp_to_use_for_write = + unsafe { state.timestamp_to_use_for_read.increment_unchecked(cs) }; + state.timestamp_to_use_for_write = UInt32::conditionally_select( + cs, + state.read_precompile_call, + ×tamp_to_use_for_write, + &state.timestamp_to_use_for_write, + ); + + let reset_buffer = Boolean::multi_or(cs, &[state.read_precompile_call, state.completed]); + state.read_words_for_round = Boolean::multi_or( + cs, + &[state.read_precompile_call, state.read_words_for_round], + ); + state.read_precompile_call = boolean_false; + + // --------------------------------- + // Now perform few memory queries to read content + + let zero_rounds_left = state.precompile_call_params.num_rounds.is_zero(cs); + + let mut memory_queries_as_u32_words = [zero_u32; 8 * MEMORY_READ_QUERIES_PER_CYCLE]; + let should_read = zero_rounds_left.negated(cs); + let mut bias_variable = should_read.get_variable(); + for dst in memory_queries_as_u32_words.array_chunks_mut::<8>() { + let read_query_value = + memory_read_witness.conditionally_allocate_biased(cs, should_read, bias_variable); + bias_variable = read_query_value.inner[0].get_variable(); + + 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, + 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( + cs, + state.read_words_for_round, + &may_be_new_offset, + &state.precompile_call_params.input_offset, + ); + + // perform read + memory_queue.push(cs, read_query, should_read); + + // we need to change endianess. Memory is BE, and each of 4 byte chunks should be interpreted as BE u32 for sha256 + let be_bytes = read_query_value.to_be_bytes(cs); + for (dst, src) in dst.iter_mut().zip(be_bytes.array_chunks::<4>()) { + let as_u32 = UInt32::from_be_bytes(cs, *src); + *dst = as_u32; + } + } + + 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( + cs, + state.read_words_for_round, + &may_be_new_num_rounds, + &state.precompile_call_params.num_rounds, + ); + + // absorb + let sha256_empty_internal_state = sha256::ivs_as_uint32(cs); + + let mut current_sha256_state = <[UInt32; 8]>::conditionally_select( + cs, + reset_buffer, + &sha256_empty_internal_state, + &state.sha256_inner_state, + ); + + let sha256_output = sha256::round_function::round_function_over_uint32( + cs, + &mut current_sha256_state, + &memory_queries_as_u32_words, + ); + state.sha256_inner_state = current_sha256_state; + + let no_rounds_left = state.precompile_call_params.num_rounds.is_zero(cs); + let write_result = Boolean::multi_and(cs, &[state.read_words_for_round, no_rounds_left]); + + let mut write_word = zero_u256; + // some endianess magic + for (dst, src) in write_word + .inner + .iter_mut() + .rev() + .zip(sha256_output.array_chunks::<4>()) + { + *dst = UInt32::from_le_bytes(cs, *src); + } + + 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, + rw_flag: boolean_true, + is_ptr: boolean_false, + value: write_word, + }; + + // perform write + memory_queue.push(cs, write_query, write_result); + + // --------------------------------- + + // update 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]); + let process_next = Boolean::multi_and(cs, &[write_result, input_is_not_empty]); + + 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]); + state.read_words_for_round = t.negated(cs); + + if crate::config::CIRCUIT_VERSOBE { + dbg!(state.witness_hook(cs)()); + dbg!(precompile_calls_queue.into_state().witness_hook(cs)()); + } + } + + if crate::config::CIRCUIT_VERSOBE { + dbg!(state.witness_hook(cs)()); + dbg!(precompile_calls_queue.into_state().witness_hook(cs)()); + } + + precompile_calls_queue.enforce_consistency(cs); + + state +} + +#[track_caller] +pub fn sha256_round_function_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: Sha256RoundFunctionCircuitInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + let Sha256RoundFunctionCircuitInstanceWitness { + closed_form_input, + requests_queue_witness, + memory_reads_witness, + } = witness; + + let mut structured_input = Sha256RoundFunctionCircuitInputOutput::alloc_ignoring_outputs( + cs, + closed_form_input.clone(), + ); + + let start_flag = structured_input.start_flag; + + let requests_queue_state_from_input = structured_input.observable_input.initial_log_queue_state; + + // it must be trivial + requests_queue_state_from_input.enforce_trivial_head(cs); + + let requests_queue_state_from_fsm = structured_input.hidden_fsm_input.log_queue_state; + + let requests_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &requests_queue_state_from_input, + &requests_queue_state_from_fsm, + ); + + let memory_queue_state_from_input = + structured_input.observable_input.initial_memory_queue_state; + + // it must be trivial + memory_queue_state_from_input.enforce_trivial_head(cs); + + let memory_queue_state_from_fsm = structured_input.hidden_fsm_input.memory_queue_state; + + let memory_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &memory_queue_state_from_input, + &memory_queue_state_from_fsm, + ); + + let mut requests_queue = StorageLogQueue::::from_state(cs, requests_queue_state); + let queue_witness = CircuitQueueWitness::from_inner_witness(requests_queue_witness); + requests_queue.witness = Arc::new(queue_witness); + + let mut memory_queue = MemoryQueue::::from_state(cs, memory_queue_state); + + let read_queries_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(memory_reads_witness)), + }; + + let mut starting_fsm_state = Sha256RoundFunctionFSM::placeholder(cs); + starting_fsm_state.read_precompile_call = Boolean::allocated_constant(cs, true); + + let initial_state = Sha256RoundFunctionFSM::conditionally_select( + cs, + start_flag, + &starting_fsm_state, + &structured_input.hidden_fsm_input.internal_fsm, + ); + + let final_state = sha256_precompile_inner::( + cs, + &mut memory_queue, + &mut requests_queue, + read_queries_allocator, + initial_state, + round_function, + limit, + ); + + let final_memory_state = memory_queue.into_state(); + let final_requets_state = requests_queue.into_state(); + + // form the final state + let done = final_state.completed; + structured_input.completion_flag = done; + structured_input.observable_output = PrecompileFunctionOutputData::placeholder(cs); + + structured_input.observable_output.final_memory_state = QueueState::conditionally_select( + cs, + structured_input.completion_flag, + &final_memory_state, + &structured_input.observable_output.final_memory_state, + ); + + structured_input.hidden_fsm_output.internal_fsm = final_state; + structured_input.hidden_fsm_output.log_queue_state = final_requets_state; + structured_input.hidden_fsm_output.memory_queue_state = final_memory_state; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} diff --git a/crates/zkevm_circuits/src/sort_decommittment_requests/input.rs b/crates/zkevm_circuits/src/sort_decommittment_requests/input.rs new file mode 100644 index 00000000..de157547 --- /dev/null +++ b/crates/zkevm_circuits/src/sort_decommittment_requests/input.rs @@ -0,0 +1,124 @@ +use crate::base_structures::decommit_query::DECOMMIT_QUERY_PACKED_WIDTH; +use crate::sort_decommittment_requests::full_state_queue::FullStateCircuitQueueRawWitness; +use crate::sort_decommittment_requests::*; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::num::Num; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; + +pub const PACKED_KEY_LENGTH: usize = 8 + 1; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct CodeDecommittmentsDeduplicatorFSMInputOutput { + pub initial_queue_state: QueueState, + pub sorted_queue_state: QueueState, + pub final_queue_state: QueueState, + + pub lhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub rhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + + pub previous_packed_key: [UInt32; PACKED_KEY_LENGTH], + pub first_encountered_timestamp: UInt32, + pub previous_record: DecommitQuery, +} + +impl CSPlaceholder for CodeDecommittmentsDeduplicatorFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + let zero_num = Num::zero(cs); + let zero_u32 = UInt32::zero(cs); + + Self { + initial_queue_state: QueueState::::placeholder(cs), + sorted_queue_state: QueueState::::placeholder(cs), + final_queue_state: QueueState::::placeholder(cs), + + lhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + rhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + + previous_packed_key: [zero_u32; PACKED_KEY_LENGTH], + first_encountered_timestamp: zero_u32, + previous_record: DecommitQuery::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct CodeDecommittmentsDeduplicatorInputData { + pub initial_queue_state: QueueState, + pub sorted_queue_initial_state: QueueState, +} + +impl CSPlaceholder for CodeDecommittmentsDeduplicatorInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + initial_queue_state: QueueState::::placeholder(cs), + sorted_queue_initial_state: QueueState::::placeholder( + cs, + ), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct CodeDecommittmentsDeduplicatorOutputData { + pub final_queue_state: QueueState, +} + +impl CSPlaceholder for CodeDecommittmentsDeduplicatorOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + final_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type CodeDecommittmentsDeduplicatorInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + CodeDecommittmentsDeduplicatorFSMInputOutput, + CodeDecommittmentsDeduplicatorInputData, + CodeDecommittmentsDeduplicatorOutputData, +>; +pub type CodeDecommittmentsDeduplicatorInputOutputWitness = + crate::fsm_input_output::ClosedFormInputWitness< + F, + CodeDecommittmentsDeduplicatorFSMInputOutput, + CodeDecommittmentsDeduplicatorInputData, + CodeDecommittmentsDeduplicatorOutputData, + >; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct CodeDecommittmentsDeduplicatorInstanceWitness { + pub closed_form_input: CodeDecommittmentsDeduplicatorInputOutputWitness, + pub initial_queue_witness: FullStateCircuitQueueRawWitness< + F, + DecommitQuery, + FULL_SPONGE_QUEUE_STATE_WIDTH, + DECOMMIT_QUERY_PACKED_WIDTH, + >, + pub sorted_queue_witness: FullStateCircuitQueueRawWitness< + F, + DecommitQuery, + FULL_SPONGE_QUEUE_STATE_WIDTH, + DECOMMIT_QUERY_PACKED_WIDTH, + >, +} diff --git a/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs b/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs new file mode 100644 index 00000000..9c5b51b6 --- /dev/null +++ b/crates/zkevm_circuits/src/sort_decommittment_requests/mod.rs @@ -0,0 +1,1389 @@ +use super::*; + +use crate::base_structures::log_query::LOG_QUERY_PACKED_WIDTH; +use crate::fsm_input_output::ClosedFormInputCompactForm; +use crate::utils::accumulate_grand_products; + +use boojum::cs::{gates::*, traits::cs::ConstraintSystem}; +use boojum::field::SmallField; +use boojum::gadgets::queue::full_state_queue::FullStateCircuitQueueWitness; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::traits::witnessable::WitnessHookable; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{allocatable::CSAllocatableExt, selectable::Selectable}, + u32::UInt32, +}; + +use crate::base_structures::decommit_query::{DecommitQueue, DECOMMIT_QUERY_PACKED_WIDTH}; +use crate::base_structures::vm_state::*; +use crate::base_structures::{ + decommit_query::DecommitQuery, memory_query::MEMORY_QUERY_PACKED_WIDTH, +}; +use crate::fsm_input_output::{circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH, *}; +use crate::sort_decommittment_requests::input::*; +use crate::storage_validity_by_grand_product::unpacked_long_comparison; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::gadgets::traits::allocatable::CSPlaceholder; +use boojum::gadgets::u256::UInt256; + +pub mod input; + +// This is a sorter of logs that are kind-of "pure", F.g. event emission or L2 -> L1 messages. +// Those logs do not affect a global state and may either be rolled back in full or not. +// We identify equality of logs using "timestamp" field that is a monotonic unique counter +// across the block +pub const NUM_PERMUTATION_ARG_CHALLENGES: usize = LOG_QUERY_PACKED_WIDTH + 1; + +pub fn sort_and_deduplicate_code_decommittments_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: CodeDecommittmentsDeduplicatorInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + // as usual we assume that a caller of this fuunction has already split input queue, + // so it can be comsumed in full + + //use table + let CodeDecommittmentsDeduplicatorInstanceWitness { + closed_form_input, + initial_queue_witness, + sorted_queue_witness, + } = witness; + + let mut structured_input = CodeDecommittmentsDeduplicatorInputOutput::alloc_ignoring_outputs( + cs, + closed_form_input.clone(), + ); + + let initial_queue_from_passthrough_state = + structured_input.observable_input.initial_queue_state; + let initial_log_queue_state_from_fsm_state = + structured_input.hidden_fsm_input.initial_queue_state; + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &initial_queue_from_passthrough_state, + &initial_log_queue_state_from_fsm_state, + ); + let mut initial_queue = DecommitQueue::::from_state(cs, state); + + // passthrough must be trivial + initial_queue_from_passthrough_state.enforce_trivial_head(cs); + + use std::sync::Arc; + let initial_queue_witness = + FullStateCircuitQueueWitness::from_inner_witness(initial_queue_witness); + initial_queue.witness = Arc::new(initial_queue_witness); + + let intermediate_sorted_queue_from_passthrough_state = + structured_input.observable_input.sorted_queue_initial_state; + let intermediate_sorted_queue_from_fsm_input_state = + structured_input.hidden_fsm_input.sorted_queue_state; + + // it must be trivial + intermediate_sorted_queue_from_passthrough_state.enforce_trivial_head(cs); + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &intermediate_sorted_queue_from_passthrough_state, + &intermediate_sorted_queue_from_fsm_input_state, + ); + let mut intermediate_sorted_queue = DecommitQueue::::from_state(cs, state); + + let sorted_queue_witness = + FullStateCircuitQueueWitness::from_inner_witness(sorted_queue_witness); + intermediate_sorted_queue.witness = Arc::new(sorted_queue_witness); + + let empty_state = QueueState::empty(cs); + + let final_sorted_queue_from_fsm_state = structured_input.hidden_fsm_input.final_queue_state; + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &empty_state, + &final_sorted_queue_from_fsm_state, + ); + let mut final_sorted_queue = DecommitQueue::::from_state(cs, state); + + let challenges = crate::utils::produce_fs_challenges::< + F, + CS, + R, + FULL_SPONGE_QUEUE_STATE_WIDTH, + { DECOMMIT_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + structured_input.observable_input.initial_queue_state.tail, + structured_input + .observable_input + .sorted_queue_initial_state + .tail, + round_function, + ); + + let one = Num::allocated_constant(cs, F::ONE); + let initial_lhs = Num::parallel_select( + cs, + structured_input.start_flag, + &[one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &structured_input.hidden_fsm_input.lhs_accumulator, + ); + + let initial_rhs = Num::parallel_select( + cs, + structured_input.start_flag, + &[one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &structured_input.hidden_fsm_input.rhs_accumulator, + ); + + let trivial_record = DecommitQuery::placeholder(cs); + let mut previous_record = DecommitQuery::conditionally_select( + cs, + structured_input.start_flag, + &trivial_record, + &structured_input.hidden_fsm_input.previous_record, + ); + + let zero_u32 = UInt32::zero(cs); + let mut previous_packed_key = <[UInt32; PACKED_KEY_LENGTH]>::conditionally_select( + cs, + structured_input.start_flag, + &[zero_u32; PACKED_KEY_LENGTH], + &structured_input.hidden_fsm_input.previous_packed_key, + ); + + let mut first_encountered_timestamp = UInt32::conditionally_select( + cs, + structured_input.start_flag, + &zero_u32, + &structured_input + .hidden_fsm_input + .first_encountered_timestamp, + ); + + let (completed, new_lhs, new_rhs) = sort_and_deduplicate_code_decommittments_inner( + cs, + &mut initial_queue, + &mut intermediate_sorted_queue, + &mut final_sorted_queue, + initial_lhs, + initial_rhs, + challenges, + &mut previous_packed_key, + &mut first_encountered_timestamp, + &mut previous_record, + structured_input.start_flag, + limit, + ); + + for (lhs, rhs) in new_lhs.iter().zip(new_rhs.iter()) { + Num::conditionally_enforce_equal(cs, completed, lhs, rhs); + } + // form the final state + structured_input.observable_output = CodeDecommittmentsDeduplicatorOutputData::placeholder(cs); + + structured_input.hidden_fsm_output = + CodeDecommittmentsDeduplicatorFSMInputOutput::placeholder(cs); + structured_input.hidden_fsm_output.initial_queue_state = initial_queue.into_state(); + structured_input.hidden_fsm_output.sorted_queue_state = intermediate_sorted_queue.into_state(); + structured_input.hidden_fsm_output.final_queue_state = final_sorted_queue.into_state(); + structured_input.hidden_fsm_output.lhs_accumulator = new_lhs; + structured_input.hidden_fsm_output.rhs_accumulator = new_rhs; + structured_input.hidden_fsm_output.previous_packed_key = previous_packed_key; + structured_input.hidden_fsm_output.previous_record = previous_record; + structured_input + .hidden_fsm_output + .first_encountered_timestamp = first_encountered_timestamp; + + structured_input.observable_output.final_queue_state = QueueState::conditionally_select( + cs, + completed, + &structured_input.hidden_fsm_output.final_queue_state, + &structured_input.observable_output.final_queue_state, + ); + + structured_input.completion_flag = completed; + + // self-check + structured_input.hook_compare_witness(cs, &closed_form_input); + + 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 +} + +pub fn sort_and_deduplicate_code_decommittments_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + original_queue: &mut DecommitQueue, + sorted_queue: &mut DecommitQueue, + result_queue: &mut DecommitQueue, + mut lhs: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + mut rhs: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + fs_challenges: [[Num; DECOMMIT_QUERY_PACKED_WIDTH + 1]; + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + previous_packed_key: &mut [UInt32; PACKED_KEY_LENGTH], + first_encountered_timestamp: &mut UInt32, + previous_record: &mut DecommitQuery, + start_flag: Boolean, + limit: usize, +) -> ( + Boolean, + [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], +) +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); DECOMMIT_QUERY_PACKED_WIDTH + 1]:, +{ + assert!(limit <= u32::MAX as usize); + let unsorted_queue_length = Num::from_variable(original_queue.length.get_variable()); + let intermediate_sorted_queue_length = Num::from_variable(sorted_queue.length.get_variable()); + + Num::enforce_equal( + cs, + &unsorted_queue_length, + &intermediate_sorted_queue_length, + ); + + let no_work = original_queue.is_empty(cs); + + let mut previous_item_is_trivial = no_work.or(cs, start_flag); + + // Simultaneously pop, prove sorting and resolve logic + + for _cycle in 0..limit { + let original_is_empty = original_queue.is_empty(cs); + let sorted_is_empty = sorted_queue.is_empty(cs); + Boolean::enforce_equal(cs, &original_is_empty, &sorted_is_empty); + + let should_pop = original_is_empty.negated(cs); + let is_trivial = original_is_empty; + + let (_, original_encoding) = original_queue.pop_front(cs, should_pop); + let (sorted_item, sorted_encoding) = sorted_queue.pop_front(cs, should_pop); + + // we make encoding that is the same as defined for timestamped item + accumulate_grand_products::< + F, + CS, + DECOMMIT_QUERY_PACKED_WIDTH, + { DECOMMIT_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + &mut lhs, + &mut rhs, + &fs_challenges, + &original_encoding, + &sorted_encoding, + should_pop, + ); + + // check if keys are equal and check a value + let packed_key = concatenate_key(cs, (sorted_item.timestamp, sorted_item.code_hash)); + + // ensure sorting for uniqueness timestamp and rollback flag + // We know that timestamps are unique accross logs, and are also the same between write and rollback + let (_keys_are_equal, new_key_is_greater) = + unpacked_long_comparison(cs, &packed_key, &*previous_packed_key); + // always ascedning + new_key_is_greater.conditionally_enforce_true(cs, should_pop); + + let same_hash = UInt256::equals(cs, &previous_record.code_hash, &sorted_item.code_hash); + + // if we get new hash then it my have a "first" marker + let different_hash = same_hash.negated(cs); + let enforce_must_be_first = Boolean::multi_and(cs, &[different_hash, should_pop]); + sorted_item + .is_first + .conditionally_enforce_true(cs, enforce_must_be_first); + + // otherwise it should have the same memory page + let previous_is_non_trivial = previous_item_is_trivial.negated(cs); + let enforce_same_memory_page = + Boolean::multi_and(cs, &[same_hash, previous_is_non_trivial]); + + Num::conditionally_enforce_equal( + cs, + enforce_same_memory_page, + &sorted_item.page.into_num(), + &previous_record.page.into_num(), + ); + + // decide if we should add the PREVIOUS into the queue + let add_to_the_queue = Boolean::multi_and(cs, &[previous_is_non_trivial, different_hash]); + + let mut record_to_add = *previous_record; + record_to_add.is_first = Boolean::allocated_constant(cs, true); // we use convension to be easier consistent with out of circuit part + record_to_add.timestamp = *first_encountered_timestamp; + result_queue.push(cs, record_to_add, add_to_the_queue); + + previous_item_is_trivial = is_trivial; + // may be update the timestamp + *first_encountered_timestamp = UInt32::conditionally_select( + cs, + same_hash, + &first_encountered_timestamp, + &sorted_item.timestamp, + ); + *previous_record = sorted_item; + *previous_packed_key = packed_key; + } + + // if this circuit is the last one the queues must be empty and grand products must be equal + let completed = original_queue.is_empty(cs); + let sorted_queue_is_empty = sorted_queue.is_empty(cs); + Boolean::enforce_equal(cs, &completed, &sorted_queue_is_empty); + + // finalization step - push the last one if necessary + { + let previous_is_non_trivial = previous_item_is_trivial.negated(cs); + let add_to_the_queue = Boolean::multi_and(cs, &[previous_is_non_trivial, completed]); + + let mut record_to_add = *previous_record; + record_to_add.is_first = Boolean::allocated_constant(cs, true); // we use convension to be easier consistent with out of circuit part + record_to_add.timestamp = *first_encountered_timestamp; + + result_queue.push(cs, record_to_add, add_to_the_queue); + } + + original_queue.enforce_consistency(cs); + sorted_queue.enforce_consistency(cs); + + (completed, lhs, rhs) +} + +fn concatenate_key>( + _cs: &mut CS, + key_tuple: (UInt32, UInt256), +) -> [UInt32; PACKED_KEY_LENGTH] { + // LE packing so comparison is subtraction + let (timestamp, key) = key_tuple; + [ + timestamp, + key.inner[0], + key.inner[1], + key.inner[2], + key.inner[3], + key.inner[4], + key.inner[5], + key.inner[6], + key.inner[7], + ] +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ethereum_types::U256; + use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; + use boojum::cs::implementations::reference_cs::CSDevelopmentAssembly; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::tables::*; + use boojum::gadgets::traits::allocatable::CSPlaceholder; + use boojum::gadgets::u256::UInt256; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::worker::Worker; + type F = GoldilocksField; + type P = GoldilocksField; + + #[test] + fn test_sort_and_deduplicate_code_decommittments_inner() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + use boojum::cs::cs_builder::*; + + 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 = 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 = NopGate::configure_builder( + builder, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + + builder + } + + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder_reference::CsReferenceImplementationBuilder; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 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(()); + + // add tables + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let cs = &mut owned_cs; + + let execute = Boolean::allocated_constant(cs, true); + let mut original_queue = DecommitQueue::::empty(cs); + let unsorted_input = witness_input_unsorted(cs); + for el in unsorted_input { + original_queue.push(cs, el, execute); + } + let mut sorted_queue = DecommitQueue::::empty(cs); + let sorted_input = witness_input_sorted(cs); + for el in sorted_input { + sorted_queue.push(cs, el, execute); + } + + let mut result_queue = DecommitQueue::empty(cs); + + let lhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let rhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let is_start = Boolean::allocated_constant(cs, true); + let round_function = Poseidon2Goldilocks; + let fs_challenges = crate::utils::produce_fs_challenges::< + F, + _, + Poseidon2Goldilocks, + FULL_SPONGE_QUEUE_STATE_WIDTH, + { MEMORY_QUERY_PACKED_WIDTH + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + original_queue.into_state().tail, + sorted_queue.into_state().tail, + &round_function, + ); + let limit = 16; + let mut previous_packed_key = [UInt32::allocated_constant(cs, 0); PACKED_KEY_LENGTH]; + let mut first_encountered_timestamp = UInt32::allocated_constant(cs, 0); + let mut previous_record = DecommitQuery::placeholder(cs); + sort_and_deduplicate_code_decommittments_inner( + cs, + &mut original_queue, + &mut sorted_queue, + &mut result_queue, + lhs, + rhs, + fs_challenges, + &mut previous_packed_key, + &mut first_encountered_timestamp, + &mut previous_record, + is_start, + limit, + ); + + cs.pad_and_shrink(); + let worker = Worker::new(); + let mut owned_cs = owned_cs.into_assembly(); + owned_cs.print_gate_stats(); + assert!(owned_cs.check_if_satisfied(&worker)); + } + fn witness_input_unsorted>(cs: &mut CS) -> Vec> { + let mut unsorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452334469539131717490596781220410444809589670111004622364436613658071035425", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 8), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 1), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 1205), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 1609), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 1969), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 2429), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 2901), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 3265), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 3597), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 4001), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 4389), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2048), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 4889), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 5413), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 6181), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452315323292561252187007358802027294616051526905825659974295089200090160077", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2144), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 7689), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 8333), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 9281), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 10337), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 11169), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452315323292561252187007358802027294616051526905825659974295089200090160077", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2144), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 13521), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 14197), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 15209), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 16321), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 17217), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452315323292561252187007358802027294616051526905825659974295089200090160077", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2144), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 19561), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 20269), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 21345), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 22457), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 23417), + }; + unsorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452315323292561252187007358802027294616051526905825659974295089200090160077", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2144), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 26209), + }; + unsorted_querie.push(q); + + unsorted_querie + } + fn witness_input_sorted>(cs: &mut CS) -> Vec> { + let mut sorted_querie = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452313746998214869734508634865817576060841700842481516984674100922521850987", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2368), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 40973), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452313746998214869734508634865817576060841700842481516984674100922521850987", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2368), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 41617), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452313746998214869734508634865817576060841700842481516984674100922521850987", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2368), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 42369), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314299079945159748026115793412643474177571247148724523427478208200944620", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2680), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 60885), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 5413), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 8333), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 10337), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 14197), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 16321), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 20269), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 22457), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 26949), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 31393), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 38757), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 53341), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 53865), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 54737), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 56061), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 57493), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314302625333346664221779405237214670769280401891479637776384083169086090", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2128), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 59957), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 6181), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 11169), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 17217), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 23417), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314303070458905393772003110276921984481582690891142221610001680774704050", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2136), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 37357), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_true, + timestamp: UInt32::allocated_constant(cs, 9281), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 15209), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 21345), + }; + sorted_querie.push(q); + + let q = DecommitQuery:: { + code_hash: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452314563023454543640061243127807783650961769624362936951212864970460788229", + ) + .unwrap(), + ), + page: UInt32::allocated_constant(cs, 2160), + is_first: bool_false, + timestamp: UInt32::allocated_constant(cs, 28089), + }; + sorted_querie.push(q); + + sorted_querie + } +} diff --git a/crates/zkevm_circuits/src/storage_application/input.rs b/crates/zkevm_circuits/src/storage_application/input.rs new file mode 100644 index 00000000..5eb45bc6 --- /dev/null +++ b/crates/zkevm_circuits/src/storage_application/input.rs @@ -0,0 +1,121 @@ +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + 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::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use boojum::gadgets::{ + boolean::Boolean, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, +}; +use boojum::serde_utils::BigArraySerde; +use cs_derive::*; +use derivative::*; +use std::collections::VecDeque; + +pub const STORAGE_DEPTH: usize = 256; + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct StorageApplicationFSMInputOutput { + pub current_root_hash: [UInt8; 32], + pub next_enumeration_counter: [UInt32; 2], + pub current_storage_application_log_state: QueueState, + pub current_diffs_keccak_accumulator_state: + [[[UInt8; keccak256::BYTES_PER_WORD]; keccak256::LANE_WIDTH]; keccak256::LANE_WIDTH], +} + +impl CSPlaceholder for StorageApplicationFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + Self { + current_root_hash: [UInt8::::placeholder(cs); 32], + next_enumeration_counter: [UInt32::::placeholder(cs); 2], + current_storage_application_log_state: QueueState::::placeholder( + cs, + ), + current_diffs_keccak_accumulator_state: [[[UInt8::::placeholder(cs); + keccak256::BYTES_PER_WORD]; + keccak256::LANE_WIDTH]; + keccak256::LANE_WIDTH], + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct StorageApplicationInputData { + pub shard: UInt8, + pub initial_root_hash: [UInt8; 32], + pub initial_next_enumeration_counter: [UInt32; 2], + pub storage_application_log_state: QueueState, +} + +impl CSPlaceholder for StorageApplicationInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + shard: UInt8::::placeholder(cs), + initial_root_hash: [UInt8::::placeholder(cs); 32], + initial_next_enumeration_counter: [UInt32::::placeholder(cs); 2], + storage_application_log_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct StorageApplicationOutputData { + pub new_root_hash: [UInt8; 32], + pub new_next_enumeration_counter: [UInt32; 2], + pub state_diffs_keccak256_hash: [UInt8; 32], +} + +impl CSPlaceholder for StorageApplicationOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + new_root_hash: [UInt8::::placeholder(cs); 32], + new_next_enumeration_counter: [UInt32::::placeholder(cs); 2], + state_diffs_keccak256_hash: [UInt8::::placeholder(cs); 32], + } + } +} + +pub type StorageApplicationInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + StorageApplicationFSMInputOutput, + StorageApplicationInputData, + StorageApplicationOutputData, +>; + +pub type StorageApplicationInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + StorageApplicationFSMInputOutput, + StorageApplicationInputData, + StorageApplicationOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct StorageApplicationCircuitInstanceWitness { + pub closed_form_input: StorageApplicationInputOutputWitness, + // #[serde(bound( + // serialize = "CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>: serde::Serialize" + // ))] + // #[serde(bound( + // deserialize = "CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>: serde::de::DeserializeOwned" + // ))] + pub storage_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, + pub merkle_paths: VecDeque>, + pub leaf_indexes_for_reads: VecDeque, +} diff --git a/crates/zkevm_circuits/src/storage_application/mod.rs b/crates/zkevm_circuits/src/storage_application/mod.rs new file mode 100644 index 00000000..950930f1 --- /dev/null +++ b/crates/zkevm_circuits/src/storage_application/mod.rs @@ -0,0 +1,720 @@ +use std::collections::VecDeque; +use std::mem::MaybeUninit; + +use crate::base_structures::log_query::LogQuery; +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 boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::config::*; +use boojum::cs::traits::cs::{ConstraintSystem, DstBuffer}; +use boojum::cs::{Place, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::blake2s::blake2s; +use boojum::gadgets::boolean::Boolean; +use boojum::gadgets::keccak256; +use boojum::gadgets::num::Num; +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::u256::UInt256; +use boojum::gadgets::u32::UInt32; +use boojum::gadgets::u8::UInt8; +use std::sync::{Arc, RwLock}; +use zkevm_opcode_defs::system_params::STORAGE_AUX_BYTE; + +use super::*; + +pub mod input; +use self::input::*; + +fn u64_as_u32x2_conditionally_increment>( + cs: &mut CS, + input: &[UInt32; 2], + should_increment: &Boolean, +) -> [UInt32; 2] { + let one_u32 = UInt32::allocated_constant(cs, 1u32); + let (incremented_low, carry) = input[0].overflowing_add(cs, one_u32); + let carry_as_u32 = unsafe { UInt32::from_variable_unchecked(carry.get_variable()) }; + let incremented_high = input[1].add_no_overflow(cs, carry_as_u32); + + let selected = Selectable::conditionally_select( + cs, + *should_increment, + &[incremented_low, incremented_high], + input, + ); + + selected +} + +pub(crate) fn keccak256_conditionally_absorb_and_run_permutation< + F: SmallField, + CS: ConstraintSystem, +>( + cs: &mut CS, + condition: Boolean, + state: &mut [[[Variable; keccak256::BYTES_PER_WORD]; keccak256::LANE_WIDTH]; + keccak256::LANE_WIDTH], + block: &[Variable; keccak256::KECCAK_RATE_BYTES], +) { + let mut new_state = *state; + for i in 0..keccak256::LANE_WIDTH { + for j in 0..keccak256::LANE_WIDTH { + if i + keccak256::LANE_WIDTH * j + < (keccak256::KECCAK_RATE_BYTES / keccak256::BYTES_PER_WORD) + { + let tmp = block + .array_chunks::<{ keccak256::BYTES_PER_WORD }>() + .skip(i + keccak256::LANE_WIDTH * j) + .next() + .unwrap(); + use boojum::gadgets::blake2s::mixing_function::xor_many; + new_state[i][j] = xor_many(cs, &new_state[i][j], tmp); + } + } + } + use boojum::gadgets::keccak256::round_function::keccak_256_round_function; + keccak_256_round_function(cs, &mut new_state); + + // if we do not write then discard + for (a, b) in state.iter_mut().zip(new_state.iter()) { + for (a, b) in a.iter_mut().zip(b.iter()) { + let new = b.map(|el| Num::from_variable(el)); + let old = a.map(|el| Num::from_variable(el)); + let selected = Num::parallel_select(cs, condition, &new, &old); + *a = selected.map(|el| el.get_variable()); + } + } +} + +pub struct ConditionalWitnessAllocator> { + pub witness_source: Arc>>, +} + +impl> ConditionalWitnessAllocator +where + [(); EL::INTERNAL_STRUCT_LEN]:, + [(); EL::INTERNAL_STRUCT_LEN + 1]:, +{ + pub fn print_debug_info(&self) { + if let Ok(read_lock) = self.witness_source.read() { + let inner = &*read_lock; + dbg!(inner.len()); + } + } + + pub fn conditionally_allocate_with_default< + CS: ConstraintSystem, + DEF: FnOnce() -> EL::Witness + 'static + Send + Sync, + >( + &self, + cs: &mut CS, + should_allocate: Boolean, + default_values_closure: DEF, + ) -> EL { + let el = EL::allocate_without_value(cs); + + if ::WitnessConfig::EVALUATE_WITNESS { + let dependencies = [should_allocate.get_variable().into()]; + let witness = self.witness_source.clone(); + let value_fn = move |inputs: [F; 1]| { + let should_allocate = >::cast_from_source(inputs[0]); + + let witness = if should_allocate == true { + let mut guard = witness.write().expect("not poisoned"); + let witness_element = guard.pop_front().expect("not empty witness"); + drop(guard); + + witness_element + } else { + let witness_element = (default_values_closure)(); + + witness_element + }; + + let mut result = [F::ZERO; EL::INTERNAL_STRUCT_LEN]; + let mut dst = DstBuffer::MutSlice(&mut result, 0); + EL::set_internal_variables_values(witness, &mut dst); + drop(dst); + + result + }; + + let outputs = Place::from_variables(el.flatten_as_variables()); + + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + el + } + + pub fn conditionally_allocate_with_default_biased< + CS: ConstraintSystem, + DEF: FnOnce() -> EL::Witness + 'static + Send + Sync, + >( + &self, + cs: &mut CS, + should_allocate: Boolean, + bias: Variable, // any variable that has to be resolved BEFORE executing witness query + default_values_closure: DEF, + ) -> EL { + let el = EL::allocate_without_value(cs); + + if ::WitnessConfig::EVALUATE_WITNESS { + let dependencies = [should_allocate.get_variable().into(), bias.into()]; + let witness = self.witness_source.clone(); + let value_fn = move |inputs: [F; 2]| { + let should_allocate = >::cast_from_source(inputs[0]); + + let witness = if should_allocate == true { + let mut guard = witness.write().expect("not poisoned"); + let witness_element = guard.pop_front().expect("not empty witness"); + drop(guard); + + witness_element + } else { + let witness_element = (default_values_closure)(); + + witness_element + }; + + let mut result = [F::ZERO; EL::INTERNAL_STRUCT_LEN]; + let mut dst = DstBuffer::MutSlice(&mut result, 0); + EL::set_internal_variables_values(witness, &mut dst); + drop(dst); + + result + }; + + let outputs = Place::from_variables(el.flatten_as_variables()); + + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + el + } + + pub fn conditionally_allocate>( + &self, + cs: &mut CS, + should_allocate: Boolean, + ) -> EL + where + EL::Witness: Default, + { + self.conditionally_allocate_with_default(cs, should_allocate, || { + std::default::Default::default() + }) + } + + pub fn conditionally_allocate_biased>( + &self, + cs: &mut CS, + should_allocate: Boolean, + bias: Variable, // any variable that has to be resolved BEFORE executing witness query + ) -> EL + where + EL::Witness: Default, + { + self.conditionally_allocate_with_default_biased(cs, should_allocate, bias, || { + std::default::Default::default() + }) + } +} + +fn allocate_enumeration_index_from_witness>( + cs: &mut CS, + should_allocate: Boolean, + witness_source: Arc>>, +) -> [UInt32; 2] { + let flattened: [_; 2] = std::array::from_fn(|_| UInt32::allocate_without_value(cs)); + + if ::WitnessConfig::EVALUATE_WITNESS { + let dependencies = [should_allocate.get_variable().into()]; + let witness = witness_source.clone(); + let value_fn = move |inputs: [F; 1]| { + let should_allocate = >::cast_from_source(inputs[0]); + + let (low, high) = if should_allocate == true { + let mut guard = witness.write().expect("not poisoned"); + let witness_element = guard.pop_front().expect("not empty witness"); + drop(guard); + + witness_element + } else { + (0, 0) + }; + + [ + F::from_u64_with_reduction(low as u64), + F::from_u64_with_reduction(high as u64), + ] + }; + + let outputs = Place::from_variables(flattened.map(|el| el.get_variable())); + + cs.set_values_with_dependencies(&dependencies, &outputs, value_fn); + } + + flattened +} + +pub fn storage_applicator_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + witness: StorageApplicationCircuitInstanceWitness, + round_function: &R, + params: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN + 1]:, +{ + let limit = params; + + assert!(limit <= u32::MAX as usize); + + let StorageApplicationCircuitInstanceWitness { + closed_form_input, + storage_queue_witness, + merkle_paths, + leaf_indexes_for_reads, + } = witness; + + let leaf_indexes_for_reads: VecDeque<_> = leaf_indexes_for_reads + .into_iter() + .map(|el| (el as u32, (el >> 32) as u32)) + .collect(); + + let merkle_paths: VecDeque = merkle_paths + .into_iter() + .flatten() + .map(|el| U256::from_little_endian(&el)) + .collect(); + + let mut structured_input = + StorageApplicationInputOutput::alloc_ignoring_outputs(cs, closed_form_input.clone()); + let start_flag = structured_input.start_flag; + + let mut current_root_hash = UInt8::::parallel_select( + cs, + start_flag, + &structured_input.observable_input.initial_root_hash, + &structured_input.hidden_fsm_input.current_root_hash, + ); + let shard = structured_input.observable_input.shard; + + let mut current_next_enumeration_index = <[UInt32; 2]>::conditionally_select( + cs, + start_flag, + &structured_input + .observable_input + .initial_next_enumeration_counter, + &structured_input.hidden_fsm_input.next_enumeration_counter, + ); + + let storage_queue_state_from_input = structured_input + .observable_input + .storage_application_log_state; + + // it must be trivial + storage_queue_state_from_input.enforce_trivial_head(cs); + + let storage_queue_state_from_fsm = structured_input + .hidden_fsm_input + .current_storage_application_log_state; + + let storage_accesses_queue_state = QueueState::conditionally_select( + cs, + start_flag, + &storage_queue_state_from_input, + &storage_queue_state_from_fsm, + ); + + let mut storage_accesses_queue = + StorageLogQueue::::from_state(cs, storage_accesses_queue_state); + let storage_queue_witness = CircuitQueueWitness::from_inner_witness(storage_queue_witness); + storage_accesses_queue.witness = Arc::new(storage_queue_witness); + + let zero_u8: UInt8 = UInt8::zero(cs); + + let mut diffs_keccak_accumulator_state = + [[[zero_u8; keccak256::BYTES_PER_WORD]; keccak256::LANE_WIDTH]; keccak256::LANE_WIDTH]; + let keccak_sponge_state_from_fsm = structured_input + .hidden_fsm_input + .current_diffs_keccak_accumulator_state; + + for (a, b) in diffs_keccak_accumulator_state + .iter_mut() + .zip(keccak_sponge_state_from_fsm.iter()) + { + for (a, b) in a.iter_mut().zip(b.iter()) { + *a = UInt8::parallel_select(cs, start_flag, &*a, b); + } + } + + let mut diffs_keccak_accumulator_state = + diffs_keccak_accumulator_state.map(|el| el.map(|el| el.map(|el| el.get_variable()))); + + let boolean_false = Boolean::allocated_constant(cs, false); + let boolean_true = Boolean::allocated_constant(cs, true); + let zero_u32 = UInt32::allocated_constant(cs, 0u32); + + let storage_aux_byte = UInt8::allocated_constant(cs, STORAGE_AUX_BYTE); + let mut write_stage_in_progress = boolean_false; + let mut path_key = [zero_u8; 32]; + let mut completed = storage_accesses_queue.is_empty(cs); + let mut merkle_path_witness = Box::new([[zero_u8; 32]; STORAGE_DEPTH]); + let mut current_in_progress_enumeration_index = [zero_u32; 2]; + let mut saved_written_value = [zero_u8; 32]; + + let mut state_diff_data = StateDiffRecord { + address: [zero_u8; 20], + key: [zero_u8; 32], + derived_key: [zero_u8; 32], + enumeration_index: [zero_u8; 8], + initial_value: [zero_u8; 32], + final_value: [zero_u8; 32], + }; + + let read_index_witness_allocator = Arc::new(RwLock::new(leaf_indexes_for_reads)); + let merkle_path_witness_allocator = ConditionalWitnessAllocator::> { + witness_source: Arc::new(RwLock::new(merkle_paths)), + }; + + for cycle in 0..limit { + let is_first = cycle == 0; + let is_last = cycle == limit - 1; + + // if this is the last executing cycle - we do not start the parsing of the new element: + // instead we either complete the second iter of processing of the last element or simply do nothing + let (should_compare_roots, parse_next_queue_elem) = if is_last == false { + let should_compare_roots = completed.negated(cs); + let read_stage_in_progress = write_stage_in_progress.negated(cs); + let parse_next_queue_elem = should_compare_roots.and(cs, read_stage_in_progress); + + (should_compare_roots, parse_next_queue_elem) + } else { + // last iteration can never pop, and is never "read" + + (boolean_false, boolean_false) + }; + + let (storage_log, _) = storage_accesses_queue.pop_front(cs, parse_next_queue_elem); + + let LogQuery { + address, + key, + read_value, + written_value, + rw_flag, + shard_id, + .. + } = storage_log; + + let shard_is_valid = UInt8::equals(cs, &shard_id, &shard); + let aux_byte_is_valid = UInt8::equals(cs, &storage_log.aux_byte, &storage_aux_byte); + let is_valid = Boolean::multi_and(cs, &[shard_is_valid, aux_byte_is_valid]); + is_valid.conditionally_enforce_true(cs, parse_next_queue_elem); + + // we can decompose everythin right away + let address_bytes = address.to_be_bytes(cs); + let key_bytes = key.to_be_bytes(cs); + + let mut bytes_for_key_derivation = [zero_u8; 64]; + bytes_for_key_derivation[12..32].copy_from_slice(&address_bytes); + bytes_for_key_derivation[32..64].copy_from_slice(&key_bytes); + + let derived_key = blake2s(cs, &bytes_for_key_derivation); + + // update current key + path_key = UInt8::parallel_select(cs, parse_next_queue_elem, &derived_key, &path_key); + let mut path_selectors = [boolean_false; STORAGE_DEPTH]; + // get path bits + for (dst, src) in path_selectors.array_chunks_mut::<8>().zip(path_key.iter()) { + let bits: [_; 8] = Num::from_variable(src.get_variable()).spread_into_bits(cs); + *dst = bits; + } + + // determine whether we need to increment enumeration index + let read_index = allocate_enumeration_index_from_witness( + cs, + parse_next_queue_elem, + read_index_witness_allocator.clone(), + ); + // update index over which we work + current_in_progress_enumeration_index = <[UInt32; 2]>::conditionally_select( + cs, + parse_next_queue_elem, + &read_index, + ¤t_in_progress_enumeration_index, + ); + + let idx_parts_are_zeroes = current_in_progress_enumeration_index.map(|el| el.is_zero(cs)); + let current_idx_is_zero = Boolean::multi_and(cs, &idx_parts_are_zeroes); + let should_assign_fresh_idx = + Boolean::multi_and(cs, &[write_stage_in_progress, current_idx_is_zero]); + + // use next enumeration index + current_in_progress_enumeration_index = <[UInt32; 2]>::conditionally_select( + cs, + should_assign_fresh_idx, + ¤t_next_enumeration_index, + ¤t_in_progress_enumeration_index, + ); + current_next_enumeration_index = u64_as_u32x2_conditionally_increment( + cs, + ¤t_next_enumeration_index, + &should_assign_fresh_idx, + ); + + // index is done, now we need merkle path + let mut new_merkle_path_witness = Vec::with_capacity(STORAGE_DEPTH); + let mut bias_variable = parse_next_queue_elem.get_variable(); + for _ in 0..STORAGE_DEPTH { + let wit = merkle_path_witness_allocator.conditionally_allocate_biased( + cs, + parse_next_queue_elem, + bias_variable, + ); + bias_variable = wit.inner[0].get_variable(); + new_merkle_path_witness.push(wit); + } + + // if we read then we save and use it for write too + for (dst, src) in merkle_path_witness + .iter_mut() + .zip(new_merkle_path_witness.iter()) + { + let src_bytes = src.to_le_bytes(cs); // NOP + *dst = UInt8::parallel_select(cs, parse_next_queue_elem, &src_bytes, &*dst); + } + + let read_value_bytes = read_value.to_be_bytes(cs); + let written_value_bytes = written_value.to_be_bytes(cs); + + let mut leaf_value_for_this_stage = read_value_bytes; + // if we just processed a value from the queue then save it + saved_written_value = UInt8::parallel_select( + cs, + parse_next_queue_elem, + &written_value_bytes, + &saved_written_value, + ); + // if we have write stage in progress then use saved value as the one we will use for path + leaf_value_for_this_stage = UInt8::parallel_select( + cs, + write_stage_in_progress, + &saved_written_value, + &leaf_value_for_this_stage, + ); + + // we need to serialize leaf index as 8 bytes + + let leaf_index_low_be = current_in_progress_enumeration_index[0].to_be_bytes(cs); + let leaf_index_high_be = current_in_progress_enumeration_index[1].to_be_bytes(cs); + + let mut leaf_index_bytes = [zero_u8; 8]; + leaf_index_bytes[0..4].copy_from_slice(&leaf_index_high_be); + leaf_index_bytes[4..8].copy_from_slice(&leaf_index_low_be); + + // now we have everything to update state diff data + { + state_diff_data.address = UInt8::parallel_select( + cs, + parse_next_queue_elem, + &address_bytes, + &state_diff_data.address, + ); + state_diff_data.key = + UInt8::parallel_select(cs, parse_next_queue_elem, &key_bytes, &state_diff_data.key); + state_diff_data.derived_key = UInt8::parallel_select( + cs, + parse_next_queue_elem, + &derived_key, + &state_diff_data.derived_key, + ); + // NOTE: we need READ index, before updating + state_diff_data.enumeration_index = UInt8::parallel_select( + cs, + parse_next_queue_elem, + &leaf_index_bytes, + &state_diff_data.enumeration_index, + ); + state_diff_data.initial_value = UInt8::parallel_select( + cs, + parse_next_queue_elem, + &read_value_bytes, + &state_diff_data.initial_value, + ); + state_diff_data.final_value = UInt8::parallel_select( + cs, + parse_next_queue_elem, + &written_value_bytes, + &state_diff_data.final_value, + ); + } + + let mut leaf_bytes = [zero_u8; 32 + 8]; + leaf_bytes[0..8].copy_from_slice(&leaf_index_bytes); + leaf_bytes[8..40].copy_from_slice(&leaf_value_for_this_stage); + + let mut current_hash = blake2s(cs, &leaf_bytes); + + for (path_bit, path_witness) in path_selectors + .into_iter() + .zip(merkle_path_witness.into_iter()) + { + let left = UInt8::parallel_select(cs, path_bit, &path_witness, ¤t_hash); + let right = UInt8::parallel_select(cs, path_bit, ¤t_hash, &path_witness); + let mut input = [zero_u8; 64]; + input[0..32].copy_from_slice(&left); + input[32..64].copy_from_slice(&right); + + current_hash = blake2s(cs, &input); + } + + // in case of read: merkle_root == computed_merkle_root == new_merkle_root + // new_merkle_root = select(if is_write: then new_merkle_root else computed_merkle_root); + // so we first compute merkle_root - either the old one or the selected one and then enforce equality + + // update if we write + current_root_hash = UInt8::parallel_select( + cs, + write_stage_in_progress, + ¤t_hash, + ¤t_root_hash, + ); + // otherwise enforce equality + for (a, b) in current_root_hash.iter().zip(current_hash.iter()) { + Num::conditionally_enforce_equal( + cs, + should_compare_roots, + &Num::from_variable(a.get_variable()), + &Num::from_variable(b.get_variable()), + ); + } + + // update our accumulator + + // we use keccak256 here because it's same table structure + use crate::base_structures::state_diff_record::NUM_KECCAK256_ROUNDS_PER_RECORD_ACCUMULATION; + let mut extended_state_diff_encoding = + [zero_u8; keccak256::KECCAK_RATE_BYTES * NUM_KECCAK256_ROUNDS_PER_RECORD_ACCUMULATION]; + let packed_encoding = state_diff_data.encode(cs); + extended_state_diff_encoding[0..packed_encoding.len()].copy_from_slice(&packed_encoding); + let extended_state_diff_encoding = extended_state_diff_encoding.map(|el| el.get_variable()); + // absorb and run permutation + + // we do not write here anyway + if is_first == false { + for block in + extended_state_diff_encoding.array_chunks::<{ keccak256::KECCAK_RATE_BYTES }>() + { + keccak256_conditionally_absorb_and_run_permutation( + cs, + write_stage_in_progress, + &mut diffs_keccak_accumulator_state, + block, + ); + } + } + + // toggle control flags + let input_queue_is_empty = storage_accesses_queue.is_empty(cs); + // cur elem is processed only in the case second iter in progress or rw_flag is false; + let current_element_is_read = rw_flag.negated(cs); + let cur_elem_was_processed = + Boolean::multi_or(cs, &[write_stage_in_progress, current_element_is_read]); + let completed_now = Boolean::multi_and(cs, &[input_queue_is_empty, cur_elem_was_processed]); + completed = Boolean::multi_or(cs, &[completed, completed_now]); + + write_stage_in_progress = Boolean::multi_and(cs, &[parse_next_queue_elem, rw_flag]); + } + + storage_accesses_queue.enforce_consistency(cs); + + structured_input.completion_flag = completed.clone(); + let storage_queue_state = storage_accesses_queue.into_state(); + + let current_diffs_keccak_accumulator_state_for_fsm = unsafe { + diffs_keccak_accumulator_state + .map(|el| el.map(|el| el.map(|el| UInt8::from_variable_unchecked(el)))) + }; + + let fsm_output = StorageApplicationFSMInputOutput { + current_root_hash: current_root_hash, + next_enumeration_counter: current_next_enumeration_index, + current_storage_application_log_state: storage_queue_state.clone(), + current_diffs_keccak_accumulator_state: current_diffs_keccak_accumulator_state_for_fsm, + }; + structured_input.hidden_fsm_output = fsm_output; + + // we need to run padding and one more permutation for final output + let zero_var = zero_u8.get_variable(); + let mut padding_block = [zero_var; keccak256::KECCAK_RATE_BYTES]; + use boojum::cs::gates::ConstantAllocatableCS; + padding_block[0] = cs.allocate_constant(F::from_u64_unchecked(0x01 as u64)); + padding_block[135] = cs.allocate_constant(F::from_u64_unchecked(0x80 as u64)); + keccak256_conditionally_absorb_and_run_permutation( + cs, + boolean_true, + &mut diffs_keccak_accumulator_state, + &padding_block, + ); + + // squeeze + let mut result = [MaybeUninit::>::uninit(); keccak256::KECCAK256_DIGEST_SIZE]; + for (i, dst) in result.array_chunks_mut::<8>().enumerate() { + for (dst, src) in dst + .iter_mut() + .zip(diffs_keccak_accumulator_state[i][0].iter()) + { + let tmp = unsafe { UInt8::from_variable_unchecked(*src) }; + dst.write(tmp); + } + } + + let state_diffs_keccak256_hash = unsafe { result.map(|el| el.assume_init()) }; + + let observable_output = StorageApplicationOutputData { + new_root_hash: current_root_hash, + new_next_enumeration_counter: current_next_enumeration_index, + state_diffs_keccak256_hash: state_diffs_keccak256_hash, + }; + + let empty_observable_output = StorageApplicationOutputData::placeholder(cs); + let observable_output = StorageApplicationOutputData::conditionally_select( + cs, + structured_input.completion_flag, + &observable_output, + &empty_observable_output, + ); + 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 +} diff --git a/crates/zkevm_circuits/src/storage_validity_by_grand_product/input.rs b/crates/zkevm_circuits/src/storage_validity_by_grand_product/input.rs new file mode 100644 index 00000000..84ee7139 --- /dev/null +++ b/crates/zkevm_circuits/src/storage_validity_by_grand_product/input.rs @@ -0,0 +1,136 @@ +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + vm_state::*, +}; +use crate::DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS; +use boojum::cs::{traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::traits::auxiliary::PrettyComparison; +use boojum::{ + gadgets::{ + boolean::Boolean, + num::*, + queue::*, + traits::{ + allocatable::*, encodable::CircuitVarLengthEncodable, selectable::Selectable, + witnessable::WitnessHookable, + }, + u160::*, + u256::*, + u32::*, + u8::*, + }, + serde_utils::BigArraySerde, +}; +use cs_derive::*; +use derivative::*; + +pub const PACKED_KEY_LENGTH: usize = 5 + 8; + +use super::TimestampedStorageLogRecord; + +// FSM + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct StorageDeduplicatorFSMInputOutput { + pub lhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub rhs_accumulator: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + pub current_unsorted_queue_state: QueueState, + pub current_intermediate_sorted_queue_state: QueueState, + pub current_final_sorted_queue_state: QueueState, + pub cycle_idx: UInt32, + pub previous_packed_key: [UInt32; PACKED_KEY_LENGTH], + pub previous_key: UInt256, + pub previous_address: UInt160, + pub previous_timestamp: UInt32, + pub this_cell_has_explicit_read_and_rollback_depth_zero: Boolean, + pub this_cell_base_value: UInt256, + pub this_cell_current_value: UInt256, + pub this_cell_current_depth: UInt32, +} + +impl CSPlaceholder for StorageDeduplicatorFSMInputOutput { + fn placeholder>(cs: &mut CS) -> Self { + let zero_num = Num::::zero(cs); + let zero_u32 = UInt32::zero(cs); + let zero_address = UInt160::zero(cs); + let zero_u256 = UInt256::zero(cs); + let boolean_false = Boolean::allocated_constant(cs, false); + + Self { + lhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + rhs_accumulator: [zero_num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + current_unsorted_queue_state: QueueState::::placeholder(cs), + current_intermediate_sorted_queue_state: + QueueState::::placeholder(cs), + current_final_sorted_queue_state: QueueState::::placeholder(cs), + cycle_idx: zero_u32, + previous_packed_key: [zero_u32; PACKED_KEY_LENGTH], + previous_key: zero_u256, + previous_address: zero_address, + previous_timestamp: zero_u32, + this_cell_has_explicit_read_and_rollback_depth_zero: boolean_false, + this_cell_base_value: zero_u256, + this_cell_current_value: zero_u256, + this_cell_current_depth: zero_u32, + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Debug)] +pub struct StorageDeduplicatorInputData { + pub shard_id_to_process: UInt8, + pub unsorted_log_queue_state: QueueState, + pub intermediate_sorted_queue_state: QueueState, +} + +impl CSPlaceholder for StorageDeduplicatorInputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + shard_id_to_process: UInt8::placeholder(cs), + unsorted_log_queue_state: QueueState::::placeholder(cs), + intermediate_sorted_queue_state: QueueState::::placeholder(cs), + } + } +} + +#[derive(Derivative, CSAllocatable, CSSelectable, CSVarLengthEncodable, WitnessHookable)] +#[derivative(Clone, Copy, Debug)] +#[DerivePrettyComparison("true")] +pub struct StorageDeduplicatorOutputData { + pub final_sorted_queue_state: QueueState, +} + +impl CSPlaceholder for StorageDeduplicatorOutputData { + fn placeholder>(cs: &mut CS) -> Self { + Self { + final_sorted_queue_state: QueueState::::placeholder(cs), + } + } +} + +pub type StorageDeduplicatorInputOutput = crate::fsm_input_output::ClosedFormInput< + F, + StorageDeduplicatorFSMInputOutput, + StorageDeduplicatorInputData, + StorageDeduplicatorOutputData, +>; +pub type StorageDeduplicatorInputOutputWitness = crate::fsm_input_output::ClosedFormInputWitness< + F, + StorageDeduplicatorFSMInputOutput, + StorageDeduplicatorInputData, + StorageDeduplicatorOutputData, +>; + +#[derive(Derivative, serde::Serialize, serde::Deserialize)] +#[derivative(Clone, Debug, Default)] +#[serde(bound = "")] +pub struct StorageDeduplicatorInstanceWitness { + pub closed_form_input: StorageDeduplicatorInputOutputWitness, + pub unsorted_queue_witness: CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, + pub intermediate_sorted_queue_witness: + CircuitQueueRawWitness, 4, LOG_QUERY_PACKED_WIDTH>, +} 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 new file mode 100644 index 00000000..356c481a --- /dev/null +++ b/crates/zkevm_circuits/src/storage_validity_by_grand_product/mod.rs @@ -0,0 +1,1133 @@ +use super::*; + +pub mod input; +#[cfg(test)] +mod test_input; + +use crate::base_structures::log_query::log_query_witness_from_values; +use crate::fsm_input_output::ClosedFormInputCompactForm; + +use crate::base_structures::{ + log_query::{LogQuery, LOG_QUERY_PACKED_WIDTH}, + vm_state::*, +}; +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::traits::cs::DstBuffer; +use boojum::cs::{gates::*, traits::cs::ConstraintSystem, Variable}; +use boojum::field::SmallField; +use boojum::gadgets::traits::castable::WitnessCastable; +use boojum::gadgets::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::{ + boolean::Boolean, + num::Num, + queue::*, + traits::{ + allocatable::*, + encodable::{CircuitEncodable, CircuitEncodableExt}, + selectable::Selectable, + witnessable::*, + }, + u160::*, + u256::*, + u32::UInt32, + u8::UInt8, +}; + +use crate::{ + demux_log_queue::StorageLogQueue, + fsm_input_output::{circuit_inputs::INPUT_OUTPUT_COMMITMENT_LENGTH, *}, + storage_validity_by_grand_product::input::*, +}; + +use crate::utils::accumulate_grand_products; + +// we make a generation aware memory that store all the old and new values +// for a current storage cell. There are largely 3 possible sequences that we must be aware of +// - write_0, .. .... without rollback of the current write +// - write_0, ..., rollback_0, read_0, ... - in this case we must issue and explicit read, even though it's not the first one +// - read_0, ..., - same as above + +// We use extra structure with timestamping. Even though we DO have +// timestamp field in LogQuery, such timestamp is the SAME +// for "forward" and "rollback" items, while we do need to have them +// on different timestamps + +const TIMESTAMPED_STORAGE_LOG_ENCODING_LEN: usize = 20; + +use cs_derive::*; + +use std::sync::Arc; + +#[derive(Derivative, CSAllocatable, WitnessHookable)] +#[derivative(Clone, Debug)] +pub struct TimestampedStorageLogRecord { + pub record: LogQuery, + pub timestamp: UInt32, +} + +pub const EXTENDED_TIMESTAMP_ENCODING_ELEMENT: usize = 19; +pub const EXTENDED_TIMESTAMP_ENCODING_OFFSET: usize = 8; + +impl TimestampedStorageLogRecord { + pub fn append_timestamp_to_raw_query_encoding>( + cs: &mut CS, + original_encoding: &[Variable; TIMESTAMPED_STORAGE_LOG_ENCODING_LEN], + timestamp: &UInt32, + ) -> [Variable; TIMESTAMPED_STORAGE_LOG_ENCODING_LEN] { + debug_assert!(F::CAPACITY_BITS >= 40); + // LogQuery encoding leaves last variable as < 8 bits value + let encoding = Num::linear_combination( + cs, + &[ + ( + // Original encoding at index 19 is only 8 bits + original_encoding[EXTENDED_TIMESTAMP_ENCODING_ELEMENT], + F::ONE, + ), + (timestamp.get_variable(), F::from_u64_unchecked(1u64 << 8)), + ], + ) + .get_variable(); + + let mut result = *original_encoding; + result[EXTENDED_TIMESTAMP_ENCODING_ELEMENT] = encoding; + result + } +} + +impl CircuitEncodable + for TimestampedStorageLogRecord +{ + fn encode>( + &self, + cs: &mut CS, + ) -> [Variable; TIMESTAMPED_STORAGE_LOG_ENCODING_LEN] { + let original_encoding = self.record.encode(cs); + + Self::append_timestamp_to_raw_query_encoding(cs, &original_encoding, &self.timestamp) + } +} + +impl CSAllocatableExt for TimestampedStorageLogRecord { + const INTERNAL_STRUCT_LEN: usize = 37; + + fn witness_from_set_of_values(values: [F; Self::INTERNAL_STRUCT_LEN]) -> Self::Witness { + // NOTE(shamatar) Using CSAllocatableExt causes cyclic dependency here, so we use workaround + let mut record_values = + [F::ZERO; crate::base_structures::log_query::FLATTENED_VARIABLE_LENGTH]; + record_values.copy_from_slice( + &values[..crate::base_structures::log_query::FLATTENED_VARIABLE_LENGTH], + ); + let record = log_query_witness_from_values(record_values); + let other_timestamp: u32 = WitnessCastable::cast_from_source(values[36]); + + Self::Witness { + record, + timestamp: other_timestamp, + } + } + + // we should be able to allocate without knowing values yet + fn create_without_value>(cs: &mut CS) -> Self { + Self { + record: LogQuery::::allocate_without_value(cs), + timestamp: UInt32::allocate_without_value(cs), + } + } + + fn flatten_as_variables(&self) -> [Variable; 37] + where + [(); Self::INTERNAL_STRUCT_LEN]:, + { + let mut result = [Variable::placeholder(); 37]; + let record_variables = self.record.flatten_as_variables_impl(); + result[..crate::base_structures::log_query::FLATTENED_VARIABLE_LENGTH] + .copy_from_slice(&record_variables); + result[crate::base_structures::log_query::FLATTENED_VARIABLE_LENGTH] = + self.timestamp.get_variable(); + assert_no_placeholder_variables(&result); + + result + } + + fn set_internal_variables_values(witness: Self::Witness, dst: &mut DstBuffer<'_, '_, F>) { + LogQuery::set_internal_variables_values(witness.record, dst); + UInt32::set_internal_variables_values(witness.timestamp, dst); + } +} + +impl CircuitEncodableExt + for TimestampedStorageLogRecord +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ +} + +pub fn sort_and_deduplicate_storage_access_entry_point< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, +>( + cs: &mut CS, + closed_form_input: StorageDeduplicatorInstanceWitness, + round_function: &R, + limit: usize, +) -> [Num; INPUT_OUTPUT_COMMITMENT_LENGTH] +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + let structured_input_witness = closed_form_input.closed_form_input; + let unsorted_queue_witness = closed_form_input.unsorted_queue_witness; + let intermediate_sorted_queue_witness = closed_form_input.intermediate_sorted_queue_witness; + + let mut structured_input = StorageDeduplicatorInputOutput::alloc_ignoring_outputs( + cs, + structured_input_witness.clone(), + ); + + let unsorted_queue_from_passthrough_state = QueueState { + head: structured_input + .observable_input + .unsorted_log_queue_state + .head, + tail: structured_input + .observable_input + .unsorted_log_queue_state + .tail, + }; + + let unsorted_queue_from_fsm_input_state = QueueState { + head: structured_input + .hidden_fsm_input + .current_unsorted_queue_state + .head, + tail: structured_input + .hidden_fsm_input + .current_unsorted_queue_state + .tail, + }; + + // passthrought must be trivial + unsorted_queue_from_passthrough_state.enforce_trivial_head(cs); + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &unsorted_queue_from_passthrough_state, + &unsorted_queue_from_fsm_input_state, + ); + let mut unsorted_queue = StorageLogQueue::::from_state(cs, state); + + unsorted_queue.witness = Arc::new(CircuitQueueWitness::from_inner_witness( + unsorted_queue_witness, + )); + + // same logic from sorted + let intermediate_sorted_queue_from_passthrough = CircuitQueue::< + F, + TimestampedStorageLogRecord, + 8, + 12, + 4, + QUEUE_STATE_WIDTH, + TIMESTAMPED_STORAGE_LOG_ENCODING_LEN, + R, + >::from_raw_parts( + cs, + structured_input + .observable_input + .intermediate_sorted_queue_state + .head, + structured_input + .observable_input + .intermediate_sorted_queue_state + .tail + .tail, + structured_input + .observable_input + .intermediate_sorted_queue_state + .tail + .length, + ); + + let intermediate_sorted_queue_from_fsm_input = CircuitQueue::< + F, + TimestampedStorageLogRecord, + 8, + 12, + 4, + QUEUE_STATE_WIDTH, + TIMESTAMPED_STORAGE_LOG_ENCODING_LEN, + R, + >::from_raw_parts( + cs, + structured_input + .hidden_fsm_input + .current_intermediate_sorted_queue_state + .head, + structured_input + .hidden_fsm_input + .current_intermediate_sorted_queue_state + .tail + .tail, + structured_input + .hidden_fsm_input + .current_intermediate_sorted_queue_state + .tail + .length, + ); + + // passthrought must be trivial + intermediate_sorted_queue_from_passthrough.enforce_trivial_head(cs); + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &intermediate_sorted_queue_from_passthrough.into_state(), + &intermediate_sorted_queue_from_fsm_input.into_state(), + ); + let mut intermediate_sorted_queue = CircuitQueue::< + F, + TimestampedStorageLogRecord, + 8, + 12, + 4, + QUEUE_STATE_WIDTH, + TIMESTAMPED_STORAGE_LOG_ENCODING_LEN, + R, + >::from_state(cs, state); + + intermediate_sorted_queue.witness = Arc::new(CircuitQueueWitness::from_inner_witness( + intermediate_sorted_queue_witness, + )); + + // for final sorted queue it's easier + + let empty_final_sorted_queue = StorageLogQueue::::empty(cs); + let final_sorted_queue_from_fsm_input = StorageLogQueue::::from_raw_parts( + cs, + structured_input + .hidden_fsm_input + .current_final_sorted_queue_state + .head, + structured_input + .hidden_fsm_input + .current_final_sorted_queue_state + .tail + .tail, + structured_input + .hidden_fsm_input + .current_final_sorted_queue_state + .tail + .length, + ); + + let state = QueueState::conditionally_select( + cs, + structured_input.start_flag, + &empty_final_sorted_queue.into_state(), + &final_sorted_queue_from_fsm_input.into_state(), + ); + let mut final_sorted_queue = StorageLogQueue::::from_state(cs, state); + + // get challenges for permutation argument + let challenges = crate::utils::produce_fs_challenges::< + F, + CS, + R, + QUEUE_STATE_WIDTH, + { TIMESTAMPED_STORAGE_LOG_ENCODING_LEN + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + structured_input + .observable_input + .unsorted_log_queue_state + .tail, + structured_input + .observable_input + .intermediate_sorted_queue_state + .tail, + round_function, + ); + + let one = Num::allocated_constant(cs, F::ONE); + let initial_lhs = + <[Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]>::conditionally_select( + cs, + structured_input.start_flag, + &[one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &structured_input.hidden_fsm_input.lhs_accumulator, + ); + + let initial_rhs = + <[Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]>::conditionally_select( + cs, + structured_input.start_flag, + &[one; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + &structured_input.hidden_fsm_input.rhs_accumulator, + ); + + let zero_u32: UInt32 = UInt32::zero(cs); + + // there is no code at address 0 in our case, so we can formally use it for all the purposes + let previous_packed_key = <[UInt32; PACKED_KEY_LENGTH]>::conditionally_select( + cs, + structured_input.start_flag, + &[zero_u32; PACKED_KEY_LENGTH], + &structured_input.hidden_fsm_input.previous_packed_key, + ); + + let cycle_idx = UInt32::conditionally_select( + cs, + structured_input.start_flag, + &zero_u32, + &structured_input.hidden_fsm_input.cycle_idx, + ); + + let shard_id = structured_input.observable_input.shard_id_to_process; + + let ( + new_lhs, + new_rhs, + cycle_idx, + previous_packed_key, + previous_key, + previous_address, + previous_timestamp, + this_cell_has_explicit_read_and_rollback_depth_zero, + this_cell_base_value, + this_cell_current_value, + this_cell_current_depth, + ) = sort_and_deduplicate_storage_access_inner( + cs, + initial_lhs, + initial_rhs, + &mut unsorted_queue, + &mut intermediate_sorted_queue, + &mut final_sorted_queue, + structured_input.start_flag, + cycle_idx, + challenges, + previous_packed_key, + structured_input.hidden_fsm_input.previous_key, + structured_input.hidden_fsm_input.previous_address, + structured_input.hidden_fsm_input.previous_timestamp, + structured_input + .hidden_fsm_input + .this_cell_has_explicit_read_and_rollback_depth_zero, + structured_input.hidden_fsm_input.this_cell_base_value, + structured_input.hidden_fsm_input.this_cell_current_value, + structured_input.hidden_fsm_input.this_cell_current_depth, + shard_id, + limit, + ); + + unsorted_queue.enforce_consistency(cs); + intermediate_sorted_queue.enforce_consistency(cs); + + let unsorted_is_empty = unsorted_queue.is_empty(cs); + let sorted_is_empty = intermediate_sorted_queue.is_empty(cs); + + Boolean::enforce_equal(cs, &unsorted_is_empty, &sorted_is_empty); + + let completed = unsorted_is_empty.and(cs, sorted_is_empty); + new_lhs.iter().zip(new_rhs).for_each(|(l, r)| { + Num::conditionally_enforce_equal(cs, completed, &l, &r); + }); + + // form the input/output + + structured_input.hidden_fsm_output.cycle_idx = cycle_idx; + structured_input.hidden_fsm_output.previous_packed_key = previous_packed_key; + structured_input.hidden_fsm_output.previous_key = previous_key; + structured_input.hidden_fsm_output.previous_address = previous_address; + structured_input.hidden_fsm_output.previous_timestamp = previous_timestamp; + structured_input + .hidden_fsm_output + .this_cell_has_explicit_read_and_rollback_depth_zero = + this_cell_has_explicit_read_and_rollback_depth_zero; + structured_input.hidden_fsm_output.this_cell_base_value = this_cell_base_value; + structured_input.hidden_fsm_output.this_cell_current_value = this_cell_current_value; + structured_input.hidden_fsm_output.this_cell_current_depth = this_cell_current_depth; + + structured_input.hidden_fsm_output.lhs_accumulator = new_lhs; + structured_input.hidden_fsm_output.rhs_accumulator = new_rhs; + + structured_input + .hidden_fsm_output + .current_unsorted_queue_state = unsorted_queue.into_state(); + structured_input + .hidden_fsm_output + .current_intermediate_sorted_queue_state = intermediate_sorted_queue.into_state(); + + structured_input.completion_flag = completed; + + let empty_queue_state = QueueState::empty(cs); + let state = QueueState::conditionally_select( + cs, + completed, + &final_sorted_queue.into_state(), + &empty_queue_state, + ); + let final_queue_for_observable_output = CircuitQueue::< + F, + LogQuery, + 8, + 12, + 4, + QUEUE_STATE_WIDTH, + LOG_QUERY_PACKED_WIDTH, + R, + >::from_state(cs, state); + + structured_input.observable_output.final_sorted_queue_state = + final_queue_for_observable_output.into_state(); + + structured_input + .hidden_fsm_output + .current_final_sorted_queue_state = final_sorted_queue.into_state(); + + structured_input.hook_compare_witness(cs, &structured_input_witness); + + let compact_form = + ClosedFormInputCompactForm::from_full_form(cs, &structured_input, round_function); + + // dbg!(compact_form.create_witness()); + let input_committment = + commit_variable_length_encodable_item(cs, &compact_form, round_function); + for el in input_committment.iter() { + let gate = PublicInputGate::new(el.get_variable()); + gate.add_to_cs(cs); + } + + input_committment +} + +pub const NUM_PERMUTATION_ARG_CHALLENGES: usize = TIMESTAMPED_STORAGE_LOG_ENCODING_LEN + 1; + +pub fn sort_and_deduplicate_storage_access_inner< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction, +>( + cs: &mut CS, + mut lhs: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + mut rhs: [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + original_queue: &mut StorageLogQueue, + intermediate_sorted_queue: &mut CircuitQueue< + F, + TimestampedStorageLogRecord, + 8, + 12, + 4, + QUEUE_STATE_WIDTH, + TIMESTAMPED_STORAGE_LOG_ENCODING_LEN, + R, + >, + sorted_queue: &mut StorageLogQueue, + is_start: Boolean, + mut cycle_idx: UInt32, + fs_challenges: [[Num; TIMESTAMPED_STORAGE_LOG_ENCODING_LEN + 1]; + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + mut previous_packed_key: [UInt32; PACKED_KEY_LENGTH], + mut previous_key: UInt256, + mut previous_address: UInt160, + mut previous_timestamp: UInt32, + mut this_cell_has_explicit_read_and_rollback_depth_zero: Boolean, + mut this_cell_base_value: UInt256, + mut this_cell_current_value: UInt256, + mut this_cell_current_depth: UInt32, + shard_id_to_process: UInt8, + limit: usize, +) -> ( + [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + [Num; DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS], + UInt32, + [UInt32; PACKED_KEY_LENGTH], + UInt256, + UInt160, + UInt32, + Boolean, + UInt256, + UInt256, + UInt32, +) +where + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, + [(); as CSAllocatableExt>::INTERNAL_STRUCT_LEN]:, +{ + assert!(limit <= u32::MAX as usize); + + let unsorted_queue_length = Num::from_variable(original_queue.length.get_variable()); + let intermediate_sorted_queue_length = + Num::from_variable(intermediate_sorted_queue.length.get_variable()); + + Num::enforce_equal( + cs, + &unsorted_queue_length, + &intermediate_sorted_queue_length, + ); + + // we can recreate it here, there are two cases: + // - we are 100% empty, but it's the only circuit in this case + // - otherwise we continue, and then it's not trivial + let no_work = original_queue.is_empty(cs); + let mut previous_item_is_trivial = no_work.or(cs, is_start); + + // we simultaneously pop, accumulate partial product, + // and decide whether or not we should move to the next cell + + // to ensure uniqueness we place timestamps in a addition to the original values encoding access location + + for _cycle in 0..limit { + let original_timestamp = cycle_idx; + // increment it immediatelly + unsafe { + let new_cycle_idx = cycle_idx.increment_unchecked(cs); + cycle_idx = new_cycle_idx; + } + + let original_is_empty = original_queue.is_empty(cs); + let sorted_is_empty = intermediate_sorted_queue.is_empty(cs); + Boolean::enforce_equal(cs, &original_is_empty, &sorted_is_empty); + + let original_is_not_empty = original_is_empty.negated(cs); + let sorted_is_not_empty = sorted_is_empty.negated(cs); + + let should_pop = Boolean::multi_and(cs, &[original_is_not_empty, sorted_is_not_empty]); + let item_is_trivial = original_is_empty; + + // NOTE: we do not need to check shard_id of unsorted item because we can just check it on sorted item + let (_, original_encoding) = original_queue.pop_front(cs, should_pop); + let (sorted_item, sorted_encoding) = intermediate_sorted_queue.pop_front(cs, should_pop); + let extended_original_encoding = + TimestampedStorageLogRecord::append_timestamp_to_raw_query_encoding( + cs, + &original_encoding, + &original_timestamp, + ); + + let shard_id_is_valid = + UInt8::equals(cs, &shard_id_to_process, &sorted_item.record.shard_id); + shard_id_is_valid.conditionally_enforce_true(cs, should_pop); + + accumulate_grand_products::< + F, + CS, + TIMESTAMPED_STORAGE_LOG_ENCODING_LEN, + { TIMESTAMPED_STORAGE_LOG_ENCODING_LEN + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + &mut lhs, + &mut rhs, + &fs_challenges, + &extended_original_encoding, + &sorted_encoding, + should_pop, + ); + + let TimestampedStorageLogRecord { record, timestamp } = sorted_item; + + // now resolve a logic about sorting itself + let packed_key = concatenate_key(cs, (record.address.clone(), record.key)); + + // ensure sorting. Check that previous key < this key + let (keys_are_equal, previous_key_is_greater) = + unpacked_long_comparison(cs, &previous_packed_key, &packed_key); + + let not_item_is_trivial = item_is_trivial.negated(cs); + previous_key_is_greater.conditionally_enforce_false(cs, not_item_is_trivial); + + // if keys are the same then timestamps are sorted + let (_, previous_timestamp_is_less) = previous_timestamp.overflowing_sub(cs, timestamp); + // enforce if keys are the same and not trivial + let must_enforce = keys_are_equal.and(cs, not_item_is_trivial); + previous_timestamp_is_less.conditionally_enforce_true(cs, must_enforce); + + // we follow the procedure: + // if keys are different then we finish with a previous one and update parameters + // else we just update parameters + + // if new cell + { + 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); + } + // 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), + // and if current rollback depth is 0 then we MUST issue a read + + let value_is_unchanged = + UInt256::equals(cs, &this_cell_current_value, &this_cell_base_value); + // there may be a situation when as a result of sequence of writes + // storage slot is CLAIMED to be unchanged. There are two options: + // - unchanged because we had write - ... - rollback AND we do not have read at depth 0. + // In this case we used a temporary value, and the fact that the last action is rollback + // all the way to the start (to depth 0), we are not interested in what was an initial value + // - unchanged because a -> write b -> ... -> write a AND we do or do not have read at depth 0. + // In this case we would not need to write IF prover is honest and provides a true witness to "read value" + // field at the first write. But we can not rely on this and have to check this fact! + let current_depth_is_zero = this_cell_current_depth.is_zero(cs); + let not_current_depth_is_zero = current_depth_is_zero.negated(cs); + let unchanged_but_not_by_rollback = + value_is_unchanged.and(cs, not_current_depth_is_zero); + let issue_protective_read = this_cell_has_explicit_read_and_rollback_depth_zero + .or(cs, unchanged_but_not_by_rollback); + let should_write = value_is_unchanged.negated(cs); + + let query = LogQuery { + address: previous_address, + key: previous_key, + read_value: this_cell_base_value, + written_value: this_cell_current_value, + rw_flag: should_write, + aux_byte: UInt8::zero(cs), + rollback: Boolean::allocated_constant(cs, false), + is_service: Boolean::allocated_constant(cs, false), + shard_id: shard_id_to_process, + tx_number_in_block: UInt32::zero(cs), + timestamp: UInt32::zero(cs), + }; + + // if we did only writes and rollbacks then we don't need to update + let should_update = issue_protective_read.or(cs, should_write); + let not_keys_are_equal_and_should_update = not_keys_are_equal.and(cs, should_update); + let should_push = previous_item_is_trivial + .negated(cs) + .and(cs, not_keys_are_equal_and_should_update); + + sorted_queue.push(cs, query, should_push); + + let new_non_trivial_cell = item_is_trivial.negated(cs).and(cs, not_keys_are_equal); + + // and update as we switch to the new cell with extra logic + let meaningful_value = UInt256::conditionally_select( + cs, + record.rw_flag, + &record.written_value, + &record.read_value, + ); + + // re-update + this_cell_base_value = UInt256::conditionally_select( + cs, + new_non_trivial_cell, + &record.read_value, + &this_cell_base_value, + ); + + this_cell_current_value = UInt256::conditionally_select( + cs, + new_non_trivial_cell, + &meaningful_value, + &this_cell_current_value, + ); + + let one = UInt32::allocated_constant(cs, 1); + let zero = UInt32::zero(cs); + let rollback_depth_for_new_cell = + UInt32::conditionally_select(cs, record.rw_flag, &one, &zero); + + this_cell_current_depth = UInt32::conditionally_select( + cs, + new_non_trivial_cell, + &rollback_depth_for_new_cell, + &this_cell_current_depth, + ); + + // we have new non-trivial + // and if it's read then it's definatelly at depth 0 + let not_rw_flag = record.rw_flag.negated(cs); + this_cell_has_explicit_read_and_rollback_depth_zero = Boolean::conditionally_select( + cs, + new_non_trivial_cell, + ¬_rw_flag, + &this_cell_has_explicit_read_and_rollback_depth_zero, + ); + } + + // if same cell - update + { + let not_rw_flag = record.rw_flag.negated(cs); + let non_trivial_and_same_cell = item_is_trivial.negated(cs).and(cs, keys_are_equal); + let non_trivial_read_of_same_cell = non_trivial_and_same_cell.and(cs, not_rw_flag); + let non_trivial_write_of_same_cell = non_trivial_and_same_cell.and(cs, record.rw_flag); + let not_rollback = record.rollback.negated(cs); + let write_no_rollback = non_trivial_write_of_same_cell.and(cs, not_rollback); + let write_rollback = non_trivial_write_of_same_cell.and(cs, record.rollback); + + // update rollback depth the is a result of this action + unsafe { + let incremented_depth = this_cell_current_depth.increment_unchecked(cs); + this_cell_current_depth = UInt32::conditionally_select( + cs, + write_no_rollback, + &incremented_depth, + &this_cell_current_depth, + ); + let decremented_depth = this_cell_current_depth.decrement_unchecked(cs); + this_cell_current_depth = UInt32::conditionally_select( + cs, + write_rollback, + &decremented_depth, + &this_cell_current_depth, + ); + } + + // check consistency + let read_is_equal_to_current = + UInt256::equals(cs, &this_cell_current_value, &record.read_value); + // we ALWAYS ensure read consistency on write (but not rollback) and on plain read + let check_read_consistency = + Boolean::multi_or(cs, &[non_trivial_read_of_same_cell, write_no_rollback]); + read_is_equal_to_current.conditionally_enforce_true(cs, check_read_consistency); + + // decide to update + this_cell_current_value = UInt256::conditionally_select( + cs, + write_no_rollback, + &record.written_value, + &this_cell_current_value, + ); + + this_cell_current_value = UInt256::conditionally_select( + cs, + write_rollback, + &record.read_value, + &this_cell_current_value, + ); + + let current_rollback_depth_is_zero = this_cell_current_depth.is_zero(cs); + let read_at_rollback_depth_zero_of_same_cell = + current_rollback_depth_is_zero.and(cs, non_trivial_read_of_same_cell); + + this_cell_base_value = UInt256::conditionally_select( + cs, + read_at_rollback_depth_zero_of_same_cell, + &record.read_value, + &this_cell_base_value, + ); + + // we definately read non-trivial, and that is on depth 0, so set to true + let constant_true = Boolean::allocated_constant(cs, true); + this_cell_has_explicit_read_and_rollback_depth_zero = Boolean::conditionally_select( + cs, + read_at_rollback_depth_zero_of_same_cell, + &constant_true, + &this_cell_has_explicit_read_and_rollback_depth_zero, + ); + } + + // always update counters + previous_address = record.address; + previous_key = record.key; + previous_item_is_trivial = item_is_trivial; + previous_timestamp = timestamp; + previous_packed_key = packed_key; + } + + // finalization step - out of cycle, and only if we are done just yet + { + let queues_exhausted = original_queue.is_empty(cs); + + // cell state is final + let value_is_unchanged = + UInt256::equals(cs, &this_cell_current_value, &this_cell_base_value); + let current_depth_is_zero = this_cell_current_depth.is_zero(cs); + let not_current_depth_is_zero = current_depth_is_zero.negated(cs); + let unchanged_but_not_by_rollback = value_is_unchanged.and(cs, not_current_depth_is_zero); + let issue_protective_read = this_cell_has_explicit_read_and_rollback_depth_zero + .or(cs, unchanged_but_not_by_rollback); + let should_write = value_is_unchanged.negated(cs); + + let query = LogQuery { + address: previous_address, + key: previous_key, + read_value: this_cell_base_value, + written_value: this_cell_current_value, + rw_flag: should_write, + aux_byte: UInt8::zero(cs), + rollback: Boolean::allocated_constant(cs, false), + is_service: Boolean::allocated_constant(cs, false), + shard_id: shard_id_to_process, + tx_number_in_block: UInt32::zero(cs), + timestamp: UInt32::zero(cs), + }; + + // if we did only writes and rollbacks then we don't need to update + let should_update = issue_protective_read.or(cs, should_write); + let should_update_and_queues_exhausted = should_update.and(cs, queues_exhausted); + let should_push = previous_item_is_trivial + .negated(cs) + .and(cs, should_update_and_queues_exhausted); + + sorted_queue.push(cs, query, should_push); + + // reset flag to match simple witness generation convensions + let constant_false = Boolean::allocated_constant(cs, false); + this_cell_has_explicit_read_and_rollback_depth_zero = Boolean::conditionally_select( + cs, + queues_exhausted, + &constant_false, + &this_cell_has_explicit_read_and_rollback_depth_zero, + ); + } + + // output our FSM values + + ( + lhs, + rhs, + cycle_idx, + previous_packed_key, + previous_key, + previous_address, + previous_timestamp, + this_cell_has_explicit_read_and_rollback_depth_zero, + this_cell_base_value, + this_cell_current_value, + this_cell_current_depth, + ) +} + +fn concatenate_key>( + _cs: &mut CS, + key_tuple: (UInt160, UInt256), +) -> [UInt32; PACKED_KEY_LENGTH] { + // LE packing so comparison is subtraction + let (address, key) = key_tuple; + [ + key.inner[0], + key.inner[1], + key.inner[2], + key.inner[3], + key.inner[4], + key.inner[5], + key.inner[6], + key.inner[7], + address.inner[0], + address.inner[1], + address.inner[2], + address.inner[3], + address.inner[4], + ] +} + +/// Check that a == b and a > b by performing a long subtraction b - a with borrow. +/// Both a and b are considered as least significant word first +#[track_caller] +pub fn unpacked_long_comparison, const N: usize>( + cs: &mut CS, + a: &[UInt32; N], + b: &[UInt32; N], +) -> (Boolean, Boolean) { + let boolean_false = Boolean::allocated_constant(cs, false); + let mut equals = [boolean_false; N]; + let mut borrow = boolean_false; + + for i in 0..N { + let (diff, new_borrow) = b[i].overflowing_sub_with_borrow_in(cs, a[i], borrow); + borrow = new_borrow; + equals[i] = diff.is_zero(cs); + } + + let equal = Boolean::multi_and(cs, &equals); + let a_is_greater = borrow; + + (equal, a_is_greater) +} + +#[cfg(test)] +mod tests { + use super::*; + use boojum::algebraic_props::poseidon2_parameters::Poseidon2GoldilocksExternalMatrix; + use boojum::cs::implementations::reference_cs::{ + CSDevelopmentAssembly, CSReferenceImplementation, + }; + use boojum::cs::traits::gate::GatePlacementStrategy; + use boojum::cs::CSGeometry; + // use boojum::cs::EmptyToolbox; + use boojum::cs::*; + use boojum::field::goldilocks::GoldilocksField; + use boojum::gadgets::tables::*; + use boojum::implementations::poseidon2::Poseidon2Goldilocks; + use boojum::worker::Worker; + use ethereum_types::{Address, U256}; + + type F = GoldilocksField; + type P = GoldilocksField; + + use boojum::config::*; + use boojum::cs::cs_builder::*; + + fn configure< + T: CsBuilderImpl, + GC: GateConfigurationHolder, + TB: StaticToolboxHolder, + >( + builder: CsBuilder, + ) -> CsBuilder, impl StaticToolboxHolder> { + let owned_cs = builder; + let owned_cs = owned_cs.allow_lookup( + LookupParameters::UseSpecializedColumnsWithTableIdAsConstant { + width: 3, + num_repetitions: 8, + share_table_id: true, + }, + ); + let owned_cs = ConstantsAllocatorGate::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = FmaGateInBaseFieldWithoutConstant::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = ReductionGate::::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = BooleanConstraintGate::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = UIntXAddGate::<32>::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = UIntXAddGate::<16>::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = SelectionGate::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = ZeroCheckGate::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + false, + ); + let owned_cs = DotProductGate::<4>::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = + MatrixMultiplicationGate::::configure_builder( + owned_cs, + GatePlacementStrategy::UseGeneralPurposeColumns, + ); + let owned_cs = + NopGate::configure_builder(owned_cs, GatePlacementStrategy::UseGeneralPurposeColumns); + + owned_cs + } + + #[test] + fn test_storage_validity_circuit() { + let geometry = CSGeometry { + num_columns_under_copy_permutation: 100, + num_witness_columns: 0, + num_constant_columns: 8, + max_allowed_constraint_degree: 4, + }; + + use boojum::config::DevCSConfig; + use boojum::cs::cs_builder_reference::*; + + let builder_impl = + CsReferenceImplementationBuilder::::new(geometry, 1 << 26, 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(()); + + // add tables + let table = create_xor8_table(); + owned_cs.add_lookup_table::(table); + + let cs = &mut owned_cs; + + let lhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + let rhs = [Num::allocated_constant(cs, F::from_nonreduced_u64(1)); + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS]; + + let execute = Boolean::allocated_constant(cs, true); + let mut original_queue = StorageLogQueue::::empty(cs); + let unsorted_input = test_input::generate_test_input_unsorted(cs); + for el in unsorted_input { + original_queue.push(cs, el, execute); + } + + let mut intermediate_sorted_queue = CircuitQueue::empty(cs); + let sorted_input = test_input::generate_test_input_sorted(cs); + for el in sorted_input { + intermediate_sorted_queue.push(cs, el, execute); + } + + let mut sorted_queue = StorageLogQueue::empty(cs); + + let is_start = Boolean::allocated_constant(cs, true); + let cycle_idx = UInt32::allocated_constant(cs, 0); + let round_function = Poseidon2Goldilocks; + let fs_challenges = crate::utils::produce_fs_challenges::< + F, + _, + Poseidon2Goldilocks, + QUEUE_STATE_WIDTH, + { TIMESTAMPED_STORAGE_LOG_ENCODING_LEN + 1 }, + DEFAULT_NUM_PERMUTATION_ARGUMENT_REPETITIONS, + >( + cs, + original_queue.into_state().tail, + intermediate_sorted_queue.into_state().tail, + &round_function, + ); + let previous_packed_key = [UInt32::allocated_constant(cs, 0); PACKED_KEY_LENGTH]; + let previous_key = UInt256::allocated_constant(cs, U256::default()); + let previous_address = UInt160::allocated_constant(cs, Address::default()); + let previous_timestamp = UInt32::allocated_constant(cs, 0); + let this_cell_has_explicit_read_and_rollback_depth_zero = + Boolean::allocated_constant(cs, false); + let this_cell_base_value = UInt256::allocated_constant(cs, U256::default()); + let this_cell_current_value = UInt256::allocated_constant(cs, U256::default()); + let this_cell_current_depth = UInt32::allocated_constant(cs, 0); + let shard_id_to_process = UInt8::allocated_constant(cs, 0); + let limit = 16; + + sort_and_deduplicate_storage_access_inner( + cs, + lhs, + rhs, + &mut original_queue, + &mut intermediate_sorted_queue, + &mut sorted_queue, + is_start, + cycle_idx, + fs_challenges, + previous_packed_key, + previous_key, + previous_address, + previous_timestamp, + this_cell_has_explicit_read_and_rollback_depth_zero, + this_cell_base_value, + this_cell_current_value, + this_cell_current_depth, + shard_id_to_process, + limit, + ); + + cs.pad_and_shrink(); + let worker = Worker::new(); + 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/test_input.rs b/crates/zkevm_circuits/src/storage_validity_by_grand_product/test_input.rs new file mode 100644 index 00000000..fba49703 --- /dev/null +++ b/crates/zkevm_circuits/src/storage_validity_by_grand_product/test_input.rs @@ -0,0 +1,632 @@ +use super::TimestampedStorageLogRecord; +use crate::base_structures::log_query::LogQuery; +use crate::ethereum_types::{Address, U256}; +use boojum::cs::gates::{assert_no_placeholder_variables, assert_no_placeholders}; +use boojum::cs::traits::cs::ConstraintSystem; +use boojum::field::goldilocks::GoldilocksField; +use boojum::gadgets::{boolean::Boolean, u160::*, u256::*, u32::*, u8::*}; + +type F = GoldilocksField; + +// This witness input is generated from the old test harness, and remodeled to work in the current type system. +pub fn generate_test_input_unsorted>(cs: &mut CS) -> Vec> { + let mut queries = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + let zero_8 = UInt8::allocated_constant(cs, 0); + let zero_32 = UInt32::allocated_constant(cs, 0); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1205), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("1").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1425), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1609), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("7").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1777), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 1969), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("5").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2253), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("10").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2357), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2429), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("4").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2681), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("9").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2797), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("9").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2829), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2901), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("3").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3089), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("8").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3193), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32770)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("32779").unwrap()), + read_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + written_value: UInt256::allocated_constant( + cs, + U256::from_dec_str( + "452319300877325313852488925888724764263521004047156906617735320131041551860", + ) + .unwrap(), + ), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3265), + }; + queries.push(q); + + let q = LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32779)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("2").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3421), + }; + queries.push(q); + + queries +} + +pub fn generate_test_input_sorted>( + cs: &mut CS, +) -> Vec> { + let mut records = vec![]; + let bool_false = Boolean::allocated_constant(cs, false); + let bool_true = Boolean::allocated_constant(cs, true); + let zero_8 = UInt8::allocated_constant(cs, 0); + let zero_32 = UInt32::allocated_constant(cs, 0); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 27), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("2").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 4785), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 28), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("2").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 4817), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 22), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("3").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 4317), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 25), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("4").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 4721), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 26), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("4").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 4753), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 31), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("5").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 5177), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 19), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("6").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3929), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 16), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("7").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3525), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 13), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("8").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 3193), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 9), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("9").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2797), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 10), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("9").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2829), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 6), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("10").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 2357), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 32), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("11").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 5197), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 35), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("12").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 7093), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 36), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("12").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("0").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("1").unwrap()), + rw_flag: bool_true, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 7129), + }, + }; + records.push(r); + + let r = TimestampedStorageLogRecord:: { + timestamp: UInt32::allocated_constant(cs, 38), + record: LogQuery:: { + address: UInt160::allocated_constant(cs, Address::from_low_u64_le(32769)), + key: UInt256::allocated_constant(cs, U256::from_dec_str("13").unwrap()), + read_value: UInt256::allocated_constant(cs, U256::from_dec_str("1").unwrap()), + written_value: UInt256::allocated_constant(cs, U256::from_dec_str("1").unwrap()), + rw_flag: bool_false, + aux_byte: zero_8, + rollback: bool_false, + is_service: bool_false, + shard_id: zero_8, + tx_number_in_block: zero_32, + timestamp: UInt32::allocated_constant(cs, 7177), + }, + }; + records.push(r); + + records +} diff --git a/crates/zkevm_circuits/src/tables/bitshift.rs b/crates/zkevm_circuits/src/tables/bitshift.rs new file mode 100644 index 00000000..18f71e04 --- /dev/null +++ b/crates/zkevm_circuits/src/tables/bitshift.rs @@ -0,0 +1,40 @@ +use super::*; +use crate::ethereum_types::U256; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; + +pub const VM_SHIFT_TO_NUM_CONVERTER_TABLE_NAME: &'static str = "Shift to num converter table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BitshiftTable; + +pub fn create_shift_to_num_converter_table() -> LookupTable { + // there are 256 possible shifts and 8 32-bit limbs in any 256-bit register + // we give the value of two limbs per row, so the total number of rows in the table is: + // 256 * 8/2 = 256 * 4 = 1024 + let num_rows = 1024; + let mut all_keys = Vec::with_capacity(num_rows); + + for shift in 0..256 { + let mut modulus = U256::from(1u64) << shift; + let mut idx = 0; + while idx < 4 { + let x = F::from_u64((shift + (idx << 8)) as u64).unwrap(); + let y = F::from_u64(modulus.low_u32() as u64).unwrap(); + modulus >>= 32; + let z = F::from_u64(modulus.low_u32() as u64).unwrap(); + modulus >>= 32; + idx += 1; + + let row = [x, y, z]; + all_keys.push(row); + } + } + + LookupTable::new_from_content( + all_keys, + VM_SHIFT_TO_NUM_CONVERTER_TABLE_NAME.to_string(), + 1, + ) +} diff --git a/crates/zkevm_circuits/src/tables/conditional.rs b/crates/zkevm_circuits/src/tables/conditional.rs new file mode 100644 index 00000000..6351100c --- /dev/null +++ b/crates/zkevm_circuits/src/tables/conditional.rs @@ -0,0 +1,58 @@ +use super::*; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; + +pub const FLAGS_PACKED_ENCODING_BIT_WIDTH: usize = 3; + +pub(crate) fn integer_into_flags(encoding: u8) -> (bool, bool, bool) { + ( + (encoding & 0x1) != 0, + ((encoding & 0x2) != 0), + ((encoding & 0x4) != 0), + ) +} + +pub const VM_CONDITIONAL_RESOLUTION_TABLE_NAME: &'static str = "Conditional resolution table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct VMConditionalResolutionTable; + +pub fn create_conditionals_resolution_table() -> LookupTable { + let num_rows = 8 * 8; + + let mut all_keys = Vec::with_capacity(num_rows); + + let all_conditions = zkevm_opcode_defs::ALL_CONDITIONS; + use zkevm_opcode_defs::Condition; + for condition in all_conditions.iter() { + let x = condition.variant_index(); // integer encoding + for i in 0..(1 << FLAGS_PACKED_ENCODING_BIT_WIDTH) { + let (of, eq, gt) = integer_into_flags(i as u8); + let resolution = match condition { + Condition::Always => true, + Condition::Lt => of, + Condition::Eq => eq, + Condition::Gt => gt, + Condition::Ge => gt || eq, + Condition::Le => of || eq, + Condition::Ne => !eq, + Condition::GtOrLt => gt || of, + }; + + let row = [ + F::from_u64(x as u64).unwrap(), + F::from_u64(i as u64).unwrap(), + F::from_u64(resolution as u64).unwrap(), + ]; + + all_keys.push(row); + } + } + + LookupTable::new_from_content( + all_keys, + VM_CONDITIONAL_RESOLUTION_TABLE_NAME.to_string(), + 2, + ) +} diff --git a/crates/zkevm_circuits/src/tables/integer_to_boolean_mask.rs b/crates/zkevm_circuits/src/tables/integer_to_boolean_mask.rs new file mode 100644 index 00000000..83caf643 --- /dev/null +++ b/crates/zkevm_circuits/src/tables/integer_to_boolean_mask.rs @@ -0,0 +1,91 @@ +use super::*; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; + +pub const REG_IDX_TO_BITMASK_TABLE_NAME: &'static str = "Register index to bitmask table"; +pub const UMA_SHIFT_TO_BITMASK_TABLE_NAME: &'static str = "UMA shift to bitmask table"; +pub const VM_SUBPC_TO_BITMASK_TABLE_NAME: &'static str = "Sub PC to bitmask table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RegisterIndexToBitmaskTable; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UMAShiftToBitmaskTable; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct VMSubPCToBitmaskTable; + +pub fn create_integer_to_bitmask_table( + num_bits: usize, + name: &'static str, +) -> LookupTable { + assert!(num_bits <= 16); + let mut all_keys = Vec::with_capacity(1 << num_bits); + for integer in 0..(1u64 << num_bits) { + let key = smallvec::smallvec![F::from_u64_unchecked(integer as u64)]; + all_keys.push(key); + } + + LookupTable::new_from_keys_and_generation_function(&all_keys, name.to_string(), 1, |keys| { + let a = keys[0].as_u64_reduced(); + + let result = if a == 0 { + 0u64 + } else { + 1u64 << (a - 1) // 1 in some position + }; + + smallvec::smallvec![F::from_u64_unchecked(result), F::ZERO] + }) +} + +pub fn create_integer_set_ith_bit_table( + num_bits: usize, + name: &'static str, +) -> LookupTable { + assert!(num_bits <= 16); + let mut all_keys = Vec::with_capacity(1 << num_bits); + for integer in 0..(1u64 << num_bits) { + let key = smallvec::smallvec![F::from_u64_unchecked(integer as u64)]; + all_keys.push(key); + } + + LookupTable::new_from_keys_and_generation_function(&all_keys, name.to_string(), 1, |keys| { + let a = keys[0].as_u64_reduced(); + + let result = 1u64 << a; // 1 in some position + + smallvec::smallvec![F::from_u64_unchecked(result), F::ZERO] + }) +} + +pub fn create_subpc_bitmask_table() -> LookupTable { + create_integer_to_bitmask_table(2, VM_SUBPC_TO_BITMASK_TABLE_NAME) + + // let num_bits = 2; + // let mut all_keys = Vec::with_capacity(1 << num_bits); + // for integer in 0..(1u64 << num_bits) { + // let key = smallvec::smallvec![F::from_u64_unchecked(integer as u64)]; + // all_keys.push(key); + // } + + // LookupTable::new_from_keys_and_generation_function( + // &all_keys, + // VM_SUBPC_TO_BITMASK_TABLE_NAME.to_string(), + // 1, + // |keys| { + // let a = keys[0].as_u64_reduced(); + + // let result = if a == 0 { + // 0u64 + // } else { + // 1u64 << (a - 1) // 1 in some position + // }; + + // smallvec::smallvec![F::from_u64_unchecked(result), F::ZERO] + // }, + // ) +} diff --git a/crates/zkevm_circuits/src/tables/mod.rs b/crates/zkevm_circuits/src/tables/mod.rs new file mode 100644 index 00000000..32989e75 --- /dev/null +++ b/crates/zkevm_circuits/src/tables/mod.rs @@ -0,0 +1,13 @@ +use derivative::*; + +pub mod bitshift; +pub mod conditional; +pub mod integer_to_boolean_mask; +pub mod opcodes_decoding; +pub mod uma_ptr_read_cleanup; + +pub use self::bitshift::*; +pub use self::conditional::*; +pub use self::integer_to_boolean_mask::*; +pub use self::opcodes_decoding::*; +pub use self::uma_ptr_read_cleanup::*; diff --git a/crates/zkevm_circuits/src/tables/opcodes_decoding.rs b/crates/zkevm_circuits/src/tables/opcodes_decoding.rs new file mode 100644 index 00000000..cf1ff116 --- /dev/null +++ b/crates/zkevm_circuits/src/tables/opcodes_decoding.rs @@ -0,0 +1,38 @@ +use super::*; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; + +use zkevm_opcode_defs::OPCODES_TABLE_WIDTH; + +pub const VM_OPCODE_DECODING_AND_PRICING_TABLE_NAME: &'static str = + "Opcode decoding and pricing table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct VMOpcodeDecodingTable; + +pub fn create_opcodes_decoding_and_pricing_table() -> LookupTable { + let mut all_keys = Vec::with_capacity(1 << OPCODES_TABLE_WIDTH); + let num_rows = zkevm_opcode_defs::OPCODES_TABLE.len(); + assert_eq!(num_rows, 1 << OPCODES_TABLE_WIDTH); + + for x in 0..num_rows { + let opcode_as_integer = x as u64; + let opcode_props_encoding = zkevm_opcode_defs::OPCODES_PROPS_INTEGER_BITMASKS[x]; + let price = zkevm_opcode_defs::OPCODES_PRICES[x]; + + let row = [ + F::from_u64(opcode_as_integer).unwrap(), + F::from_u64(price as u64).unwrap(), + F::from_u64(opcode_props_encoding).unwrap(), + ]; + + all_keys.push(row); + } + + LookupTable::new_from_content( + all_keys, + VM_OPCODE_DECODING_AND_PRICING_TABLE_NAME.to_string(), + 1, + ) +} diff --git a/crates/zkevm_circuits/src/tables/uma_ptr_read_cleanup.rs b/crates/zkevm_circuits/src/tables/uma_ptr_read_cleanup.rs new file mode 100644 index 00000000..ab648394 --- /dev/null +++ b/crates/zkevm_circuits/src/tables/uma_ptr_read_cleanup.rs @@ -0,0 +1,40 @@ +use super::*; +use boojum::cs::implementations::lookup_table::LookupTable; +use boojum::field::SmallField; + +pub const UMA_PTR_READ_CLEANUP_TABLE_NAME: &'static str = "UMA PTR read cleanup mask table"; + +#[derive(Derivative)] +#[derivative(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UMAPtrReadCleanupTable; + +pub fn create_uma_ptr_read_bitmask_table() -> LookupTable { + let num_keys = 32; + let mut all_keys = Vec::with_capacity(num_keys); + for integer in 0..num_keys { + let key = smallvec::smallvec![F::from_u64_unchecked(integer as u64)]; + all_keys.push(key); + } + + const FULL_MASK: u64 = (1u64 << 32) - 1; + + LookupTable::new_from_keys_and_generation_function( + &all_keys, + UMA_PTR_READ_CLEANUP_TABLE_NAME.to_string(), + 1, + |keys| { + let a = keys[0].as_u64_reduced(); + + let result = if a == 0 { + FULL_MASK + } else { + let mut tmp = FULL_MASK; + tmp -= (1u64 << a) - 1; + + tmp + }; + + smallvec::smallvec![F::from_u64_unchecked(result), F::ZERO] + }, + ) +} diff --git a/crates/zkevm_circuits/src/utils.rs b/crates/zkevm_circuits/src/utils.rs new file mode 100644 index 00000000..d0356d59 --- /dev/null +++ b/crates/zkevm_circuits/src/utils.rs @@ -0,0 +1,137 @@ +use boojum::algebraic_props::round_function::AlgebraicRoundFunction; +use boojum::cs::traits::cs::ConstraintSystem; +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::traits::round_function::CircuitRoundFunction; +use boojum::gadgets::traits::selectable::Selectable; +use boojum::gadgets::u32::UInt32; + +pub fn produce_fs_challenges< + F: SmallField, + CS: ConstraintSystem, + R: CircuitRoundFunction + AlgebraicRoundFunction, + const N: usize, + const NUM_CHALLENGES: usize, + const NUM_REPETITIONS: usize, +>( + cs: &mut CS, + unsorted_tail: QueueTailState, + sorted_tail: QueueTailState, + _round_function: &R, +) -> [[Num; NUM_CHALLENGES]; NUM_REPETITIONS] { + let mut fs_input = vec![]; + fs_input.extend_from_slice(&unsorted_tail.tail); + fs_input.push(unsorted_tail.length.into_num()); + fs_input.extend_from_slice(&sorted_tail.tail); + fs_input.push(sorted_tail.length.into_num()); + + let mut state = R::create_empty_state(cs); + let length = UInt32::allocated_constant(cs, fs_input.len() as u32); + R::apply_length_specialization(cs, &mut state, length.get_variable()); + + let zero_num = Num::allocated_constant(cs, F::ZERO); + + let mut state = state.map(|el| Num::from_variable(el)); + + let mut it = fs_input.array_chunks::<8>(); + for chunk in &mut it { + let mut state_to_keep = [zero_num; 4]; + state_to_keep.copy_from_slice(&state[8..]); + state = R::absorb_with_replacement_over_nums(cs, *chunk, state_to_keep); + state = R::compute_round_function_over_nums(cs, state); + } + + let remainder = it.remainder(); + if remainder.len() != 0 { + let mut state_to_keep = [zero_num; 4]; + state_to_keep.copy_from_slice(&state[8..]); + let mut padded_chunk = [zero_num; 8]; + padded_chunk[..remainder.len()].copy_from_slice(remainder); + state = R::absorb_with_replacement_over_nums(cs, padded_chunk, state_to_keep); + state = R::compute_round_function_over_nums(cs, state); + } + + // now get as many as necessary + let max_to_take = 8; + let mut can_take = max_to_take; + + let one_num = Num::allocated_constant(cs, F::ONE); + + let mut result = [[one_num; NUM_CHALLENGES]; NUM_REPETITIONS]; + + for dst in result.iter_mut() { + for dst in dst.iter_mut().skip(1) { + if can_take == 0 { + state = R::compute_round_function_over_nums(cs, state); + can_take = max_to_take; + } + let el = state[max_to_take - can_take]; + can_take -= 1; + *dst = el; + } + } + + result +} + +// Strange signature of the function is due to const generics bugs +pub fn accumulate_grand_products< + F: SmallField, + CS: ConstraintSystem, + const ENCODING_LENGTH: usize, + const NUM_CHALLENGES: usize, + const NUM_REPETITIONS: usize, +>( + cs: &mut CS, + lhs_accumulator: &mut [Num; NUM_REPETITIONS], + rhs_accumulator: &mut [Num; NUM_REPETITIONS], + fs_challenges: &[[Num; NUM_CHALLENGES]; NUM_REPETITIONS], + lhs_encoding: &[Variable; ENCODING_LENGTH], + rhs_encoding: &[Variable; ENCODING_LENGTH], + should_accumulate: Boolean, +) { + assert!(ENCODING_LENGTH > 0); + assert_eq!(ENCODING_LENGTH + 1, NUM_CHALLENGES); + for ((challenges, lhs), rhs) in fs_challenges + .iter() + .zip(lhs_accumulator.iter_mut()) + .zip(rhs_accumulator.iter_mut()) + { + // additive parts + let mut lhs_contribution = challenges[ENCODING_LENGTH]; + let mut rhs_contribution = challenges[ENCODING_LENGTH]; + + for ((lhs_el, rhs_el), challenge) in lhs_encoding + .iter() + .zip(rhs_encoding.iter()) + .zip(challenges.iter()) + { + lhs_contribution = Num::fma( + cs, + &Num::from_variable(*lhs_el), + challenge, + &F::ONE, + &lhs_contribution, + &F::ONE, + ); + + rhs_contribution = Num::fma( + cs, + &Num::from_variable(*rhs_el), + challenge, + &F::ONE, + &rhs_contribution, + &F::ONE, + ); + } + + let new_lhs = lhs.mul(cs, &lhs_contribution); + let new_rhs = rhs.mul(cs, &rhs_contribution); + + *lhs = Num::conditionally_select(cs, should_accumulate, &new_lhs, &lhs); + *rhs = Num::conditionally_select(cs, should_accumulate, &new_rhs, &rhs); + } +}