diff --git a/bus-mapping/src/evm/opcodes/begin_end_tx.rs b/bus-mapping/src/evm/opcodes/begin_end_tx.rs index 06b521bcd6..8b743213aa 100644 --- a/bus-mapping/src/evm/opcodes/begin_end_tx.rs +++ b/bus-mapping/src/evm/opcodes/begin_end_tx.rs @@ -749,7 +749,7 @@ fn gen_tx_eip2930_ops( state: &mut CircuitInputStateRef, exec_step: &mut ExecStep, ) -> Result<(), Error> { - if !state.tx.tx_type.is_eip2930_tx() { + if !state.tx.tx_type.is_eip2930() { return Ok(()); } diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 9a6bc09272..3b04cf0a40 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -52,14 +52,24 @@ impl TxType { matches!(*self, Self::L1Msg) } - /// If this type is EIP-155 or not - pub fn is_eip155_tx(&self) -> bool { - matches!(*self, Self::Eip155) + /// If this type is PreEip155 + pub fn is_pre_eip155(&self) -> bool { + matches!(*self, TxType::PreEip155) } - /// If this type is EIP-2930 or not - pub fn is_eip2930_tx(&self) -> bool { - matches!(*self, Self::Eip2930) + /// If this type is EIP155 or not + pub fn is_eip155(&self) -> bool { + matches!(*self, TxType::Eip155) + } + + /// If this type is Eip1559 or not + pub fn is_eip1559(&self) -> bool { + matches!(*self, TxType::Eip1559) + } + + /// If this type is Eip2930 or not + pub fn is_eip2930(&self) -> bool { + matches!(*self, TxType::Eip2930) } /// Get the type of transaction diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000000..7ec67a9076 --- /dev/null +++ b/go.work.sum @@ -0,0 +1 @@ +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index d77b41301c..40c95973bb 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -977,6 +977,9 @@ impl PiCircuitConfig { } } // Copy tx_hashes to tx table + log::trace!("tx_copy_cells: {:?}", tx_copy_cells); + log::trace!("tx_value_cells: {:?}", tx_value_cells); + for (i, tx_hash_cell) in tx_copy_cells.into_iter().enumerate() { region.constrain_equal( tx_hash_cell.cell(), diff --git a/zkevm-circuits/src/rlp_circuit_fsm.rs b/zkevm-circuits/src/rlp_circuit_fsm.rs index 8218d23133..ea73a2106e 100644 --- a/zkevm-circuits/src/rlp_circuit_fsm.rs +++ b/zkevm-circuits/src/rlp_circuit_fsm.rs @@ -13,10 +13,11 @@ use crate::{ Challenges, SubCircuit, SubCircuitConfig, }, witness::{ + rlp_fsm::StackOp, Block, DataTable, Format, RlpFsmWitnessGen, RlpFsmWitnessRow, RlpTag, RomTableRow, State, State::{DecodeTagStart, End}, Tag, - Tag::{BeginList, EndList, TxType}, + Tag::{AccessListAddress, AccessListStorageKey, BeginObject, EndObject, EndVector, TxType}, Transaction, }, }; @@ -178,6 +179,59 @@ impl RlpFsmRomTable { } } +/// Data decoding table simulates a stack-like structure for constraining remaining_bytes +#[derive(Clone, Copy, Debug)] +pub struct RlpDecodingTable { + /// Key1 (Id), concat of (tx_id, format, depth, al_idx, sk_idx) + pub id: Column, + /// Tx Id + pub tx_id: Column, + /// Format + pub format: Column, + /// Depth + pub depth: Column, + /// Byte idx for comparing with state machine + /// Byte idx is also used as Op counter similar to rw counter + pub byte_idx: Column, + /// Value + pub value: Column, + /// Value Previous + pub value_prev: Column, + /// Stack Op flag, Init + pub is_stack_init: Column, + /// Stack Op flag, Push + pub is_stack_push: Column, + /// Stack Op flag, Pop + pub is_stack_pop: Column, + /// Stack Op flag, Update + pub is_stack_update: Column, + /// Access list idx + pub al_idx: Column, + /// Storage key idx + pub sk_idx: Column, +} + +impl RlpDecodingTable { + /// Construct the decoding table. + pub fn construct(meta: &mut ConstraintSystem) -> Self { + Self { + id: meta.advice_column(), + tx_id: meta.advice_column(), + format: meta.advice_column(), + depth: meta.advice_column(), + byte_idx: meta.advice_column(), + value: meta.advice_column(), + value_prev: meta.advice_column(), + is_stack_init: meta.advice_column(), + is_stack_push: meta.advice_column(), + is_stack_pop: meta.advice_column(), + is_stack_update: meta.advice_column(), + al_idx: meta.advice_column(), + sk_idx: meta.advice_column(), + } + } +} + /// The RLP Circuit is implemented as a finite state machine. Refer the /// [design doc][doclink] for design decisions and specification details. /// @@ -194,6 +248,9 @@ pub struct RlpCircuitConfig { state_bits: BinaryNumberConfig, /// The Rlp table which can be accessed by other circuits. rlp_table: RlpFsmRlpTable, + /// The Rlp decoding table ensuring correct transition of + /// stack constraints on remaining_bytes + rlp_decoding_table: RlpDecodingTable, /// The tag, i.e. what field is being decoded at the current row. tag: Column, /// A utility gadget to compare/query what tag we are at. @@ -249,6 +306,38 @@ pub struct RlpCircuitConfig { /// Boolean to reduce the circuit's degree is_same_rlp_instance: Column, + /// Boolean to reduce the circuit's degree + /// Indicates the start of another new access list item + is_new_access_list_address: Column, + /// Boolean to reduce the circuit's degree + /// Indicates the start of another new storage key for an access list address + is_new_access_list_storage_key: Column, + /// Boolean to reduce the circuit's degree + /// Indicates the end of access list + is_access_list_end: Column, + /// Boolean to reduce the circuit's degree + /// Indicates the end of storage key list in a particular access list item + is_storage_key_list_end: Column, + /// Decoding table id change + /// id = (tx_id, format, depth, al_idx, sk_idx) + stack_op_id_diff: Column, + /// Decoding table depth boolean to reduce degree. + /// Indicates depth is 0. + is_stack_depth_zero: Column, + /// Decoding table depth indicator for depth is 1. + is_stack_depth_one: Column, + /// Decoding table depth indicator for depth is 2. + is_stack_depth_two: Column, + /// Decoding table depth indicator for depth is 3. + is_stack_depth_three: Column, + /// Decoding table depth indicator for depth is 4. + is_stack_depth_four: Column, + /// Decoding table key change indicator. depth change. + is_stack_depth_diff: Column, + /// Decoding table key change indicator. access list idx change. + is_stack_al_idx_diff: Column, + /// Decoding table key change indicator. storage key idx change. + is_stack_sk_idx_diff: Column, /// Check for byte_value <= 0x80 byte_value_lte_0x80: ComparatorConfig, /// Check for byte_value >= 0x80 @@ -275,6 +364,10 @@ pub struct RlpCircuitConfig { depth_check: IsEqualConfig, /// Check for depth == 1 depth_eq_one: IsEqualConfig, + /// CHeck for depth == 2 + depth_eq_two: IsEqualConfig, + /// Check for depth == 4 + depth_eq_four: IsEqualConfig, /// Check for byte_value == 0 byte_value_is_zero: IsZeroConfig, @@ -295,6 +388,7 @@ impl RlpCircuitConfig { data_table: RlpFsmDataTable, u8_table: U8Table, rlp_table: RlpFsmRlpTable, + rlp_decoding_table: RlpDecodingTable, challenges: &Challenges>, ) -> Self { let (tx_id, format) = (rlp_table.tx_id, rlp_table.format); @@ -319,6 +413,10 @@ impl RlpCircuitConfig { is_case3, transit_to_new_rlp_instance, is_same_rlp_instance, + is_new_access_list_address, + is_new_access_list_storage_key, + is_access_list_end, + is_storage_key_list_end, ) = ( meta.fixed_column(), meta.fixed_column(), @@ -338,6 +436,10 @@ impl RlpCircuitConfig { meta.advice_column(), meta.advice_column(), meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), ); let tag_value_acc = meta.advice_column_in(SecondPhase); @@ -381,10 +483,12 @@ impl RlpCircuitConfig { }; } - is_tag!(is_tag_begin_list, BeginList); + is_tag!(is_tag_begin_object, BeginObject); is_tag!(is_tag_begin_vector, BeginVector); - is_tag!(is_tag_end_list, EndList); + is_tag!(is_tag_end_object, EndObject); is_tag!(is_tag_end_vector, EndVector); + is_tag!(is_access_list_address, AccessListAddress); + is_tag!(is_access_list_storage_key, AccessListStorageKey); ////////////////////////////////////////////////////////// //////////// data table checks. ////////////////////////// @@ -765,6 +869,18 @@ impl RlpCircuitConfig { |meta| meta.query_advice(depth, Rotation::cur()), |_| 1.expr(), ); + let depth_eq_two = IsEqualChip::configure( + meta, + cmp_enabled, + |meta| meta.query_advice(depth, Rotation::cur()), + |_| 2.expr(), + ); + let depth_eq_four = IsEqualChip::configure( + meta, + cmp_enabled, + |meta| meta.query_advice(depth, Rotation::cur()), + |_| 4.expr(), + ); let tx_id_check_in_sm = IsEqualChip::configure( meta, |meta| meta.query_fixed(q_enabled, Rotation::cur()), @@ -793,14 +909,14 @@ impl RlpCircuitConfig { // use sum instead of or because is_tag_* cannot be true at the same time cb.require_equal( - "is_tag_end = is_tag_end_list || is_tag_end_vector", + "is_tag_end = is_tag_end_object || is_tag_end_vector", meta.query_advice(is_tag_end, Rotation::cur()), - sum::expr([is_tag_end_list(meta), is_tag_end_vector(meta)]), + sum::expr([is_tag_end_object(meta), is_tag_end_vector(meta)]), ); cb.require_equal( - "is_tag_begin = is_tag_begin_list || is_tag_begin_vector", + "is_tag_begin = is_tag_begin_object || is_tag_begin_vector", meta.query_advice(is_tag_begin, Rotation::cur()), - sum::expr([is_tag_begin_list(meta), is_tag_begin_vector(meta)]), + sum::expr([is_tag_begin_object(meta), is_tag_begin_vector(meta)]), ); cb.require_equal( "is_case3 = (0xc0 <= byte_value < 0xf8) && (is_tag_end == false)", @@ -907,8 +1023,8 @@ impl RlpCircuitConfig { constrain_eq!(meta, cb, tx_id, 1.expr()); constrain_eq!(meta, cb, byte_idx, 1.expr()); cb.require_zero( - "tag == TxType or tag == BeginList", - (tag.expr() - TxType.expr()) * (tag - BeginList.expr()), + "tag == TxType or tag == BeginObject", + (tag.expr() - TxType.expr()) * (tag - BeginObject.expr()), ); cb.gate(meta.query_fixed(q_first, Rotation::cur())) @@ -1002,7 +1118,7 @@ impl RlpCircuitConfig { }, ); - // case 4: tag in [EndList, EndVector] + // case 4: tag in [EndObject, EndVector] let case_4 = is_tag_end_expr(meta); cb.condition( and::expr([case_4.expr(), depth_eq_one.is_equal_expression.expr()]), @@ -1034,9 +1150,9 @@ impl RlpCircuitConfig { update_state!(meta, cb, state, DecodeTagStart); cb.require_zero( - "tag == TxType or tag == BeginList", + "tag == TxType or tag == BeginObject", (tag_next.expr() - TxType.expr()) - * (tag_next.expr() - BeginList.expr()), + * (tag_next.expr() - BeginObject.expr()), ); }, ); @@ -1084,7 +1200,6 @@ impl RlpCircuitConfig { }, ); - // debug_assert!(meta.degree() <= 9); // DecodeTagStart => Bytes meta.create_gate("state transition: DecodeTagStart => Bytes", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -1405,12 +1520,713 @@ impl RlpCircuitConfig { cb.gate(meta.query_fixed(q_last, Rotation::cur())) }); + /////////////////////////////////////////////////////////////////// + /////////////////// Access List Constraint Transitions //////////// + /////////////////////////////////////////////////////////////////// + meta.create_gate("booleans for reducing degree (part four)", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_new_access_list_address", + meta.query_advice(is_new_access_list_address, Rotation::cur()), + is_access_list_address(meta), + ); + cb.require_equal( + "is_new_access_list_storage_key", + meta.query_advice(is_new_access_list_storage_key, Rotation::cur()), + is_access_list_storage_key(meta), + ); + cb.require_boolean( + "is_new_access_list_address is boolean", + meta.query_advice(is_new_access_list_address, Rotation::cur()), + ); + cb.require_boolean( + "is_new_access_list_storage_key is boolean", + meta.query_advice(is_new_access_list_storage_key, Rotation::cur()), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + is_decode_tag_start(meta), + ])) + }); + meta.create_gate("boolean for reducing degree (part five)", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_access_list_end", + meta.query_advice(is_access_list_end, Rotation::cur()), + depth_eq_two.is_equal_expression.expr(), + ); + cb.require_equal( + "is_storage_key_list_end", + meta.query_advice(is_storage_key_list_end, Rotation::cur()), + depth_eq_four.is_equal_expression.expr(), + ); + cb.require_boolean( + "is_access_list_end is boolean", + meta.query_advice(is_access_list_end, Rotation::cur()), + ); + cb.require_boolean( + "is_storage_key_list_end is boolean", + meta.query_advice(is_storage_key_list_end, Rotation::cur()), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + is_tag_end_vector(meta), + ])) + }); + + // Access List Increments + meta.create_gate("access list: access_list_idx increments", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition( + meta.query_advice(is_new_access_list_address, Rotation::cur()), + |cb| { + cb.require_equal( + "al_idx - al_idx::prev = 1", + meta.query_advice(rlp_table.access_list_idx, Rotation::prev()) + 1.expr(), + meta.query_advice(rlp_table.access_list_idx, Rotation::cur()), + ); + }, + ); + + cb.condition( + meta.query_advice(is_new_access_list_storage_key, Rotation::cur()), + |cb| { + cb.require_equal( + "sk_idx - sk_idx::prev = 1", + meta.query_advice(rlp_table.storage_key_idx, Rotation::prev()) + 1.expr(), + meta.query_advice(rlp_table.storage_key_idx, Rotation::cur()), + ); + }, + ); + + cb.gate(meta.query_fixed(q_enabled, Rotation::cur())) + }); + + // Access List Clearing + // note: right now no other nested structures are defined at these depth levels + // hence using depth alone is sufficient to determine clearing conditions. + // however, this might change in the future if more nested structures are introduced at same + // depth level + meta.create_gate( + "access list: clearing access_list_idx and storage_key_idx", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition( + meta.query_advice(is_access_list_end, Rotation::cur()), + |cb| { + cb.require_zero( + "al_idx = 0", + meta.query_advice(rlp_table.access_list_idx, Rotation::cur()), + ); + }, + ); + + cb.condition( + meta.query_advice(is_storage_key_list_end, Rotation::cur()), + |cb| { + cb.require_zero( + "sk_idx = 0", + meta.query_advice(rlp_table.storage_key_idx, Rotation::cur()), + ); + }, + ); + + cb.gate(meta.query_fixed(q_enabled, Rotation::cur())) + }, + ); + + // Access List Consistency + // When no conditions for access list address or storage key changes are present, these idxs + // stay the same + meta.create_gate( + "access list: access_list_idx and storage_key_idx don't change when no conditions present", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition( + and::expr([ + not::expr(meta.query_advice(is_new_access_list_address, Rotation::cur())), + not::expr(meta.query_advice(is_access_list_end, Rotation::cur())), + ]), + |cb| { + cb.require_equal( + "al_idx stays the same", + meta.query_advice(rlp_table.access_list_idx, Rotation::prev()), + meta.query_advice(rlp_table.access_list_idx, Rotation::cur()), + ); + }); + + cb.condition( + and::expr([ + not::expr(meta.query_advice(is_new_access_list_storage_key, Rotation::cur())), + not::expr(meta.query_advice(is_storage_key_list_end, Rotation::cur())), + ]), + |cb| { + cb.require_equal( + "storage_key_idx stays the same", + meta.query_advice(rlp_table.storage_key_idx, Rotation::prev()), + meta.query_advice(rlp_table.storage_key_idx, Rotation::cur()), + ); + }); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + not::expr(meta.query_fixed(q_first, Rotation::cur())), + ])) + }, + ); + + debug_assert!(meta.degree() <= 9); + + /////////////////////////////////////////////////////////////////// + /////////////////// Rlp Decoding Table Transitions //////////////// + ///////////////////////// (Stack Constraints) ///////////////////// + /////////////////////////////////////////////////////////////////// + let ( + is_stack_depth_zero, + is_stack_depth_one, + is_stack_depth_two, + is_stack_depth_three, + is_stack_depth_four, + stack_op_id_diff, + is_stack_depth_diff, + is_stack_al_idx_diff, + is_stack_sk_idx_diff, + ) = ( + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + meta.advice_column(), + ); + + meta.create_gate( + "Decoding table depth, operation and key change indicators", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + + // stack operation indicators + cb.condition(not::expr(is_end(meta)), |cb| { + cb.require_equal( + "each row must have a stack operation", + sum::expr([ + meta.query_advice(rlp_decoding_table.is_stack_init, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_push, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_pop, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_update, Rotation::cur()), + ]), + 1.expr(), + ); + }); + + cb.require_boolean( + "is_stack_init is binary", + meta.query_advice(rlp_decoding_table.is_stack_init, Rotation::cur()), + ); + cb.require_boolean( + "is_stack_push is binary", + meta.query_advice(rlp_decoding_table.is_stack_push, Rotation::cur()), + ); + cb.require_boolean( + "is_stack_pop is binary", + meta.query_advice(rlp_decoding_table.is_stack_pop, Rotation::cur()), + ); + cb.require_boolean( + "is_stack_update is binary", + meta.query_advice(rlp_decoding_table.is_stack_update, Rotation::cur()), + ); + + // depth indicators + cb.condition(not::expr(is_end(meta)), |cb| { + cb.require_equal( + "each row must have a depth indicator", + sum::expr([ + meta.query_advice(is_stack_depth_zero, Rotation::cur()), + meta.query_advice(is_stack_depth_one, Rotation::cur()), + meta.query_advice(is_stack_depth_two, Rotation::cur()), + meta.query_advice(is_stack_depth_three, Rotation::cur()), + meta.query_advice(is_stack_depth_four, Rotation::cur()), + ]), + 1.expr(), + ); + }); + + for (idx, col) in [ + is_stack_depth_zero, + is_stack_depth_one, + is_stack_depth_two, + is_stack_depth_three, + is_stack_depth_four, + ] + .into_iter() + .enumerate() + { + cb.require_boolean( + "stack depth indicator is binary", + meta.query_advice(col, Rotation::cur()), + ); + + cb.condition(meta.query_advice(col, Rotation::cur()), |cb| { + cb.require_zero( + "stack depth indicator has correct depth", + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()) + - idx.expr(), + ); + }); + } + + // key change indicators + cb.require_boolean( + "is_stack_depth_diff is binary", + meta.query_advice(is_stack_depth_diff, Rotation::cur()), + ); + cb.require_boolean( + "is_stack_al_idx_diff is binary", + meta.query_advice(is_stack_al_idx_diff, Rotation::cur()), + ); + cb.require_boolean( + "is_stack_sk_idx_diff is binary", + meta.query_advice(is_stack_sk_idx_diff, Rotation::cur()), + ); + + cb.gate(meta.query_fixed(q_enabled, Rotation::cur())) + }, + ); + + meta.create_gate("Conditions for when key = (tx_id, format, depth, al_idx, sk_idx) stays the same", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.condition( + not::expr(meta.query_advice(rlp_decoding_table.is_stack_init, Rotation::cur())), + |cb| { + cb.require_equal( + "Unless stack is init, no change on tx_id", + meta.query_advice(rlp_decoding_table.tx_id, Rotation::prev()), + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()), + ); + cb.require_equal( + "Unless stack is init, no change on format", + meta.query_advice(rlp_decoding_table.tx_id, Rotation::prev()), + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()), + ); + } + ); + cb.condition( + not::expr(meta.query_advice(stack_op_id_diff, Rotation::cur())), + |cb| { + cb.require_equal( + "Key stays the same when no stack op id diff", + meta.query_advice(rlp_decoding_table.id, Rotation::prev()), + meta.query_advice(rlp_decoding_table.id, Rotation::cur()), + ); + } + ); + cb.condition( + not::expr(meta.query_advice(stack_op_id_diff, Rotation::cur())), + |cb| { + cb.require_zero( + "When key stays the same, stack is not init (when tx_id and format change) and no change for depth, al_idx and sk_idx", + sum::expr([ + meta.query_advice(rlp_decoding_table.is_stack_init, Rotation::cur()), + meta.query_advice(is_stack_depth_diff, Rotation::cur()), + meta.query_advice(is_stack_al_idx_diff, Rotation::cur()), + meta.query_advice(is_stack_sk_idx_diff, Rotation::cur()), + ]) + ); + } + ); + cb.condition( + not::expr(meta.query_advice(is_stack_depth_diff, Rotation::cur())), + |cb| { + cb.require_equal( + "Stack depth doesn't change", + meta.query_advice(rlp_decoding_table.depth, Rotation::prev()), + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()), + ); + } + ); + cb.condition( + and::expr([ + meta.query_advice(is_stack_depth_diff, Rotation::cur()), + not::expr(meta.query_advice(rlp_decoding_table.is_stack_init, Rotation::cur())), + ]), + |cb| { + cb.require_boolean( + "When stack is not INIT, depth can only stay the same or increment by 1", + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()) - meta.query_advice(rlp_decoding_table.depth, Rotation::prev()), + ); + } + ); + cb.condition( + not::expr(meta.query_advice(is_stack_al_idx_diff, Rotation::cur())), + |cb| { + cb.require_equal( + "Stack al_idx doesn't change", + meta.query_advice(rlp_decoding_table.al_idx, Rotation::prev()), + meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()), + ); + } + ); + cb.condition( + not::expr(meta.query_advice(is_stack_sk_idx_diff, Rotation::cur())), + |cb| { + cb.require_equal( + "Stack sk_idx doesn't change", + meta.query_advice(rlp_decoding_table.sk_idx, Rotation::prev()), + meta.query_advice(rlp_decoding_table.sk_idx, Rotation::cur()), + ); + } + ); + + cb.gate(meta.query_fixed(q_enabled, Rotation::cur())) + }); + + // Operation-specific constraints + // Specifies how different stack op change the stack op key + meta.create_gate("Decoding table stack op UPDATE", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + // Constraints true for any UPDATE operation + cb.require_equal( + "UPDATE stack operation reads 1 byte", + meta.query_advice(rlp_decoding_table.value, Rotation::cur()) + 1.expr(), + meta.query_advice(rlp_decoding_table.value_prev, Rotation::cur()), + ); + cb.require_equal( + "UPDATE stack operation doesn't skip bytes", + meta.query_advice(rlp_decoding_table.value, Rotation::prev()), + meta.query_advice(rlp_decoding_table.value_prev, Rotation::cur()), + ); + + // When key changes with an UPDATE, it's a new storage key on depth 4" + cb.condition(meta.query_advice(stack_op_id_diff, Rotation::cur()), |cb| { + cb.require_equal( + "Key change with UPDATE is on depth 4 (for a new storage key)", + meta.query_advice(is_stack_depth_four, Rotation::cur()), + 1.expr(), + ); + cb.require_zero( + "Depth and al_idx don't change", + sum::expr([ + meta.query_advice(is_stack_depth_diff, Rotation::cur()), + meta.query_advice(is_stack_al_idx_diff, Rotation::cur()), + ]), + ); + cb.require_equal( + "sk_idx increments by 1", + meta.query_advice(rlp_decoding_table.sk_idx, Rotation::prev()) + 1.expr(), + meta.query_advice(rlp_decoding_table.sk_idx, Rotation::cur()), + ); + }); + + // For any other depth, the key must stay the same + cb.condition( + sum::expr([ + meta.query_advice(is_stack_depth_zero, Rotation::cur()), + meta.query_advice(is_stack_depth_one, Rotation::cur()), + meta.query_advice(is_stack_depth_two, Rotation::cur()), + meta.query_advice(is_stack_depth_three, Rotation::cur()), + ]), + |cb| { + cb.require_zero( + "For UPDATE on any other depth, key must stay the same", + meta.query_advice(stack_op_id_diff, Rotation::cur()), + ); + }, + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_update, Rotation::cur()), + ])) + }); + + meta.create_gate("Decoding table stack op POP", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + // Constraints true for any POP operation + cb.require_equal( + "POP stack operation doesn't skip bytes", + meta.query_advice(rlp_decoding_table.value, Rotation::prev()), + meta.query_advice(rlp_decoding_table.value_prev, Rotation::cur()), + ); + + // When key changes with a POP, it's the end of decoding an access list item on depth 2" + cb.condition(meta.query_advice(stack_op_id_diff, Rotation::cur()), |cb| { + cb.require_equal( + "Key change with POP is on depth 2 (the end of decoding an access list item)", + meta.query_advice(is_stack_depth_two, Rotation::cur()), + 1.expr(), + ); + cb.require_zero( + "Depth and sk_idx don't change", + sum::expr([ + meta.query_advice(is_stack_depth_diff, Rotation::cur()), + meta.query_advice(is_stack_sk_idx_diff, Rotation::cur()), + ]), + ); + cb.require_equal( + "al_idx increments by 1", + meta.query_advice(rlp_decoding_table.al_idx, Rotation::prev()) + 1.expr(), + meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()), + ); + }); + + // For any other depth, the key must stay the same + cb.condition( + sum::expr([ + meta.query_advice(is_stack_depth_zero, Rotation::cur()), + meta.query_advice(is_stack_depth_one, Rotation::cur()), + meta.query_advice(is_stack_depth_three, Rotation::cur()), + meta.query_advice(is_stack_depth_four, Rotation::cur()), + ]), + |cb| { + cb.require_zero( + "For POP on any other depth, key must stay the same", + meta.query_advice(stack_op_id_diff, Rotation::cur()), + ); + }, + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_pop, Rotation::cur()), + ])) + }); + + meta.create_gate("Decoding table stack op PUSH", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + // Constraints true for any PUSH operation + cb.require_zero( + "Before a new PUSH, all previous bytes must be processed.", + meta.query_advice(rlp_decoding_table.value, Rotation::prev()), + ); + cb.require_zero( + "PUSH has value_prev at 0", + meta.query_advice(rlp_decoding_table.value_prev, Rotation::cur()), + ); + + // When key changes with a PUSH, there're 4 scenarios: + // depth=1: start decoding after TxType and Len on depth 0 + // depth=2: start decoding the access list + // depth=3: a new access list item + // depth=4: a new storage key list + cb.condition( + and::expr([ + meta.query_advice(stack_op_id_diff, Rotation::cur()), + sum::expr([ + meta.query_advice(is_stack_depth_one, Rotation::cur()), + meta.query_advice(is_stack_depth_two, Rotation::cur()), + ]), + ]), + |cb| { + cb.require_zero( + "al_idx and sk_idx don't change", + sum::expr([ + meta.query_advice(is_stack_al_idx_diff, Rotation::cur()), + meta.query_advice(is_stack_sk_idx_diff, Rotation::cur()), + ]), + ); + cb.require_equal( + "depth increments by 1", + meta.query_advice(is_stack_depth_diff, Rotation::cur()), + 1.expr(), + ); + }, + ); + + cb.condition( + and::expr([ + meta.query_advice(stack_op_id_diff, Rotation::cur()), + meta.query_advice(is_stack_depth_three, Rotation::cur()), + ]), + |cb| { + cb.require_zero( + "If depth increments by 1, the al_idx is 1", + meta.query_advice(is_stack_depth_diff, Rotation::cur()) + * (meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()) + - 1.expr()), + ); + cb.require_zero( + "If depth stays the same, the al_idx increments by 1", + (1.expr() - meta.query_advice(is_stack_depth_diff, Rotation::cur())) + * (meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()) + - meta.query_advice(rlp_decoding_table.al_idx, Rotation::prev()) + - 1.expr()), + ); + cb.require_zero( + "sk_idx doesn't change", + meta.query_advice(is_stack_sk_idx_diff, Rotation::cur()), + ); + }, + ); + + cb.condition( + and::expr([ + meta.query_advice(stack_op_id_diff, Rotation::cur()), + meta.query_advice(is_stack_depth_four, Rotation::cur()), + ]), + |cb| { + cb.require_zero( + "If depth increments by 1, the al_idx is 1", + meta.query_advice(is_stack_depth_diff, Rotation::cur()) + * (meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()) + - 1.expr()), + ); + cb.require_zero( + "If depth stays the same, the al_idx increments by 1", + (1.expr() - meta.query_advice(is_stack_depth_diff, Rotation::cur())) + * (meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()) + - meta.query_advice(rlp_decoding_table.al_idx, Rotation::prev()) + - 1.expr()), + ); + cb.require_equal( + "For a new storage key list, sk_idx always starts at 1", + meta.query_advice(rlp_decoding_table.sk_idx, Rotation::cur()), + 1.expr(), + ); + }, + ); + + cb.condition(meta.query_advice(stack_op_id_diff, Rotation::cur()), |cb| { + cb.require_zero( + "depth 0 has no PUSH. Depth 0 has INIT.", + meta.query_advice(is_stack_depth_zero, Rotation::cur()), + ); + }); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_push, Rotation::cur()), + ])) + }); + + meta.create_gate("Decoding table stack op INIT", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "stack init pushes all remaining_bytes onto depth 0", + meta.query_advice(rlp_decoding_table.value, Rotation::cur()), + meta.query_advice(byte_rev_idx, Rotation::cur()), + ); + cb.require_equal( + "stack can only init once with the first byte", + meta.query_advice(byte_idx, Rotation::cur()), + 1.expr(), + ); + cb.require_zero( + "stack inits at depth 0", + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()), + ); + cb.require_zero( + "stack inits with al_idx at 0", + meta.query_advice(rlp_decoding_table.al_idx, Rotation::cur()), + ); + cb.require_zero( + "stack inits with sk_idx at 0", + meta.query_advice(rlp_decoding_table.sk_idx, Rotation::cur()), + ); + cb.condition( + not::expr(meta.query_fixed(q_first, Rotation::cur())), + |cb| { + cb.require_boolean( + "tx_id can only stay the same or increment by 1", + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()) + - meta.query_advice(rlp_decoding_table.tx_id, Rotation::prev()), + ); + }, + ); + + cb.gate(and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_init, Rotation::cur()), + ])) + }); + + // Cross-depth stack constraints in the RlpDecodingTable + // These two sets of lookups ensure exact correspondence of PUSH and POP records + meta.lookup_any( + "Each stack PUSH must correspond exactly to a POP into a lower depth level", + |meta| { + let enable = and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_push, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()), + meta.query_advice(rlp_decoding_table.format, Rotation::cur()), + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()) - 1.expr(), + 1.expr(), // is_pop = true + meta.query_advice(rlp_decoding_table.value, Rotation::cur()) + 1.expr(), + ]; + let table_exprs = vec![ + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()), + meta.query_advice(rlp_decoding_table.format, Rotation::cur()), + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_pop, Rotation::cur()), + meta.query_advice(rlp_decoding_table.value_prev, Rotation::cur()) + - meta.query_advice(rlp_decoding_table.value, Rotation::cur()), + ]; + input_exprs + .into_iter() + .zip(table_exprs) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }, + ); + meta.lookup_any( + "Each stack POP must correspond exactly to a PUSH onto a higher depth", + |meta| { + let enable = and::expr([ + meta.query_fixed(q_enabled, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_pop, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()), + meta.query_advice(rlp_decoding_table.format, Rotation::cur()), + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()) + 1.expr(), + 1.expr(), // is_push = true + meta.query_advice(rlp_decoding_table.value_prev, Rotation::cur()) + - meta.query_advice(rlp_decoding_table.value, Rotation::cur()) + - 1.expr(), + ]; + let table_exprs = vec![ + meta.query_advice(rlp_decoding_table.tx_id, Rotation::cur()), + meta.query_advice(rlp_decoding_table.format, Rotation::cur()), + meta.query_advice(rlp_decoding_table.depth, Rotation::cur()), + meta.query_advice(rlp_decoding_table.is_stack_push, Rotation::cur()), + meta.query_advice(rlp_decoding_table.value, Rotation::cur()), + ]; + input_exprs + .into_iter() + .zip(table_exprs) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }, + ); + + debug_assert!(meta.degree() <= 9); + Self { q_first, q_last, state, state_bits, rlp_table, + rlp_decoding_table, tag, tag_bits, tag_next, @@ -1439,6 +2255,23 @@ impl RlpCircuitConfig { transit_to_new_rlp_instance, is_same_rlp_instance, + // access list checks + is_new_access_list_address, + is_new_access_list_storage_key, + is_access_list_end, + is_storage_key_list_end, + + // decoding table + stack_op_id_diff, + is_stack_depth_zero, + is_stack_depth_one, + is_stack_depth_two, + is_stack_depth_three, + is_stack_depth_four, + is_stack_depth_diff, + is_stack_al_idx_diff, + is_stack_sk_idx_diff, + // comparators byte_value_lte_0x80, byte_value_gte_0x80, @@ -1453,6 +2286,8 @@ impl RlpCircuitConfig { tlength_lte_mlength, depth_check, depth_eq_one, + depth_eq_two, + depth_eq_four, byte_value_is_zero, // internal tables @@ -1468,6 +2303,7 @@ impl RlpCircuitConfig { row: usize, witness: &RlpFsmWitnessRow, witness_next: Option<&RlpFsmWitnessRow>, + witness_prev: Option<&RlpFsmWitnessRow>, ) -> Result<(), Error> { // assign to selector region.assign_fixed( @@ -1529,6 +2365,231 @@ impl RlpCircuitConfig { row, || Value::known(F::from(witness.rlp_table.is_none as u64)), )?; + region.assign_advice( + || "rlp_table.access_list_idx", + self.rlp_table.access_list_idx, + row, + || Value::known(F::from(witness.rlp_table.access_list_idx)), + )?; + region.assign_advice( + || "rlp_table.storage_key_idx", + self.rlp_table.storage_key_idx, + row, + || Value::known(F::from(witness.rlp_table.storage_key_idx)), + )?; + + // RlpDecodingTable assignments + let stack_op_id_diff = if let Some(witness_prev) = witness_prev { + (witness_prev.rlp_decoding_table.tx_id != witness.rlp_decoding_table.tx_id) + || (witness_prev.rlp_decoding_table.format != witness.rlp_decoding_table.format) + || (witness_prev.rlp_decoding_table.depth != witness.rlp_decoding_table.depth) + || (witness_prev.rlp_decoding_table.al_idx != witness.rlp_decoding_table.al_idx) + || (witness_prev.rlp_decoding_table.sk_idx != witness.rlp_decoding_table.sk_idx) + } else { + true + }; + region.assign_advice( + || "stack_op_id_diff", + self.stack_op_id_diff, + row, + || Value::known(F::from(stack_op_id_diff as u64)), + )?; + + region.assign_advice( + || "rlp_decoding_table.id", + self.rlp_decoding_table.id, + row, + || witness.rlp_decoding_table.id, + )?; + region.assign_advice( + || "rlp_decoding_table.tx_id", + self.rlp_decoding_table.tx_id, + row, + || Value::known(F::from(witness.rlp_table.tx_id)), + )?; + region.assign_advice( + || "rlp_decoding_table.format", + self.rlp_decoding_table.format, + row, + || Value::known(F::from(usize::from(witness.rlp_table.format) as u64)), + )?; + region.assign_advice( + || "rlp_decoding_table.depth", + self.rlp_decoding_table.depth, + row, + || Value::known(F::from(witness.rlp_decoding_table.depth as u64)), + )?; + region.assign_advice( + || "rlp_decoding_table.value", + self.rlp_decoding_table.value, + row, + || Value::known(F::from(witness.rlp_decoding_table.value as u64)), + )?; + region.assign_advice( + || "rlp_decoding_table.value_prev", + self.rlp_decoding_table.value_prev, + row, + || Value::known(F::from(witness.rlp_decoding_table.value_prev as u64)), + )?; + region.assign_advice( + || "rlp_decoding_table.is_stack_init", + self.rlp_decoding_table.is_stack_init, + row, + || { + Value::known(F::from( + matches!(witness.rlp_decoding_table.stack_op, StackOp::Init) as u64, + )) + }, + )?; + region.assign_advice( + || "rlp_decoding_table.is_stack_push", + self.rlp_decoding_table.is_stack_push, + row, + || { + Value::known(F::from( + matches!(witness.rlp_decoding_table.stack_op, StackOp::Push) as u64, + )) + }, + )?; + region.assign_advice( + || "rlp_decoding_table.is_stack_pop", + self.rlp_decoding_table.is_stack_pop, + row, + || { + Value::known(F::from( + matches!(witness.rlp_decoding_table.stack_op, StackOp::Pop) as u64, + )) + }, + )?; + region.assign_advice( + || "rlp_decoding_table.is_stack_update", + self.rlp_decoding_table.is_stack_update, + row, + || { + Value::known(F::from( + matches!(witness.rlp_decoding_table.stack_op, StackOp::Update) as u64, + )) + }, + )?; + region.assign_advice( + || "rlp_decoding_table.al_idx", + self.rlp_decoding_table.al_idx, + row, + || Value::known(F::from(witness.rlp_decoding_table.al_idx)), + )?; + region.assign_advice( + || "rlp_decoding_table.sk_idx", + self.rlp_decoding_table.sk_idx, + row, + || Value::known(F::from(witness.rlp_decoding_table.sk_idx)), + )?; + region.assign_advice( + || "rlp_decoding_table.byte_idx", + self.rlp_decoding_table.byte_idx, + row, + || Value::known(F::from(witness.rlp_decoding_table.byte_idx as u64)), + )?; + + let is_new_access_list_address = witness.state_machine.state == DecodeTagStart + && witness.state_machine.tag == AccessListAddress; + region.assign_advice( + || "is_new_access_list_address", + self.is_new_access_list_address, + row, + || Value::known(F::from(is_new_access_list_address as u64)), + )?; + let is_new_access_list_storage_key = witness.state_machine.state == DecodeTagStart + && witness.state_machine.tag == AccessListStorageKey; + region.assign_advice( + || "is_new_access_list_storage_key", + self.is_new_access_list_storage_key, + row, + || Value::known(F::from(is_new_access_list_storage_key as u64)), + )?; + let is_access_list_end = + witness.state_machine.tag == EndVector && witness.state_machine.depth == 2; + region.assign_advice( + || "is_access_list_end", + self.is_access_list_end, + row, + || Value::known(F::from(is_access_list_end as u64)), + )?; + let is_storage_key_list_end = + witness.state_machine.tag == EndVector && witness.state_machine.depth == 4; + region.assign_advice( + || "is_storage_key_list_end", + self.is_storage_key_list_end, + row, + || Value::known(F::from(is_storage_key_list_end as u64)), + )?; + for (idx, col) in [ + self.is_stack_depth_zero, + self.is_stack_depth_one, + self.is_stack_depth_two, + self.is_stack_depth_three, + self.is_stack_depth_four, + ] + .into_iter() + .enumerate() + { + region.assign_advice( + || format!("is_stack_depth_x (depth: {:?})", idx), + col, + row, + || Value::known(F::from((witness.rlp_decoding_table.depth == idx) as u64)), + )?; + } + let stack_depth_prev = if let Some(witness_prev) = witness_prev { + witness_prev.rlp_decoding_table.depth + } else { + 0usize + }; + region.assign_advice( + || "is_stack_depth_diff", + self.is_stack_depth_diff, + row, + || { + Value::known(F::from( + (matches!(witness.rlp_decoding_table.stack_op, StackOp::Init) + || (witness.rlp_decoding_table.depth != stack_depth_prev)) + as u64, + )) + }, + )?; + let al_idx_prev = if let Some(witness_prev) = witness_prev { + witness_prev.rlp_decoding_table.al_idx + } else { + 0u64 + }; + region.assign_advice( + || "is_stack_al_idx_diff", + self.is_stack_al_idx_diff, + row, + || { + Value::known(F::from( + (matches!(witness.rlp_decoding_table.stack_op, StackOp::Init) + || (witness.rlp_decoding_table.al_idx != al_idx_prev)) + as u64, + )) + }, + )?; + let sk_idx_prev = if let Some(witness_prev) = witness_prev { + witness_prev.rlp_decoding_table.sk_idx + } else { + 0u64 + }; + region.assign_advice( + || "is_stack_sk_idx_diff", + self.is_stack_sk_idx_diff, + row, + || { + Value::known(F::from( + (matches!(witness.rlp_decoding_table.stack_op, StackOp::Init) + || (witness.rlp_decoding_table.sk_idx != sk_idx_prev)) + as u64, + )) + }, + )?; // assign to sm region.assign_advice( @@ -1686,7 +2747,6 @@ impl RlpCircuitConfig { F::from(witness.rlp_table.tag_length as u64), F::from(witness.state_machine.max_length as u64), )?; - let depth_check_chip = IsEqualChip::construct(self.depth_check.clone()); depth_check_chip.assign( region, @@ -1702,6 +2762,20 @@ impl RlpCircuitConfig { Value::known(F::from(witness.state_machine.depth as u64)), Value::known(F::one()), )?; + let depth_eq_two_chip = IsEqualChip::construct(self.depth_eq_two.clone()); + depth_eq_two_chip.assign( + region, + row, + Value::known(F::from(witness.state_machine.depth as u64)), + Value::known(F::from(2u64)), + )?; + let depth_eq_four_chip = IsEqualChip::construct(self.depth_eq_four.clone()); + depth_eq_four_chip.assign( + region, + row, + Value::known(F::from(witness.state_machine.depth as u64)), + Value::known(F::from(4u64)), + )?; let mlength_lte_0x20_chip = ComparatorChip::construct(self.mlength_lte_0x20.clone()); mlength_lte_0x20_chip.assign( @@ -1754,7 +2828,7 @@ impl RlpCircuitConfig { || "q_enable", self.rlp_table.q_enable, row, - || Value::known(F::one()), + || Value::known(F::zero()), )?; region.assign_advice( || "sm.state", @@ -1766,12 +2840,12 @@ impl RlpCircuitConfig { || "sm.tag", self.tag, row, - || Value::known(F::from(usize::from(EndList) as u64)), + || Value::known(F::from(usize::from(EndObject) as u64)), )?; let state_chip = BinaryNumberChip::construct(self.state_bits); state_chip.assign(region, row, &End)?; let tag_chip = BinaryNumberChip::construct(self.tag_bits); - tag_chip.assign(region, row, &EndList)?; + tag_chip.assign(region, row, &EndObject)?; Ok(()) } @@ -1868,7 +2942,8 @@ impl RlpCircuitConfig { } else { Some(&sm_rows[i + 1]) }; - self.assign_sm_row(&mut region, i, sm_row, sm_row_next)?; + let sm_row_prev = if i == 0 { None } else { Some(&sm_rows[i - 1]) }; + self.assign_sm_row(&mut region, i, sm_row, sm_row_next, sm_row_prev)?; } for i in sm_rows.len()..last_row { self.assign_sm_end_row(&mut region, i)?; @@ -1905,6 +2980,7 @@ impl SubCircuitConfig for RlpCircuitConfig { fn new(meta: &mut ConstraintSystem, args: Self::ConfigArgs) -> Self { let data_table = RlpFsmDataTable::construct(meta); let rom_table = RlpFsmRomTable::construct(meta); + let decoding_table = RlpDecodingTable::construct(meta); Self::configure( meta, @@ -1912,6 +2988,7 @@ impl SubCircuitConfig for RlpCircuitConfig { data_table, args.u8_table, args.rlp_table, + decoding_table, &args.challenges, ) } diff --git a/zkevm-circuits/src/rlp_circuit_fsm/test.rs b/zkevm-circuits/src/rlp_circuit_fsm/test.rs index 2e56f0924c..06dc403291 100644 --- a/zkevm-circuits/src/rlp_circuit_fsm/test.rs +++ b/zkevm-circuits/src/rlp_circuit_fsm/test.rs @@ -39,7 +39,7 @@ fn get_tx(is_eip155: bool) -> Transaction { log::debug!("num_unsigned_bytes: {}", unsigned_bytes.len()); log::debug!("num_signed_bytes: {}", signed_bytes.len()); - Transaction::new_from_rlp_bytes(tx_type, signed_bytes, unsigned_bytes) + Transaction::new_from_rlp_bytes(1, tx_type, signed_bytes, unsigned_bytes) } #[test] @@ -109,26 +109,51 @@ fn test_l1_msg_tx() { #[test] fn test_eip1559_tx() { - let raw_tx_rlp_bytes = hex::decode("02f901e901833c3139842b27f14d86012309ce540083055ca8945f65f7b609678448494de4c87521cdf6cef1e93280b8e4fa558b7100000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000016a217dedfacdf9c23edb84b57154f26a15848e60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000028cad80bb7cf17e27c4c8f893f7945f65f7b609678448494de4c87521cdf6cef1e932e1a0d2dc2a0881b05440a4908cf506b4871b1f7eaa46ea0c5dfdcda5f52bc17164a4f8599495ad61b0a150d79219dcf64e1e6cc01f0b64c4cef842a0ba03decd934aae936605e9d437c401439ec4cefbad5795e0965100f929fe339ca0b36e2afa1a25492257090107ad99d079032e543c8dd1ffcd44cf14a96d3015ac80a0821193127789b107351f670025dd3b862f5836e5155f627a29741a251e8d28e8a07ea1e82b1bf6f29c5d0f1e4024acdb698086ac40c353704d7d5e301fb916f2e3") - .expect("decode tx's hex shall not fail"); - - let eth_tx = EthTransaction::decode(&Rlp::new(&raw_tx_rlp_bytes)) - .expect("decode tx's rlp bytes shall not fail"); - - let eth_tx_req: Eip1559TransactionRequest = (ð_tx).into(); - let typed_tx: TypedTransaction = eth_tx_req.into(); - let rlp_unsigned = typed_tx.rlp().to_vec(); + let test_bytes = vec![ + // empty access list + "02f8b1010a8404a75411850f705051f08301724b94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000006c57d84c55b01f7022999f6c0f95daf0e319dc37000000000000000000000000000000000000000000000000000000003b9aca00c001a0634f6d4b3b4fc658c2c26c1ba0966bd39d7e993b815390f1e778af9cf28d2c22a05410b97e41240ea25eb6250e1af7554cda8991bc4159228c43cfb240503d9870", + // same as third test but with abridged access list + "02f9025d01825cb38520955af4328521cf92558d830a1bff9400fc00900000002c00be4ef8f49c000211000c43830cc4d0b9015504673a0b85b3000bef3e26e01428d1b525a532ea7513b8f21661d0d1d76d3ecb8e1b9f1c923dbfffae4097020c532d1b995b7e3e37a1aa6369386e5939053779abd3597508b00129cd75b800073edec02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f21661d0d1d76d3ecb8e1b9f1c923dbfffae40970bb86c3dc790b0d7291f864244b559b59b30f850a8cfb40dc7c53760375530e5af29fded5e139893252993820686c92b000094b61ba302f01b0f027d40c80d8f70f77d3884776531f80b21d20e5a6b806300024b2c713b4502988e070f96cf3bea50b4811cd5844e13a81b61a8078c761b0b85b3000bef3e26e01428d1b525a532ea7513b80002594ea302f03b9eb369241e4270796e665ea1afac355cb99f0c32078ab8ba00013c08711b06ed871e5a66bebf0af6fb768d343b1d14a04b5b34ab10cf761b0b85b3000bef3e26e01428d1b525a532ea7513b8000143542ef893f7940b85b3000bef3e26e01428d1b525a532ea7513b8e1a00000000000000000000000000000000000000000000000000000000000000006f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0e1dd9768c9de657aca2536cf1cdd1c4536b13ec81ff764307ea8312aa7a8790da070bc879403c8b875e45ea7afbb591f1fd4bde469db47d5f0e879e44c6798d33e80a0d274986e36e16ec2d4846168d59422f68e4b8ec41690b80bdd2ee65819f238eea03d0394f6daae31ba5a276a3741cc2b3ba79b90024f80df865622a62078e72910", + // unabridged access list + "02f90b7b01825cb38520955af4328521cf92558d830a1bff9400fc00900000002c00be4ef8f49c000211000c43830cc4d0b9015504673a0b85b3000bef3e26e01428d1b525a532ea7513b8f21661d0d1d76d3ecb8e1b9f1c923dbfffae4097020c532d1b995b7e3e37a1aa6369386e5939053779abd3597508b00129cd75b800073edec02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f21661d0d1d76d3ecb8e1b9f1c923dbfffae40970bb86c3dc790b0d7291f864244b559b59b30f850a8cfb40dc7c53760375530e5af29fded5e139893252993820686c92b000094b61ba302f01b0f027d40c80d8f70f77d3884776531f80b21d20e5a6b806300024b2c713b4502988e070f96cf3bea50b4811cd5844e13a81b61a8078c761b0b85b3000bef3e26e01428d1b525a532ea7513b80002594ea302f03b9eb369241e4270796e665ea1afac355cb99f0c32078ab8ba00013c08711b06ed871e5a66bebf0af6fb768d343b1d14a04b5b34ab10cf761b0b85b3000bef3e26e01428d1b525a532ea7513b8000143542ef909b0f89b940b85b3000bef3e26e01428d1b525a532ea7513b8f884a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008f8dd94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f8c6a0e1dd9768c9de657aca2536cf1cdd1c4536b13ec81ff764307ea8312aa7a8790da070bc879403c8b875e45ea7afbb591f1fd4bde469db47d5f0e879e44c6798d33ea0f88aa3ad276c350a067c34b2bed705e1a2cd30c7c3154f62ece8ee00939bbd2ea0be11b0e2ba48478671bfcd8fd182e025c26fbfbcf4fdf6952051d6147955a36fa09a1a5a7ef77f3399dea2a1044425aaca7fec294fdfdcacd7a960c9c94d15f0a6a091828b9b711948523369ff1651b6332e98f75bcd940a551dc7247d5af88e71faf8bc945b7e3e37a1aa6369386e5939053779abd3597508f8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000002a0697b2bd7bb2984c4e0dc14c79c987d37818484a62958b9c45a0e8b962f20650fa00000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000000f9018394c7c53760375530e5af29fded5e13989325299382f9016ba00000000000000000000000000000000000000000000000000000000000000010a0000000000000000000000000000000000000000000000000000000000000000ba00000000000000000000000000000000000000000000000000000000000000016a0000000000000000000000000000000000000000000000000000000000000000ea051d155e8243cd6886ab3b36f59778d90f3bbb4af820bc2d4536b23ca13814bfba00000000000000000000000000000000000000000000000000000000000000013a0a7609b0290b911c4b52861d3739b36793fd0e23d9ef78cf2fa96dd1b0cbc764da00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000ca0bda2b1a2a3e35ca431f3c4b50639098537d215591b9ca3db95c24c01795a9981a0000000000000000000000000000000000000000000000000000000000000000df89b94c790b0d7291f864244b559b59b30f850a8cfb40df884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8dd9406ed871e5a66bebf0af6fb768d343b1d14a04b5bf8c6a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000af8bc94f21661d0d1d76d3ecb8e1b9f1c923dbfffae4097f8a5a04d3eb812b43a439547ce41ef251d01e8ad3d0dad3fde6f2bed3d0c0e29dcdd7aa026644b9dbbd32f8882f3abce5ac1575313789ab081b0fe9f3f39c946527bfa27a072fd74a6edf1b99d41f2c81c57f871e198cb7a24fd9861e998221c4aeb776014a0a7609b0290b911c4b52861d3739b36793fd0e23d9ef78cf2fa96dd1b0cbc764da01a3159eb932a0bb66f4d5b9c1cb119796d815774e3c4904b36748d7870d915c2f8dd940f027d40c80d8f70f77d3884776531f80b21d20ef8c6a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8bc941a76bffd6d1fc1660e1d0e0552fde51ddbb120cff8a5a06d5257204ebe7d88fd91ae87941cb2dd9d8062b64ae5a2bd2d28ec40b9fbf6dfa030e699f4646032d62d40ca795ecffcb27a2d9d2859f21626b5a588210198e7a6a0c929f5ae32c0eabfbdd06198210bc49736d88e6501f814a66dd5b2fa59508b3ea0ea52bdd009b752a3e91262d66aae31638bc36b449d247d61d646b87a733d7d5da0877978b096db3b11862d0cdfe5f5b74f30fd7d5d29e8ce80626ed8a8bbef1beef8dd944502988e070f96cf3bea50b4811cd5844e13a81bf8c6a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8dd949eb369241e4270796e665ea1afac355cb99f0c32f8c6a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000cf85994f9a2d7e60a3297e513317ad1d7ce101cc4c6c8f6f842a04b376a11d00750d42abab4d4e465d5dc4d9b1286d77cf0c819f028213ea08bdfa072fd74a6edf1b99d41f2c81c57f871e198cb7a24fd9861e998221c4aeb77601480a0d274986e36e16ec2d4846168d59422f68e4b8ec41690b80bdd2ee65819f238eea03d0394f6daae31ba5a276a3741cc2b3ba79b90024f80df865622a62078e72910", + ]; + + let txs = test_bytes + .into_iter() + .enumerate() + .map(|(idx, bytes)| { + let raw_tx_rlp_bytes = hex::decode(bytes).expect("decode tx's hex shall not fail"); + + let eth_tx = EthTransaction::decode(&Rlp::new(&raw_tx_rlp_bytes)) + .expect("decode tx's rlp bytes shall not fail"); + + let eth_tx_req: Eip1559TransactionRequest = (ð_tx).into(); + let typed_tx: TypedTransaction = eth_tx_req.into(); + let rlp_unsigned = typed_tx.rlp().to_vec(); + + Transaction::new_from_rlp_bytes( + idx + 1, + TxType::Eip1559, + raw_tx_rlp_bytes, + rlp_unsigned, + ) + }) + .collect::>(); + + assert!( + txs.len() <= 10, + "Maximum test cases for Rlp circuit can't exceed 10" + ); - let tx = Transaction::new_from_rlp_bytes(TxType::Eip1559, raw_tx_rlp_bytes, rlp_unsigned); let rlp_circuit = RlpCircuit:: { - txs: vec![tx], + txs, max_txs: 10, - size: 1000, + size: 2 << 13, _marker: Default::default(), }; - let mock_prover = MockProver::run(14, &rlp_circuit, vec![]); - assert!(mock_prover.is_ok()); + let mock_prover = MockProver::run(16, &rlp_circuit, vec![]); + // assert!(mock_prover.is_ok()); let mock_prover = mock_prover.unwrap(); if let Err(errors) = mock_prover.verify_par() { log::debug!("errors.len() = {}", errors.len()); @@ -139,8 +164,9 @@ fn test_eip1559_tx() { #[test] fn test_eip2930_tx() { - let raw_tx_rlp_bytes = hex::decode("01f8710183018c418502edc2c0dc8307a1209480464c21a0639510142d510c5be486f1bd801cdb87f753258d79d80080c001a0563304e8f2306c3fafed471bee76db83690ec113965c6775a8a94625dcb03774a05bcc59f5737520f7d0dc8b4f967635473e0a58526ce9ddd69c4a2454c9955f12") - .expect("decode tx's hex shall not fail"); + let bytes = "01f8710183018c418502edc2c0dc8307a1209480464c21a0639510142d510c5be486f1bd801cdb87f753258d79d80080c001a0563304e8f2306c3fafed471bee76db83690ec113965c6775a8a94625dcb03774a05bcc59f5737520f7d0dc8b4f967635473e0a58526ce9ddd69c4a2454c9955f12"; + + let raw_tx_rlp_bytes = hex::decode(bytes).expect("decode tx's hex shall not fail"); let eth_tx = EthTransaction::decode(&Rlp::new(&raw_tx_rlp_bytes)) .expect("decode tx's rlp bytes shall not fail"); @@ -149,7 +175,7 @@ fn test_eip2930_tx() { let typed_tx: TypedTransaction = eth_tx_req.into(); let rlp_unsigned = typed_tx.rlp().to_vec(); - let tx = Transaction::new_from_rlp_bytes(TxType::Eip2930, raw_tx_rlp_bytes, rlp_unsigned); + let tx = Transaction::new_from_rlp_bytes(1, TxType::Eip2930, raw_tx_rlp_bytes, rlp_unsigned); let rlp_circuit = RlpCircuit:: { txs: vec![tx], max_txs: 10, diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 53e48da01b..bdad726286 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -186,6 +186,10 @@ pub enum TxFieldTag { AccessListRLC, /// The block number in which this tx is included. BlockNumber, + /// Max Priority Fee Per Gas (EIP1559) + MaxPriorityFeePerGas, + /// Max Fee Per Gas (EIP1559) + MaxFeePerGas, } impl_expr!(TxFieldTag); @@ -211,6 +215,8 @@ pub struct TxTable { pub index: Column, /// Value pub value: Column, + /// Access list address + pub access_list_address: Column, } impl TxTable { @@ -224,6 +230,7 @@ impl TxTable { tag, index: meta.advice_column(), value: meta.advice_column_in(SecondPhase), + access_list_address: meta.advice_column(), } } @@ -260,7 +267,7 @@ impl TxTable { q_enable: Column, advice_columns: &[Column], tag: &Column, - row: &[Value; 4], + row: &[Value; 5], msg: &str, ) -> Result, Error> { let mut value_cell = None; @@ -303,18 +310,19 @@ impl TxTable { self.q_enable, &advice_columns, &self.tag, - &[(); 4].map(|_| Value::known(F::zero())), + &[(); 5].map(|_| Value::known(F::zero())), "all-zero", )?; offset += 1; // Tx Table contains an initial region that has a size parametrized by max_txs - // with all the tx data except for calldata, and then a second + // with all the tx data except for calldata and access list, and then a second // region that has a size parametrized by max_calldata with all - // the tx calldata. This is required to achieve a constant fixed column tag - // regardless of the number of input txs or the calldata size of each tx. - let mut calldata_assignments: Vec<[Value; 4]> = Vec::new(); - // Assign Tx data (all tx fields except for calldata) + // the tx calldata and access list. This is required to achieve a constant fixed + // column tag regardless of the number of input txs or the + // calldata/access list size of each tx. + + // Assign Tx data (all tx fields except for calldata and access list) let padding_txs = (txs.len()..max_txs) .map(|tx_id| { let mut padding_tx = Transaction::dummy(chain_id); @@ -326,7 +334,6 @@ impl TxTable { for (i, tx) in txs.iter().chain(padding_txs.iter()).enumerate() { debug_assert_eq!(i + 1, tx.id); let tx_data = tx.table_assignments_fixed(*challenges); - let tx_calldata = tx.table_assignments_dyn(*challenges); for row in tx_data { tx_value_cells.push(assign_row( &mut region, @@ -339,21 +346,39 @@ impl TxTable { )?); offset += 1; } - calldata_assignments.extend(tx_calldata.iter()); } - // Assign Tx calldata - for row in calldata_assignments.into_iter() { - assign_row( - &mut region, - offset, - self.q_enable, - &advice_columns, - &self.tag, - &row, - "", - )?; - offset += 1; + + // Assign dynamic calldata and access list section + for tx in txs.iter().chain(padding_txs.iter()) { + for row in tx.table_assignments_dyn(*challenges).into_iter() { + assign_row( + &mut region, + offset, + self.q_enable, + &advice_columns, + &self.tag, + &row, + "", + )?; + offset += 1; + } + for row in tx + .table_assignments_access_list_dyn(*challenges) + .into_iter() + { + assign_row( + &mut region, + offset, + self.q_enable, + &advice_columns, + &self.tag, + &row, + "", + )?; + offset += 1; + } } + Ok(tx_value_cells) }, ) @@ -368,6 +393,7 @@ impl LookupTable for TxTable { self.tag.into(), self.index.into(), self.value.into(), + self.access_list_address.into(), ] } @@ -378,6 +404,7 @@ impl LookupTable for TxTable { String::from("tag"), String::from("index"), String::from("value"), + String::from("access_list_address"), ] } @@ -388,6 +415,7 @@ impl LookupTable for TxTable { meta.query_fixed(self.tag, Rotation::cur()), meta.query_advice(self.index, Rotation::cur()), meta.query_advice(self.value, Rotation::cur()), + meta.query_advice(self.access_list_address, Rotation::cur()), ] } } @@ -2260,6 +2288,10 @@ pub struct RlpFsmRlpTable { pub is_output: Column, /// Whether or not the current tag's value was nil. pub is_none: Column, + /// Index of access list address + pub access_list_idx: Column, + /// Index of storage key in an access list item + pub storage_key_idx: Column, } impl LookupTable for RlpFsmRlpTable { @@ -2274,6 +2306,8 @@ impl LookupTable for RlpFsmRlpTable { self.tag_length.into(), self.is_output.into(), self.is_none.into(), + self.access_list_idx.into(), + self.storage_key_idx.into(), ] } @@ -2288,6 +2322,8 @@ impl LookupTable for RlpFsmRlpTable { String::from("tag_length"), String::from("is_output"), String::from("is_none"), + String::from("access_list_idx"), + String::from("storage_key_idx"), ] } } @@ -2305,6 +2341,8 @@ impl RlpFsmRlpTable { tag_length: meta.advice_column(), is_output: meta.advice_column(), is_none: meta.advice_column(), + access_list_idx: meta.advice_column(), + storage_key_idx: meta.advice_column(), } } @@ -2372,6 +2410,16 @@ impl RlpFsmRlpTable { self.is_none.into(), Value::known(F::from(row.is_none as u64)), ), + ( + "access_list_idx", + self.access_list_idx.into(), + Value::known(F::from(row.access_list_idx)), + ), + ( + "storage_key_idx", + self.storage_key_idx.into(), + Value::known(F::from(row.storage_key_idx)), + ), ]; for cell in cells.into_iter() { diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index bd886e5c5c..cefee440a0 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -21,8 +21,8 @@ use crate::{ TxFieldTag::{ AccessListAddressesLen, AccessListRLC, AccessListStorageKeysLen, BlockNumber, CallData, CallDataGasCost, CallDataLength, CallDataRLC, CalleeAddress, CallerAddress, ChainID, - Gas, GasPrice, IsCreate, Nonce, SigR, SigS, SigV, TxDataGasCost, TxHashLength, - TxHashRLC, TxSignHash, TxSignLength, TxSignRLC, + Gas, GasPrice, IsCreate, MaxFeePerGas, MaxPriorityFeePerGas, Nonce, SigR, SigS, SigV, + TxDataGasCost, TxHashLength, TxHashRLC, TxSignHash, TxSignLength, TxSignRLC, }, TxTable, U16Table, U8Table, }, @@ -33,7 +33,10 @@ use crate::{ witness, witness::{ rlp_fsm::{Tag, ValueTagLength}, - Format::{L1MsgHash, TxHashEip155, TxHashPreEip155, TxSignEip155, TxSignPreEip155}, + Format::{ + L1MsgHash, TxHashEip155, TxHashEip1559, TxHashEip2930, TxHashPreEip155, TxSignEip155, + TxSignEip1559, TxSignEip2930, TxSignPreEip155, + }, RlpTag, RlpTag::{GasCost, Len, Null, RLC}, Tag::TxType as RLPTxType, @@ -44,12 +47,12 @@ use bus_mapping::circuit_input_builder::keccak_inputs_sign_verify; use eth_types::{ geth_types::{ access_list_size, TxType, - TxType::{Eip155, L1Msg, PreEip155}, + TxType::{Eip155, Eip1559, Eip2930, L1Msg, PreEip155}, }, sign_types::SignData, - Address, Field, ToAddress, ToBigEndian, ToScalar, + AccessList, Address, Field, ToAddress, ToBigEndian, ToScalar, }; -use ethers_core::utils::{keccak256, rlp::Encodable}; +use ethers_core::utils::keccak256; use gadgets::{ binary_number::{BinaryNumberChip, BinaryNumberConfig}, comparator::{ComparatorChip, ComparatorConfig, ComparatorInstruction}, @@ -80,7 +83,7 @@ use halo2_proofs::plonk::SecondPhase; use itertools::Itertools; /// Number of rows of one tx occupies in the fixed part of tx table -pub const TX_LEN: usize = 26; +pub const TX_LEN: usize = 28; /// Offset of TxHash tag in the tx table pub const TX_HASH_OFFSET: usize = 21; /// Offset of ChainID tag in the tx table @@ -96,6 +99,8 @@ enum LookupCondition { RlpHashTag, // lookup into keccak table Keccak, + // lookup into dynamic access list section of tx table + TxAccessList, } #[derive(Clone, Debug)] @@ -145,7 +150,10 @@ pub struct TxCircuitConfig { is_calldata: Column, is_caller_address: Column, is_l1_msg: Column, + is_eip2930: Column, + is_eip1559: Column, is_chain_id: Column, + is_tx_id_zero: Column, lookup_conditions: HashMap>, /// Columns for computing num_all_txs @@ -162,10 +170,11 @@ pub struct TxCircuitConfig { /// An accumulator value used to correctly calculate the calldata gas cost /// for a tx. calldata_gas_cost_acc: Column, - /// An accumulator value used to correctly calculate the RLC(calldata) for a tx. - calldata_rlc: Column, + /// An accumulator value used to correctly calculate the RLC(calldata and access list) for a + /// tx. contains two sections if access list is present on the tx + section_rlc: Column, /// 1st phase column which equals to tx_table.value when is_calldata is true - /// We need this because tx_table.value is a 2nd phase column and is used to get calldata_rlc. + /// We need this because tx_table.value is a 2nd phase column and is used to get section_rlc. /// It's not safe to do RLC on columns of same phase. calldata_byte: Column, @@ -189,6 +198,20 @@ pub struct TxCircuitConfig { rlp_table: RlpTable, keccak_table: KeccakTable, + // Access list columns + al_idx: Column, + sk_idx: Column, + sks_acc: Column, + // section denoter for access list, reduces degree + is_access_list: Column, + // access list tag denoter, reduces degree + is_access_list_address: Column, + is_access_list_storage_key: Column, + // field_rlc holds tag rlc from RLP FSM + // works together with section_rlc to ensure + // no ommittance in access list dynamic section + field_rlc: Column, + _marker: PhantomData, } @@ -274,12 +297,15 @@ impl SubCircuitConfig for TxCircuitConfig { // columns for accumulating length and gas_cost of call_data let is_final = meta.advice_column(); let calldata_gas_cost_acc = meta.advice_column(); - let calldata_rlc = meta.advice_column_in(SecondPhase); + let section_rlc = meta.advice_column_in(SecondPhase); let calldata_byte = meta.advice_column(); // booleans to reduce degree let is_l1_msg = meta.advice_column(); + let is_eip2930 = meta.advice_column(); + let is_eip1559 = meta.advice_column(); let is_calldata = meta.advice_column(); + let is_tx_id_zero = meta.advice_column(); let is_caller_address = meta.advice_column(); let is_chain_id = meta.advice_column(); let is_tag_block_num = meta.advice_column(); @@ -289,11 +315,21 @@ impl SubCircuitConfig for TxCircuitConfig { LookupCondition::RlpSignTag, LookupCondition::RlpHashTag, LookupCondition::Keccak, + LookupCondition::TxAccessList, ] .into_iter() .map(|condition| (condition, meta.advice_column())) .collect::>>(); + // access list columns + let al_idx = meta.advice_column(); + let sk_idx = meta.advice_column(); + let sks_acc = meta.advice_column(); + let is_access_list = meta.advice_column(); + let is_access_list_address = meta.advice_column(); + let is_access_list_storage_key = meta.advice_column(); + let field_rlc = meta.advice_column(); + // TODO: add lookup to SignVerify table for sv_address let sv_address = meta.advice_column(); meta.enable_equality(tx_table.value); @@ -349,6 +385,10 @@ impl SubCircuitConfig for TxCircuitConfig { is_tx_tag!(is_access_list_addresses_len, AccessListAddressesLen); is_tx_tag!(is_access_list_storage_keys_len, AccessListStorageKeysLen); is_tx_tag!(is_access_list_rlc, AccessListRLC); + is_tx_tag!(is_tag_access_list_address, AccessListAddress); + is_tx_tag!(is_tag_access_list_storage_key, AccessListStorageKey); + is_tx_tag!(is_max_fee_per_gas, MaxFeePerGas); + is_tx_tag!(is_max_priority_fee_per_gas, MaxPriorityFeePerGas); let tx_id_unchanged = IsEqualChip::configure( meta, @@ -465,6 +505,19 @@ impl SubCircuitConfig for TxCircuitConfig { (is_hash_rlc(meta), RLC), (is_caller_addr(meta), Tag::Sender.into()), (is_tx_gas_cost(meta), GasCost), + ( + is_tag_access_list_address(meta), + Tag::AccessListAddress.into(), + ), + ( + is_tag_access_list_storage_key(meta), + Tag::AccessListStorageKey.into(), + ), + (is_max_fee_per_gas(meta), Tag::MaxFeePerGas.into()), + ( + is_max_priority_fee_per_gas(meta), + Tag::MaxPriorityFeePerGas.into(), + ), // tx tags which correspond to Null (is_null(meta), Null), (is_create(meta), Null), @@ -493,6 +546,8 @@ impl SubCircuitConfig for TxCircuitConfig { usize::from(PreEip155).expr(), usize::from(Eip155).expr(), usize::from(L1Msg).expr(), + usize::from(Eip2930).expr(), + usize::from(Eip1559).expr(), ], ); @@ -564,9 +619,6 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); - // TODO: add constraints for AccessListAddressesLen, AccessListStorageKeysLen - // and AccessListRLC. - ////////////////////////////////////////////////////////// ///// Constraints for booleans that reducing degree ///// ////////////////////////////////////////////////////////// @@ -582,6 +634,54 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); + meta.create_gate("is_tx_id_zero", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_tx_id_zero", + tx_id_is_zero.expr(Rotation::cur())(meta), + meta.query_advice(is_tx_id_zero, Rotation::cur()), + ); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + + meta.create_gate("is_access_list", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_access_list", + sum::expr([ + meta.query_advice(is_access_list_address, Rotation::cur()), + meta.query_advice(is_access_list_storage_key, Rotation::cur()), + ]), + meta.query_advice(is_access_list, Rotation::cur()), + ); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }); + + meta.create_gate( + "is_access_list_address and is_access_list_storage_key", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_access_list_address", + tag_bits.value_equals(TxFieldTag::AccessListAddress, Rotation::cur())(meta), + meta.query_advice(is_access_list_address, Rotation::cur()), + ); + + cb.require_equal( + "is_access_list_storage_key", + tag_bits.value_equals(TxFieldTag::AccessListStorageKey, Rotation::cur())(meta), + meta.query_advice(is_access_list_storage_key, Rotation::cur()), + ); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }, + ); + meta.create_gate("is_caller_address", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -618,29 +718,62 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); - meta.create_gate("is_l1_msg", |meta| { + meta.create_gate( + "distinguish tx type: is_l1_msg, is_eip2930, is_eip1559", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + + cb.require_equal( + "is_l1_msg = (tx_type == L1Msg)", + meta.query_advice(is_l1_msg, Rotation::cur()), + tx_type_bits.value_equals(L1Msg, Rotation::cur())(meta), + ); + + cb.require_equal( + "is_eip2930 = (tx_type == Eip2930)", + meta.query_advice(is_eip2930, Rotation::cur()), + tx_type_bits.value_equals(Eip2930, Rotation::cur())(meta), + ); + + cb.require_equal( + "is_eip1559 = (tx_type == Eip1559)", + meta.query_advice(is_eip1559, Rotation::cur()), + tx_type_bits.value_equals(Eip1559, Rotation::cur())(meta), + ); + + cb.gate(meta.query_fixed(q_enable, Rotation::cur())) + }, + ); + + meta.create_gate("calldata lookup into tx table condition", |meta| { let mut cb = BaseConstraintBuilder::default(); cb.require_equal( - "is_l1_msg = (tx_type == L1Msg)", - meta.query_advice(is_l1_msg, Rotation::cur()), - tx_type_bits.value_equals(L1Msg, Rotation::cur())(meta), + "condition", + and::expr([ + is_data_length(meta), + not::expr(value_is_zero.expr(Rotation::cur())(meta)), + ]), + meta.query_advice( + lookup_conditions[&LookupCondition::TxCalldata], + Rotation::cur(), + ), ); cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); - meta.create_gate("calldata lookup into tx table condition", |meta| { + meta.create_gate("lookup to access list dynamic section condition", |meta| { let mut cb = BaseConstraintBuilder::default(); cb.require_equal( "condition", and::expr([ - is_data_length(meta), + is_access_list_addresses_len(meta), not::expr(value_is_zero.expr(Rotation::cur())(meta)), ]), meta.query_advice( - lookup_conditions[&LookupCondition::TxCalldata], + lookup_conditions[&LookupCondition::TxAccessList], Rotation::cur(), ), ); @@ -653,14 +786,29 @@ impl SubCircuitConfig for TxCircuitConfig { let is_tag_in_tx_sign = sum::expr([ is_nonce(meta), - is_gas_price(meta), + and::expr([ + not::expr(meta.query_advice(is_eip1559, Rotation::cur())), + is_gas_price(meta), + ]), is_gas(meta), is_to(meta), is_value(meta), is_data_rlc(meta), and::expr([ meta.query_advice(is_chain_id, Rotation::cur()), - tx_type_bits.value_equals(Eip155, Rotation::cur())(meta), + sum::expr([ + tx_type_bits.value_equals(Eip155, Rotation::cur())(meta), + meta.query_advice(is_eip2930, Rotation::cur()), + meta.query_advice(is_eip1559, Rotation::cur()), + ]), + ]), + and::expr([ + meta.query_advice(is_eip1559, Rotation::cur()), + is_max_fee_per_gas(meta), + ]), + and::expr([ + meta.query_advice(is_eip1559, Rotation::cur()), + is_max_priority_fee_per_gas(meta), ]), is_sign_length(meta), is_sign_rlc(meta), @@ -686,7 +834,10 @@ impl SubCircuitConfig for TxCircuitConfig { let is_tag_in_tx_hash = sum::expr([ is_nonce(meta), - is_gas_price(meta), + and::expr([ + not::expr(meta.query_advice(is_eip1559, Rotation::cur())), + is_gas_price(meta), + ]), is_gas(meta), is_to(meta), is_value(meta), @@ -697,6 +848,14 @@ impl SubCircuitConfig for TxCircuitConfig { is_sig_s(meta), is_hash_length(meta), is_hash_rlc(meta), + and::expr([ + meta.query_advice(is_eip1559, Rotation::cur()), + is_max_fee_per_gas(meta), + ]), + and::expr([ + meta.query_advice(is_eip1559, Rotation::cur()), + is_max_priority_fee_per_gas(meta), + ]), ]); cb.require_equal( @@ -776,13 +935,22 @@ impl SubCircuitConfig for TxCircuitConfig { is_calldata, is_chain_id, is_l1_msg, + is_eip2930, + is_eip1559, sv_address, calldata_gas_cost_acc, - calldata_rlc, + section_rlc, + field_rlc, tx_table.clone(), keccak_table.clone(), rlp_table, sig_table, + is_access_list, + is_access_list_address, + is_access_list_storage_key, + al_idx, + sk_idx, + sks_acc, ); meta.create_gate("tx_gas_cost == 0 for L1 msg", |meta| { @@ -1176,8 +1344,8 @@ impl SubCircuitConfig for TxCircuitConfig { gas_cost, ); cb.require_equal( - "calldata_rlc == byte", - meta.query_advice(calldata_rlc, Rotation::cur()), + "section_rlc == byte", + meta.query_advice(section_rlc, Rotation::cur()), meta.query_advice(tx_table.value, Rotation::cur()), ); @@ -1215,25 +1383,36 @@ impl SubCircuitConfig for TxCircuitConfig { meta.query_advice(calldata_gas_cost_acc, Rotation::cur()) + gas_cost_next, ); cb.require_equal( - "calldata_rlc' = calldata_rlc * r + byte'", - meta.query_advice(calldata_rlc, Rotation::next()), - meta.query_advice(calldata_rlc, Rotation::cur()) * challenges.keccak_input() + "section_rlc' = section_rlc * r + byte'", + meta.query_advice(section_rlc, Rotation::next()), + meta.query_advice(section_rlc, Rotation::cur()) * challenges.keccak_input() + meta.query_advice(tx_table.value, Rotation::next()), ); }); - // on the final call data byte, tx_id must change. - cb.condition(is_final_cur.expr(), |cb| { - cb.require_zero( - "tx_id changes at is_final == 1", - tx_id_unchanged.is_equal_expression.clone(), - ); - }); + // End of calldata bytes transition: + // on the final call data byte, must transition to another + // calldata section or an access list section for the same tx + // on the final call data byte, if there's no access list, tx_id must change. cb.condition( and::expr([ - is_final_cur, - not::expr(tx_id_is_zero.expr(Rotation::next())(meta)), + is_final_cur.expr(), + not::expr(meta.query_advice(is_access_list, Rotation::next())), + ]), + |cb| { + cb.require_zero( + "tx_id changes at is_final == 1", + tx_id_unchanged.is_equal_expression.clone(), + ); + }, + ); + + cb.condition( + and::expr([ + is_final_cur.clone(), + not::expr(meta.query_advice(is_tx_id_zero, Rotation::next())), + meta.query_advice(is_calldata, Rotation::next()), ]), |cb| { let value_next_is_zero = value_is_zero.expr(Rotation::next())(meta); @@ -1250,16 +1429,186 @@ impl SubCircuitConfig for TxCircuitConfig { gas_cost_next, ); cb.require_equal( - "calldata_rlc' == byte'", - meta.query_advice(calldata_rlc, Rotation::next()), + "section_rlc' == byte'", + meta.query_advice(section_rlc, Rotation::next()), meta.query_advice(tx_table.value, Rotation::next()), ); }, ); + // Initialize the dynamic access list assignment section. + // Must follow immediately when the calldata section ends. + cb.condition( + and::expr([ + is_final_cur.expr(), + meta.query_advice(is_access_list, Rotation::next()), + ]), + |cb| { + cb.require_zero( + "tx_id stays the same for access list section at is_final == 1", + tx_id_unchanged.is_equal_expression.clone() - 1.expr(), + ); + cb.require_equal( + "al_idx starts with 1", + meta.query_advice(al_idx, Rotation::next()), + 1.expr(), + ); + cb.require_zero( + "sks_acc starts with 0", + meta.query_advice(sks_acc, Rotation::next()), + ); + cb.require_equal( + "section_rlc::cur == field_rlc::cur", + meta.query_advice(section_rlc, Rotation::next()), + meta.query_advice(field_rlc, Rotation::next()), + ); + }, + ); + cb.gate(and::expr(vec![ meta.query_fixed(q_enable, Rotation::cur()), meta.query_advice(is_calldata, Rotation::cur()), + not::expr(meta.query_advice(is_tx_id_zero, Rotation::cur())), + ])) + }); + + //////////////////////////////////////////////////////////////////////// + /////////// Access List Constraints (if available on tx) ///////////// + //////////////////////////////////////////////////////////////////////// + meta.create_gate("tx access list", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + let is_final_cur = meta.query_advice(is_final, Rotation::cur()); + cb.require_boolean("is_final is boolean", is_final_cur.clone()); + + // section_rlc accumulation factor, rand^20 for addresses or rand^32 for storage keys + let r20 = [1, 0, 1, 0, 0].iter() + .fold(1.expr(), |acc: Expression, bit| acc.clone() * acc * if *bit > 0 { challenges.keccak_input() } else { 1.expr() }); + let r32 = [1, 0, 0, 0, 0, 0].iter() + .fold(1.expr(), |acc: Expression, bit| acc.clone() * acc * if *bit > 0 { challenges.keccak_input() } else { 1.expr() }); + + // current tag is AccessListAddress + cb.condition( + and::expr([ + not::expr(is_final_cur.clone()), + meta.query_advice(is_access_list_address, Rotation::cur()) + ]), + |cb| { + cb.require_equal( + "index = al_idx", + meta.query_advice(al_idx, Rotation::cur()), + meta.query_advice(tx_table.index, Rotation::cur()), + ); + cb.require_equal( + "access_list_address = value", + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(tx_table.access_list_address, Rotation::cur()), + ); + cb.require_zero( + "sk_idx = 0", + meta.query_advice(sk_idx, Rotation::cur()), + ); + cb.require_equal( + "section_rlc accumulation: r = rand^20, section_rlc' = section_rlc * r + field_rlc'", + meta.query_advice(section_rlc, Rotation::next()), + meta.query_advice(section_rlc, Rotation::cur()) * r20 + + meta.query_advice(field_rlc, Rotation::next()), + ); + }); + + // current tag is AccessListStorageKey + cb.condition( + and::expr([ + not::expr(is_final_cur.clone()), + meta.query_advice(is_access_list_storage_key, Rotation::cur()) + ]), + |cb| { + cb.require_equal( + "index = sks_acc", + meta.query_advice(sks_acc, Rotation::cur()), + meta.query_advice(tx_table.index, Rotation::cur()), + ); + cb.require_equal( + "section_rlc accumulation: r = rand^32, section_rlc' = section_rlc * r + field_rlc'", + meta.query_advice(section_rlc, Rotation::next()), + meta.query_advice(section_rlc, Rotation::cur()) * r32 + + meta.query_advice(field_rlc, Rotation::next()), + ); + }); + + // within same tx, next tag is AccessListAddress + cb.condition( + and::expr([ + not::expr(is_final_cur.clone()), + meta.query_advice(is_access_list_address, Rotation::next()) + ]), + |cb| { + cb.require_equal( + "sks_acc' = sks_acc", + meta.query_advice(sks_acc, Rotation::cur()), + meta.query_advice(sks_acc, Rotation::next()), + ); + cb.require_equal( + "al_idx' = al_idx + 1", + meta.query_advice(al_idx, Rotation::cur()) + 1.expr(), + meta.query_advice(al_idx, Rotation::next()), + ); + }); + + // within same tx, next tag is AccessListStorageKey + cb.condition( + and::expr([ + not::expr(is_final_cur.clone()), + meta.query_advice(is_access_list_storage_key, Rotation::next()) + ]), + |cb| { + cb.require_equal( + "sks_acc' = sks_acc + 1", + meta.query_advice(sks_acc, Rotation::cur()) + 1.expr(), + meta.query_advice(sks_acc, Rotation::next()), + ); + cb.require_equal( + "sk_idx' = sk_idx + 1", + meta.query_advice(sk_idx, Rotation::cur()) + 1.expr(), + meta.query_advice(sk_idx, Rotation::next()), + ); + cb.require_equal( + "al_idx' = al_idx", + meta.query_advice(al_idx, Rotation::cur()), + meta.query_advice(al_idx, Rotation::next()), + ); + cb.require_equal( + "access_list_address' = access_list_address", + meta.query_advice(tx_table.access_list_address, Rotation::cur()), + meta.query_advice(tx_table.access_list_address, Rotation::next()), + ); + }); + + // End conditions for the dynamic access list section, if the dynamic calldata section for the next tx doesn't exist + // For a regular access list section that immediately follows a calldata section, the init idx + // conditions are defined using the tail location of calldata (in the previous constraint block) + cb.condition( + and::expr([ + is_final_cur, + not::expr(tx_id_is_zero.expr(Rotation::next())(meta)), + meta.query_advice(is_access_list, Rotation::next()), + ]), + |cb| { + cb.require_equal( + "al_idx starts with 1", + meta.query_advice(al_idx, Rotation::next()), + 1.expr() + ); + cb.require_zero( + "sks_acc starts with 0", + meta.query_advice(sks_acc, Rotation::next()), + ); + } + ); + + cb.gate(and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list, Rotation::cur()), not::expr(tx_id_is_zero.expr(Rotation::cur())(meta)), ])) }); @@ -1361,6 +1710,7 @@ impl SubCircuitConfig for TxCircuitConfig { value_is_zero, tx_id_unchanged, is_calldata, + is_tx_id_zero, is_caller_address, tx_id_cmp_cum_num_txs, tx_id_gt_prev_cnt, @@ -1373,10 +1723,12 @@ impl SubCircuitConfig for TxCircuitConfig { num_all_txs_acc, total_l1_popped_before, is_l1_msg, + is_eip2930, + is_eip1559, is_chain_id, is_final, calldata_gas_cost_acc, - calldata_rlc, + section_rlc, calldata_byte, sv_address, sig_table, @@ -1385,6 +1737,13 @@ impl SubCircuitConfig for TxCircuitConfig { keccak_table, rlp_table, is_tag_block_num, + al_idx, + sk_idx, + sks_acc, + is_access_list, + is_access_list_address, + is_access_list_storage_key, + field_rlc, _marker: PhantomData, num_txs, } @@ -1407,13 +1766,22 @@ impl TxCircuitConfig { is_calldata: Column, is_chain_id: Column, is_l1_msg_col: Column, + is_eip2930: Column, + is_eip1559: Column, sv_address: Column, calldata_gas_cost_acc: Column, - calldata_rlc: Column, + section_rlc: Column, + field_rlc: Column, tx_table: TxTable, keccak_table: KeccakTable, rlp_table: RlpTable, sig_table: SigTable, + is_access_list: Column, + is_access_list_address: Column, + is_access_list_storage_key: Column, + al_idx: Column, + sk_idx: Column, + sks_acc: Column, ) { macro_rules! is_tx_type { ($var:ident, $type_variant:ident) => { @@ -1488,7 +1856,7 @@ impl TxCircuitConfig { }); meta.lookup_any("lookup CallDataRLC in the calldata part", |meta| { let is_call_data = meta.query_advice(is_calldata, Rotation::cur()); - let calldata_rlc = meta.query_advice(calldata_rlc, Rotation::cur()); + let section_rlc = meta.query_advice(section_rlc, Rotation::cur()); let enable = and::expr([ meta.query_fixed(tx_table.q_enable, Rotation::cur()), is_call_data, @@ -1499,7 +1867,55 @@ impl TxCircuitConfig { let input_exprs = vec![ meta.query_advice(tx_table.tx_id, Rotation::cur()), CallDataRLC.expr(), - calldata_rlc.expr(), + section_rlc.expr(), + ]; + let table_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_fixed(tx_table.tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ]; + + input_exprs + .into_iter() + .zip(table_exprs) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }); + meta.lookup_any("lookup AccessListAddressLen in the TxTable", |meta| { + let enable = and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list, Rotation::cur()), + meta.query_advice(is_final, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + AccessListAddressesLen.expr(), + meta.query_advice(al_idx, Rotation::cur()), + ]; + let table_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_fixed(tx_table.tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ]; + + input_exprs + .into_iter() + .zip(table_exprs) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }); + meta.lookup_any("lookup AccessListStorageKeysLen in the TxTable", |meta| { + let enable = and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list, Rotation::cur()), + meta.query_advice(is_final, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + AccessListStorageKeysLen.expr(), + meta.query_advice(sks_acc, Rotation::cur()), ]; let table_exprs = vec![ meta.query_advice(tx_table.tx_id, Rotation::cur()), @@ -1513,6 +1929,61 @@ impl TxCircuitConfig { .map(|(input, table)| (input * enable.expr(), table)) .collect() }); + meta.lookup_any("lookup AccessListRLC in the TxTable", |meta| { + let enable = and::expr([ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list, Rotation::cur()), + meta.query_advice(is_final, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + AccessListRLC.expr(), + meta.query_advice(section_rlc, Rotation::cur()), + ]; + let table_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_fixed(tx_table.tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ]; + + input_exprs + .into_iter() + .zip(table_exprs) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }); + meta.lookup_any("is_final access list row should be present", |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice( + lookup_conditions[&LookupCondition::TxAccessList], + Rotation::cur(), + ), + ]); + let input_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + 1.expr(), + 1.expr(), + meta.query_advice(tx_table.value, Rotation(0)), // al_idx + meta.query_advice(tx_table.value, Rotation(1)), // sks_acc + meta.query_advice(tx_table.value, Rotation(2)), // section_rlc for access list + ]; + let table_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + meta.query_advice(is_access_list, Rotation::cur()), + meta.query_advice(is_final, Rotation::cur()), + meta.query_advice(al_idx, Rotation::cur()), + meta.query_advice(sks_acc, Rotation::cur()), + meta.query_advice(section_rlc, Rotation::cur()), + ]; + + input_exprs + .into_iter() + .zip(table_exprs) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }); ///////////////////////////////////////////////////////////////// ///////////////// RLP table lookups ////////////////////// @@ -1539,6 +2010,8 @@ impl TxCircuitConfig { tag_length, 1.expr(), // is_output = true 0.expr(), // is_none = false + 0.expr(), // access_list_idx + 0.expr(), // storage_key_idx ]; assert_eq!(input_exprs.len(), rlp_table.table_exprs(meta).len()); @@ -1561,7 +2034,9 @@ impl TxCircuitConfig { let rlp_tag = meta.query_advice(rlp_tag, Rotation::cur()); let is_none = meta.query_advice(is_none, Rotation::cur()); let sign_format = is_pre_eip155(meta) * TxSignPreEip155.expr() - + is_eip155(meta) * TxSignEip155.expr(); + + is_eip155(meta) * TxSignEip155.expr() + + meta.query_advice(is_eip2930, Rotation::cur()) * TxSignEip2930.expr() + + meta.query_advice(is_eip1559, Rotation::cur()) * TxSignEip1559.expr(); // q_enable, tx_id, format, rlp_tag, tag_value, is_output, is_none vec![ @@ -1574,6 +2049,8 @@ impl TxCircuitConfig { meta.query_advice(tx_value_length, Rotation::cur()), 1.expr(), // is_output = true is_none, + 0.expr(), // access_list_idx + 0.expr(), // storage_key_idx ] .into_iter() .zip_eq(rlp_table.table_exprs(meta)) @@ -1600,7 +2077,9 @@ impl TxCircuitConfig { let is_none = meta.query_advice(is_none, Rotation::cur()); let hash_format = is_pre_eip155(meta) * TxHashPreEip155.expr() + is_eip155(meta) * TxHashEip155.expr() - + is_l1_msg(meta) * L1MsgHash.expr(); + + is_l1_msg(meta) * L1MsgHash.expr() + + meta.query_advice(is_eip2930, Rotation::cur()) * TxHashEip2930.expr() + + meta.query_advice(is_eip1559, Rotation::cur()) * TxHashEip1559.expr(); vec![ 1.expr(), // q_enable = true @@ -1612,6 +2091,8 @@ impl TxCircuitConfig { meta.query_advice(tx_value_length, Rotation::cur()), 1.expr(), // is_output = true is_none, + 0.expr(), // access_list_idx + 0.expr(), // storage_key_idx ] .into_iter() .zip_eq(rlp_table.table_exprs(meta)) @@ -1619,6 +2100,144 @@ impl TxCircuitConfig { .collect() }); + // lookup access list address in RLP table + // 1. ensure field_rlc is correct + // 2. ensure value of address is correct + meta.lookup_any( + "Lookup access list address in RLP Table from tx circuit dynamic section (Signing)", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list_address, Rotation::cur()), + ]); + + // only eip2930 and eip1559 contains an access list + let sign_format = meta.query_advice(is_eip2930, Rotation::cur()) + * TxSignEip2930.expr() + + meta.query_advice(is_eip1559, Rotation::cur()) * TxSignEip1559.expr(); + + vec![ + 1.expr(), // q_enable = true + meta.query_advice(tx_table.tx_id, Rotation::cur()), + sign_format, + meta.query_advice(rlp_tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(field_rlc, Rotation::cur()), + 20.expr(), // 20 bytes for address + 1.expr(), // is_output = true + 0.expr(), // is_none = false. must have value + meta.query_advice(al_idx, Rotation::cur()), // access_list_idx + meta.query_advice(sk_idx, Rotation::cur()), // storage_key_idx + ] + .into_iter() + .zip_eq(rlp_table.table_exprs(meta)) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + + meta.lookup_any( + "Lookup access list address in RLP Table from tx circuit dynamic section (Hashing)", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list_address, Rotation::cur()), + ]); + + // only eip2930 and eip1559 contains an access list + let hash_format = meta.query_advice(is_eip2930, Rotation::cur()) + * TxHashEip2930.expr() + + meta.query_advice(is_eip1559, Rotation::cur()) * TxHashEip1559.expr(); + + vec![ + 1.expr(), // q_enable = true + meta.query_advice(tx_table.tx_id, Rotation::cur()), + hash_format, + meta.query_advice(rlp_tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(field_rlc, Rotation::cur()), + 20.expr(), // 20 bytes for address + 1.expr(), // is_output = true + 0.expr(), // is_none = false. must have value + meta.query_advice(al_idx, Rotation::cur()), // access_list_idx + meta.query_advice(sk_idx, Rotation::cur()), // storage_key_idx + ] + .into_iter() + .zip_eq(rlp_table.table_exprs(meta)) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + + // lookup access list storage key in RLP table + // 1. ensure field_rlc is correct + // 2. ensure value of storage key is correct + meta.lookup_any( + "Lookup access list storage key in RLP Table from tx circuit dynamic section (Signing)", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list_storage_key, Rotation::cur()), + ]); + + // only eip2930 and eip1559 contains an access list + let sign_format = meta.query_advice(is_eip2930, Rotation::cur()) + * TxSignEip2930.expr() + + meta.query_advice(is_eip1559, Rotation::cur()) * TxSignEip1559.expr(); + + vec![ + 1.expr(), // q_enable = true + meta.query_advice(tx_table.tx_id, Rotation::cur()), + sign_format, + meta.query_advice(rlp_tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(field_rlc, Rotation::cur()), + 32.expr(), // 32 bytes for storage keys + 1.expr(), // is_output = true + 0.expr(), // is_none = false. must have value + meta.query_advice(al_idx, Rotation::cur()), // access_list_idx + meta.query_advice(sk_idx, Rotation::cur()), // storage_key_idx + ] + .into_iter() + .zip_eq(rlp_table.table_exprs(meta)) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + + meta.lookup_any( + "Lookup access list storage key in RLP Table from tx circuit dynamic section (Hashing)", + |meta| { + let enable = and::expr(vec![ + meta.query_fixed(q_enable, Rotation::cur()), + meta.query_advice(is_access_list_storage_key, Rotation::cur()), + ]); + + // only eip2930 and eip1559 contains an access list + let hash_format = meta.query_advice(is_eip2930, Rotation::cur()) + * TxHashEip2930.expr() + + meta.query_advice(is_eip1559, Rotation::cur()) * TxHashEip1559.expr(); + + vec![ + 1.expr(), // q_enable = true + meta.query_advice(tx_table.tx_id, Rotation::cur()), + hash_format, + meta.query_advice(rlp_tag, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(field_rlc, Rotation::cur()), + 32.expr(), // 32 bytes for storage keys + 1.expr(), // is_output = true + 0.expr(), // is_none = false. must have value + meta.query_advice(al_idx, Rotation::cur()), // access_list_idx + meta.query_advice(sk_idx, Rotation::cur()), // storage_key_idx + ] + .into_iter() + .zip_eq(rlp_table.table_exprs(meta)) + .map(|(arg, table)| (enable.clone() * arg, table)) + .collect() + }, + ); + //////////////////////////////////////////////////////////////////// ///////////////// Sig table lookups ////////////////////// ///////////////// ////////////////////////////////////////////////// @@ -1707,6 +2326,7 @@ impl TxCircuitConfig { TxFieldTag::Null, 0, Value::known(F::zero()), + Value::known(F::zero()), )?; let (col_anno, col, col_val) = ("rlp_tag", self.rlp_tag, F::from(usize::from(Null) as u64)); region.assign_advice(|| col_anno, col, *offset, || Value::known(col_val))?; @@ -1956,18 +2576,33 @@ impl TxCircuitConfig { be_bytes_len: 0, be_bytes_rlc: zero_rlc, }), - // TODO: need to check if it's correct with RLP. - rlc_be_bytes( - &tx.access_list - .as_ref() - .map(|access_list| access_list.rlp_bytes()) - .unwrap_or_default(), - keccak_input, - ), + access_list_rlc(&tx.access_list, challenges), + ), + ( + MaxFeePerGas, + Some(RlpTableInputValue { + tag: Tag::MaxFeePerGas.into(), + is_none: tx.max_fee_per_gas.is_zero(), + be_bytes_len: tx.max_fee_per_gas.tag_length(), + be_bytes_rlc: rlc_be_bytes(&tx.max_fee_per_gas.to_be_bytes(), keccak_input), + }), + rlc_be_bytes(&tx.max_fee_per_gas.to_be_bytes(), evm_word), + ), + ( + MaxPriorityFeePerGas, + Some(RlpTableInputValue { + tag: Tag::MaxPriorityFeePerGas.into(), + is_none: tx.max_priority_fee_per_gas.is_zero(), + be_bytes_len: tx.max_priority_fee_per_gas.tag_length(), + be_bytes_rlc: rlc_be_bytes( + &tx.max_priority_fee_per_gas.to_be_bytes(), + keccak_input, + ), + }), + rlc_be_bytes(&tx.max_priority_fee_per_gas.to_be_bytes(), evm_word), ), (BlockNumber, None, Value::known(F::from(tx.block_number))), ]; - for (tx_tag, rlp_input, tx_value) in fixed_rows { let rlp_tag = rlp_input.clone().map_or(Null, |input| input.tag); let rlp_is_none = rlp_input.clone().map_or(false, |input| input.is_none); @@ -1991,6 +2626,7 @@ impl TxCircuitConfig { tx_tag, 0, tx_value, + Value::known(F::zero()), )?); // 1st phase columns @@ -2033,12 +2669,12 @@ impl TxCircuitConfig { self.sv_address, sign_data.get_addr().to_scalar().unwrap(), ), - // tx_tag related indicator columns ( "is_tag_calldata", self.is_calldata, F::from((tx_tag == CallData) as u64), ), + // tx_tag related indicator columns ( "is_tag_block_num", self.is_tag_block_num, @@ -2076,11 +2712,22 @@ impl TxCircuitConfig { F::zero() } }); - // 2. lookup to RLP table for signing (non L1 msg) + // 2. lookup to ensure the final row in the access list dynamic section is present. + conditions.insert(LookupCondition::TxAccessList, { + let tag_enable = tx_tag == AccessListAddressesLen; + if tag_enable + && tx.access_list.is_some() + && !tx.access_list.as_ref().unwrap().0.is_empty() + { + F::one() + } else { + F::zero() + } + }); + // 3. lookup to RLP table for signing (non L1 msg) conditions.insert(LookupCondition::RlpSignTag, { let sign_set = [ Nonce, - GasPrice, Gas, CalleeAddress, TxFieldTag::Value, @@ -2090,14 +2737,16 @@ impl TxCircuitConfig { ]; let is_tag_in_set = sign_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; let case1 = is_tag_in_set && !is_l1_msg; - let case2 = tx.tx_type.is_eip155_tx() && (tx_tag == ChainID); - F::from((case1 || case2) as u64) + let case2 = !tx.tx_type.is_pre_eip155() && !is_l1_msg && (tx_tag == ChainID); + let case3 = !tx.tx_type.is_eip1559() && !is_l1_msg && (tx_tag == GasPrice); + let case4 = tx.tx_type.is_eip1559() + && (tx_tag == MaxFeePerGas || tx_tag == MaxPriorityFeePerGas); + F::from((case1 || case2 || case3 || case4) as u64) }); - // 3. lookup to RLP table for hashing (non L1 msg) + // 4. lookup to RLP table for hashing (non L1 msg) conditions.insert(LookupCondition::RlpHashTag, { let hash_set = [ Nonce, - GasPrice, Gas, CalleeAddress, TxFieldTag::Value, @@ -2110,9 +2759,13 @@ impl TxCircuitConfig { TxHashRLC, ]; let is_tag_in_set = hash_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; - F::from((!is_l1_msg && is_tag_in_set) as u64) + let case1 = is_tag_in_set && !is_l1_msg; + let case2 = !tx.tx_type.is_eip1559() && !is_l1_msg && (tx_tag == GasPrice); + let case3 = tx.tx_type.is_eip1559() + && (tx_tag == MaxFeePerGas || tx_tag == MaxPriorityFeePerGas); + F::from((case1 || case2 || case3) as u64) }); - // 4. lookup to RLP table for hashing (L1 msg) + // 5. lookup to RLP table for hashing (L1 msg) conditions.insert(LookupCondition::L1MsgHash, { let hash_set = [ Nonce, @@ -2128,7 +2781,7 @@ impl TxCircuitConfig { let is_tag_in_set = hash_set.into_iter().filter(|tag| tx_tag == *tag).count() == 1; F::from((is_l1_msg && is_tag_in_set) as u64) }); - // 5. lookup to Keccak table for tx_sign_hash and tx_hash + // 6. lookup to Keccak table for tx_sign_hash and tx_hash conditions.insert(LookupCondition::Keccak, { let case1 = (tx_tag == TxSignLength) && !is_l1_msg; let case2 = tx_tag == TxHashLength; @@ -2207,6 +2860,7 @@ impl TxCircuitConfig { CallData, idx as u64, Value::known(F::from(*byte as u64)), + Value::known(F::zero()), )?; // 1st phase columns @@ -2226,7 +2880,7 @@ impl TxCircuitConfig { } // 2nd phase columns - region.assign_advice(|| "rlc", self.calldata_rlc, *offset, || rlc)?; + region.assign_advice(|| "rlc", self.section_rlc, *offset, || rlc)?; *offset += 1; } @@ -2234,6 +2888,161 @@ impl TxCircuitConfig { Ok(()) } + /// Assign access list rows of each tx + fn assign_access_list_rows( + &self, + region: &mut Region<'_, F>, + offset: &mut usize, + tx: &Transaction, + next_tx: Option<&Transaction>, + challenges: &Challenges>, + ) -> Result<(), Error> { + // assign to access_list related columns + + if tx.access_list.is_some() { + // storage key len accumulator + let mut sks_acc: usize = 0; + + // row counting for determining when section ends + let total_rows: usize = tx + .access_list + .as_ref() + .unwrap() + .0 + .iter() + .fold(0, |acc, al| acc + 1 + al.storage_keys.len()); + let mut curr_row: usize = 0; + + // initialize access list section rlc + let mut section_rlc = challenges.keccak_input().map(|_| F::zero()); + // depending on prev row, the accumulator advances by different magnitude + let r20 = challenges.keccak_input().map(|f| f.pow([20, 0, 0, 0])); + let r32 = challenges.keccak_input().map(|f| f.pow([32, 0, 0, 0])); + + for (al_idx, al) in tx.access_list.as_ref().unwrap().0.iter().enumerate() { + curr_row += 1; + let is_final = curr_row == total_rows; + + let field_rlc = + rlc_be_bytes(&al.address.to_fixed_bytes(), challenges.keccak_input()); + section_rlc = section_rlc * r32 + field_rlc; + + let tx_id_next = if curr_row == total_rows { + next_tx.map_or(0, |tx| tx.id) + } else { + tx.id + }; + + self.assign_common_part( + region, + *offset, + Some(tx), + tx_id_next, + TxFieldTag::AccessListAddress, + (al_idx + 1) as u64, + Value::known(al.address.to_scalar().unwrap()), + Value::known(al.address.to_scalar().unwrap()), + )?; + + // 1st phase columns + for (col_anno, col, col_val) in [ + ("block_num", self.block_num, F::from(tx.block_number)), + ("al_idx", self.al_idx, F::from((al_idx + 1) as u64)), + ("sk_idx", self.sk_idx, F::from(0u64)), + ("sks_acc", self.sks_acc, F::from(sks_acc as u64)), + ( + "rlp_tag", + self.rlp_tag, + F::from(usize::from(Tag::AccessListAddress) as u64), + ), + ("is_final", self.is_final, F::from(is_final as u64)), + ("is_access_list", self.is_access_list, F::one()), + ( + "is_access_list_address", + self.is_access_list_address, + F::one(), + ), + ] { + region.assign_advice(|| col_anno, col, *offset, || Value::known(col_val))?; + } + + region.assign_advice(|| "field_rlc", self.field_rlc, *offset, || field_rlc)?; + + // 2nd phase columns + region.assign_advice(|| "rlc", self.section_rlc, *offset, || section_rlc)?; + + *offset += 1; + + for (sk_idx, sk) in al.storage_keys.iter().enumerate() { + curr_row += 1; + sks_acc += 1; + let is_final = curr_row == total_rows; + + let field_rlc = rlc_be_bytes(&sk.to_fixed_bytes(), challenges.keccak_input()); + section_rlc = if sk_idx > 0 { + section_rlc * r32 + field_rlc + } else { + section_rlc * r20 + field_rlc + }; + + let tx_id_next = if curr_row == total_rows { + next_tx.map_or(0, |tx| tx.id) + } else { + tx.id + }; + + self.assign_common_part( + region, + *offset, + Some(tx), + tx_id_next, + TxFieldTag::AccessListStorageKey, + sks_acc as u64, + rlc_be_bytes(&sk.to_fixed_bytes(), challenges.evm_word()), + Value::known(al.address.to_scalar().unwrap()), + )?; + + // 1st phase columns + for (col_anno, col, col_val) in [ + ("block_num", self.block_num, F::from(tx.block_number)), + ("al_idx", self.al_idx, F::from((al_idx + 1) as u64)), + ("sk_idx", self.sk_idx, F::from((sk_idx + 1) as u64)), + ("sks_acc", self.sks_acc, F::from(sks_acc as u64)), + ( + "rlp_tag", + self.rlp_tag, + F::from(usize::from(Tag::AccessListStorageKey) as u64), + ), + ("is_final", self.is_final, F::from(is_final as u64)), + ("is_access_list", self.is_access_list, F::one()), + ( + "is_access_list_storage_key", + self.is_access_list_storage_key, + F::one(), + ), + ] { + region.assign_advice( + || col_anno, + col, + *offset, + || Value::known(col_val), + )?; + } + + // field_rlc to work with section_rlc + region.assign_advice(|| "field_rlc", self.field_rlc, *offset, || field_rlc)?; + + // 2nd phase columns + region.assign_advice(|| "rlc", self.section_rlc, *offset, || section_rlc)?; + + *offset += 1; + } + } + } + + Ok(()) + } + // Assigns to common columns in different parts of tx circuit // 1. 1st all zero row // 2. fixed rows of each tx @@ -2248,6 +3057,7 @@ impl TxCircuitConfig { tag: TxFieldTag, index: u64, value: Value, + access_list_address: Value, ) -> Result, Error> { let (tx_type, tx_id) = if let Some(tx) = tx { (tx.tx_type, tx.id) @@ -2295,9 +3105,32 @@ impl TxCircuitConfig { self.is_l1_msg, F::from(tx_type.is_l1_msg() as u64), ), + ( + "is_eip2930", + self.is_eip2930, + F::from(tx_type.is_eip2930() as u64), + ), + ( + "is_eip1559", + self.is_eip1559, + F::from(tx_type.is_eip1559() as u64), + ), + ( + "is_tx_id_zero", + self.is_tx_id_zero, + F::from((tx_id == 0) as u64), + ), ] { region.assign_advice(|| col_anno, col, offset, || Value::known(col_val))?; } + + region.assign_advice( + || "access_list_address value", + self.tx_table.access_list_address, + offset, + || access_list_address, + )?; + // 2nd phase columns let tx_value_cell = region.assign_advice(|| "tx_value", self.tx_table.value, offset, || value)?; @@ -2351,6 +3184,7 @@ impl TxCircuitConfig { (self.is_final, F::one()), (self.is_calldata, F::one()), (self.calldata_gas_cost_acc, F::zero()), + (self.is_tx_id_zero, F::one()), ] { region.assign_advice(|| "", col, offset, || Value::known(value))?; } @@ -2707,6 +3541,13 @@ impl TxCircuit { next_tx, challenges, )?; + config.assign_access_list_rows( + &mut region, + &mut offset, + tx, + next_tx, + challenges, + )?; } assert!(offset <= calldata_last_row, "{offset}, {calldata_last_row}"); // 3.2 pad calldata with zeros @@ -2873,3 +3714,35 @@ pub(crate) fn get_sign_data( .collect::, halo2_proofs::plonk::Error>>()?; Ok(signatures) } + +/// Returns the RLC of the access list including addresses and storage keys +/// This function provides an alternative routine to calculate access_list_rlc +/// to ascertain the correctness of assignment in witness generation. +pub fn access_list_rlc( + access_list: &Option, + challenges: &Challenges>, +) -> Value { + if access_list.is_some() { + let mut section_rlc = challenges.keccak_input().map(|_| F::zero()); + let r20 = challenges.keccak_input().map(|f| f.pow([20, 0, 0, 0])); + let r32 = challenges.keccak_input().map(|f| f.pow([32, 0, 0, 0])); + + for al in access_list.as_ref().unwrap().0.iter() { + let field_rlc = rlc_be_bytes(&al.address.to_fixed_bytes(), challenges.keccak_input()); + section_rlc = section_rlc * r32 + field_rlc; + + for (sk_idx, sk) in al.storage_keys.iter().enumerate() { + let field_rlc = rlc_be_bytes(&sk.to_fixed_bytes(), challenges.keccak_input()); + section_rlc = if sk_idx > 0 { + section_rlc * r32 + field_rlc + } else { + section_rlc * r20 + field_rlc + }; + } + } + + section_rlc + } else { + Value::known(F::zero()) + } +} diff --git a/zkevm-circuits/src/tx_circuit/test.rs b/zkevm-circuits/src/tx_circuit/test.rs index efe550b4b4..1e25dcbee8 100644 --- a/zkevm-circuits/src/tx_circuit/test.rs +++ b/zkevm-circuits/src/tx_circuit/test.rs @@ -1,8 +1,14 @@ #![allow(unused_imports)] use ethers_core::{ - types::{NameOrAddress, Signature, Transaction as EthTransaction, TransactionRequest}, - utils::{keccak256, rlp, rlp::Decodable}, + types::{ + transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, NameOrAddress, + Signature, Transaction as EthTransaction, TransactionRequest, + }, + utils::{ + keccak256, + rlp::{Decodable, Rlp}, + }, }; use std::cmp::max; @@ -12,7 +18,11 @@ use crate::{ tx_circuit::{dev::TxCircuitTester, get_sign_data}, util::{log2_ceil, unusable_rows}, }; -use eth_types::{address, evm_types::gas_utils::tx_data_gas_cost, word, H256, U256, U64}; +use eth_types::{ + address, + evm_types::gas_utils::{tx_access_list_gas_cost, tx_data_gas_cost}, + word, H256, U256, U64, +}; use halo2_proofs::{ dev::{MockProver, VerifyFailure}, halo2curves::bn256::Fr, @@ -52,7 +62,7 @@ fn build_pre_eip155_tx() -> Transaction { "9cd2288e69623b109e25edc46bc518156498b521e5c162d96e1ab392ff1d9dff" ); - let mut tx = Transaction::new_from_rlp_bytes(PreEip155, signed_bytes, unsigned_bytes); + let mut tx = Transaction::new_from_rlp_bytes(1, PreEip155, signed_bytes, unsigned_bytes); tx.hash = H256(eth_tx_hash); tx.block_number = 1; @@ -82,7 +92,7 @@ fn build_l1_msg_tx() -> Transaction { let raw_tx_rlp_bytes = hex::decode("7ef901b60b825dc0941a258d17bf244c4df02d40343a7626a9d321e10580b901848ef1332e000000000000000000000000ea08a65b1829af779261e768d609e59279b510f2000000000000000000000000f2ec6b6206f6208e8f9b394efc1a01c1cbde77750000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000a4232e87480000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000094478cdd110520a8e733e2acf9e543d2c687ea5239") .expect("decode tx's hex shall not fail"); - let eth_tx = EthTransaction::decode(&rlp::Rlp::new(&raw_tx_rlp_bytes)) + let eth_tx = EthTransaction::decode(&Rlp::new(&raw_tx_rlp_bytes)) .expect("decode tx's rlp bytes shall not fail"); let signed_bytes = eth_tx.rlp().to_vec(); @@ -109,6 +119,47 @@ fn build_l1_msg_tx() -> Transaction { tx } +#[cfg(test)] +fn build_eip1559_tx(id: usize) -> Transaction { + let bytes = "02f90b7b01825cb38520955af4328521cf92558d830a1bff9400fc00900000002c00be4ef8f49c000211000c43830cc4d0b9015504673a0b85b3000bef3e26e01428d1b525a532ea7513b8f21661d0d1d76d3ecb8e1b9f1c923dbfffae4097020c532d1b995b7e3e37a1aa6369386e5939053779abd3597508b00129cd75b800073edec02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f21661d0d1d76d3ecb8e1b9f1c923dbfffae40970bb86c3dc790b0d7291f864244b559b59b30f850a8cfb40dc7c53760375530e5af29fded5e139893252993820686c92b000094b61ba302f01b0f027d40c80d8f70f77d3884776531f80b21d20e5a6b806300024b2c713b4502988e070f96cf3bea50b4811cd5844e13a81b61a8078c761b0b85b3000bef3e26e01428d1b525a532ea7513b80002594ea302f03b9eb369241e4270796e665ea1afac355cb99f0c32078ab8ba00013c08711b06ed871e5a66bebf0af6fb768d343b1d14a04b5b34ab10cf761b0b85b3000bef3e26e01428d1b525a532ea7513b8000143542ef909b0f89b940b85b3000bef3e26e01428d1b525a532ea7513b8f884a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008f8dd94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f8c6a0e1dd9768c9de657aca2536cf1cdd1c4536b13ec81ff764307ea8312aa7a8790da070bc879403c8b875e45ea7afbb591f1fd4bde469db47d5f0e879e44c6798d33ea0f88aa3ad276c350a067c34b2bed705e1a2cd30c7c3154f62ece8ee00939bbd2ea0be11b0e2ba48478671bfcd8fd182e025c26fbfbcf4fdf6952051d6147955a36fa09a1a5a7ef77f3399dea2a1044425aaca7fec294fdfdcacd7a960c9c94d15f0a6a091828b9b711948523369ff1651b6332e98f75bcd940a551dc7247d5af88e71faf8bc945b7e3e37a1aa6369386e5939053779abd3597508f8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000002a0697b2bd7bb2984c4e0dc14c79c987d37818484a62958b9c45a0e8b962f20650fa00000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000000f9018394c7c53760375530e5af29fded5e13989325299382f9016ba00000000000000000000000000000000000000000000000000000000000000010a0000000000000000000000000000000000000000000000000000000000000000ba00000000000000000000000000000000000000000000000000000000000000016a0000000000000000000000000000000000000000000000000000000000000000ea051d155e8243cd6886ab3b36f59778d90f3bbb4af820bc2d4536b23ca13814bfba00000000000000000000000000000000000000000000000000000000000000013a0a7609b0290b911c4b52861d3739b36793fd0e23d9ef78cf2fa96dd1b0cbc764da00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000ca0bda2b1a2a3e35ca431f3c4b50639098537d215591b9ca3db95c24c01795a9981a0000000000000000000000000000000000000000000000000000000000000000df89b94c790b0d7291f864244b559b59b30f850a8cfb40df884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8dd9406ed871e5a66bebf0af6fb768d343b1d14a04b5bf8c6a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000af8bc94f21661d0d1d76d3ecb8e1b9f1c923dbfffae4097f8a5a04d3eb812b43a439547ce41ef251d01e8ad3d0dad3fde6f2bed3d0c0e29dcdd7aa026644b9dbbd32f8882f3abce5ac1575313789ab081b0fe9f3f39c946527bfa27a072fd74a6edf1b99d41f2c81c57f871e198cb7a24fd9861e998221c4aeb776014a0a7609b0290b911c4b52861d3739b36793fd0e23d9ef78cf2fa96dd1b0cbc764da01a3159eb932a0bb66f4d5b9c1cb119796d815774e3c4904b36748d7870d915c2f8dd940f027d40c80d8f70f77d3884776531f80b21d20ef8c6a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8bc941a76bffd6d1fc1660e1d0e0552fde51ddbb120cff8a5a06d5257204ebe7d88fd91ae87941cb2dd9d8062b64ae5a2bd2d28ec40b9fbf6dfa030e699f4646032d62d40ca795ecffcb27a2d9d2859f21626b5a588210198e7a6a0c929f5ae32c0eabfbdd06198210bc49736d88e6501f814a66dd5b2fa59508b3ea0ea52bdd009b752a3e91262d66aae31638bc36b449d247d61d646b87a733d7d5da0877978b096db3b11862d0cdfe5f5b74f30fd7d5d29e8ce80626ed8a8bbef1beef8dd944502988e070f96cf3bea50b4811cd5844e13a81bf8c6a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8dd949eb369241e4270796e665ea1afac355cb99f0c32f8c6a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000cf85994f9a2d7e60a3297e513317ad1d7ce101cc4c6c8f6f842a04b376a11d00750d42abab4d4e465d5dc4d9b1286d77cf0c819f028213ea08bdfa072fd74a6edf1b99d41f2c81c57f871e198cb7a24fd9861e998221c4aeb77601480a0d274986e36e16ec2d4846168d59422f68e4b8ec41690b80bdd2ee65819f238eea03d0394f6daae31ba5a276a3741cc2b3ba79b90024f80df865622a62078e72910"; + let raw_tx_rlp_bytes = hex::decode(bytes).expect("decode tx's hex shall not fail"); + + let eth_tx = EthTransaction::decode(&Rlp::new(&raw_tx_rlp_bytes)) + .expect("decode tx's rlp bytes shall not fail"); + + let eth_tx_req: Eip1559TransactionRequest = (ð_tx).into(); + let typed_tx: TypedTransaction = eth_tx_req.into(); + let rlp_unsigned = typed_tx.rlp().to_vec(); + + let mut tx = + Transaction::new_from_rlp_bytes(1, TxType::Eip1559, raw_tx_rlp_bytes, rlp_unsigned); + + tx.hash = eth_tx.hash; + tx.block_number = 1; + tx.id = id; + tx.chain_id = eth_tx.chain_id.unwrap_or(U256::zero()).as_u64(); + tx.nonce = eth_tx.nonce.as_u64(); + tx.value = eth_tx.value; + tx.gas_price = eth_tx.gas_price.unwrap_or(U256::zero()); + tx.gas = eth_tx.gas.as_u64(); + tx.max_fee_per_gas = eth_tx.max_fee_per_gas.unwrap_or(U256::zero()); + tx.max_priority_fee_per_gas = eth_tx.max_priority_fee_per_gas.unwrap_or(U256::zero()); + tx.call_data = eth_tx.input.to_vec(); + tx.callee_address = eth_tx.to; + tx.caller_address = eth_tx.from; + tx.is_create = eth_tx.to.is_none(); + tx.call_data_length = tx.call_data.len(); + tx.call_data_gas_cost = tx_data_gas_cost(&tx.call_data); + tx.access_list = eth_tx.access_list.clone(); + tx.access_list_gas_cost = tx_access_list_gas_cost(ð_tx.access_list); + tx.tx_data_gas_cost = tx_data_gas_cost(&tx.rlp_signed); + tx.v = eth_tx.v.as_u64(); + tx.r = eth_tx.r; + tx.s = eth_tx.s; + + tx +} + fn run( txs: Vec, chain_id: u64, @@ -135,6 +186,41 @@ fn run( prover.verify_at_rows_par(0..active_row_num, 0..active_row_num) } +#[test] +#[cfg(feature = "scroll")] +fn tx_circuit_1tx_2max_eip1559() { + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 3200; + + let tx = build_eip1559_tx(1); + + assert_eq!( + run::(vec![tx], mock::MOCK_CHAIN_ID, MAX_TXS, MAX_CALLDATA, 0), + Ok(()) + ); +} + +#[test] +#[cfg(feature = "scroll")] +fn tx_circuit_2tx_2max_tx_eip1559() { + const MAX_TXS: usize = 2; + const MAX_CALLDATA: usize = 6400; + + let tx1 = build_eip1559_tx(1); + let tx2 = build_eip1559_tx(2); + + assert_eq!( + run::( + vec![tx1, tx2], + mock::MOCK_CHAIN_ID, + MAX_TXS, + MAX_CALLDATA, + 0 + ), + Ok(()) + ); +} + #[test] #[cfg(feature = "scroll")] fn tx_circuit_2tx_2max_tx() { diff --git a/zkevm-circuits/src/witness/l1_msg.rs b/zkevm-circuits/src/witness/l1_msg.rs index 1eb13bb916..70b5d2e37a 100644 --- a/zkevm-circuits/src/witness/l1_msg.rs +++ b/zkevm-circuits/src/witness/l1_msg.rs @@ -4,7 +4,7 @@ use crate::{ rlp_fsm::{MAX_TAG_LENGTH_OF_LIST, N_BYTES_CALLDATA}, Format::L1MsgHash, RomTableRow, - Tag::{BeginList, Data, EndList, Gas, Nonce, Sender, To, TxType, Value as TxValue}, + Tag::{BeginObject, Data, EndObject, Gas, Nonce, Sender, To, TxType, Value as TxValue}, }, }; use ethers_core::utils::rlp::Encodable; @@ -20,17 +20,17 @@ impl Encodable for L1MsgTx { pub fn rom_table_rows() -> Vec { let rows = vec![ - (TxType, BeginList, 1, vec![1]), - (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![2]), + (TxType, BeginObject, 1, vec![1]), + (BeginObject, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![2]), (Nonce, Gas, N_BYTES_U64, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![5]), (TxValue, Data, N_BYTES_WORD, vec![6]), (Data, Sender, N_BYTES_CALLDATA, vec![7]), - (Sender, EndList, N_BYTES_ACCOUNT_ADDRESS, vec![8]), - (EndList, EndList, 0, vec![9]), + (Sender, EndObject, N_BYTES_ACCOUNT_ADDRESS, vec![8]), + (EndObject, EndObject, 0, vec![9]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() diff --git a/zkevm-circuits/src/witness/rlp_fsm.rs b/zkevm-circuits/src/witness/rlp_fsm.rs index bb2c2d9bc0..2b46ada1fd 100644 --- a/zkevm-circuits/src/witness/rlp_fsm.rs +++ b/zkevm-circuits/src/witness/rlp_fsm.rs @@ -1,6 +1,6 @@ use eth_types::{Address, Field, H160, U256}; use gadgets::{impl_expr, util::Expr}; -use halo2_proofs::{circuit::Value, plonk::Expression}; +use halo2_proofs::{circuit::Value, halo2curves::ff::PrimeField, plonk::Expression}; use strum_macros::EnumIter; use crate::util::Challenges; @@ -54,22 +54,22 @@ impl ValueTagLength for Vec { } } -// return the tag length of the top-level BeginList tag +// return the tag length of the top-level BeginObject tag pub(crate) fn get_rlp_len_tag_length(rlp_bytes: &[u8]) -> u32 { - let begin_list_byte = if rlp_bytes[0] < 0xc0 { + let begin_object_byte = if rlp_bytes[0] < 0xc0 { // it's eip2718 (first byte is transaction type) rlp_bytes[1] } else { rlp_bytes[0] }; - assert!(begin_list_byte >= 0xc0); - if begin_list_byte < 0xf8 { + assert!(begin_object_byte >= 0xc0); + if begin_object_byte < 0xf8 { // list 1 } else { // long_list - (begin_list_byte - 0xf7).into() + (begin_object_byte - 0xf7).into() } } @@ -79,14 +79,14 @@ pub enum Tag { #[default] /// Tag that marks the beginning of a list /// whose value gives the length of bytes of this list. - BeginList = 4, + BeginObject = 4, /// Tag that marks the ending of a list and /// it does not consume any byte. - EndList, - /// Special case of BeginList in which each item's key is + EndObject, + /// Special case of BeginObject in which each item's key is /// an increasing integer starting from 1. BeginVector, - /// Special case of EndList + /// Special case of EndObject EndVector, // Pre EIP-155 @@ -154,18 +154,28 @@ impl Tag { pub fn is_list(&self) -> bool { matches!( self, - Self::BeginList | Self::BeginVector | Self::EndList | Self::EndVector + Self::BeginObject | Self::BeginVector | Self::EndObject | Self::EndVector ) } - /// If the tag is BeginList or BeginVector + /// If the tag is BeginObject or BeginVector pub fn is_begin(&self) -> bool { - matches!(self, Self::BeginList | Self::BeginVector) + matches!(self, Self::BeginObject | Self::BeginVector) } - /// If the tag is EndList or EndVector + /// If the tag is EndObject or EndVector pub fn is_end(&self) -> bool { - matches!(self, Self::EndList | Self::EndVector) + matches!(self, Self::EndObject | Self::EndVector) + } + + /// If the tag is AccessListAddress + pub fn is_access_list_address(&self) -> bool { + matches!(self, Self::AccessListAddress) + } + + /// If the tag is AccessListStorageKey + pub fn is_access_list_storage_key(&self) -> bool { + matches!(self, Self::AccessListStorageKey) } } @@ -212,8 +222,8 @@ use crate::{ TxSignEip1559, TxSignEip2930, TxSignPreEip155, }, Tag::{ - AccessListAddress, AccessListStorageKey, BeginList, BeginVector, ChainId, Data, - EndList, EndVector, Gas, GasPrice, MaxFeePerGas, MaxPriorityFeePerGas, Nonce, SigR, + AccessListAddress, AccessListStorageKey, BeginObject, BeginVector, ChainId, Data, + EndObject, EndVector, Gas, GasPrice, MaxFeePerGas, MaxPriorityFeePerGas, Nonce, SigR, SigS, SigV, To, TxType, Value as TxValue, Zero1, Zero2, }, }, @@ -227,7 +237,7 @@ pub(crate) const N_BYTES_CALLDATA: usize = 1 << 24; fn eip155_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), + (BeginObject, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -236,10 +246,10 @@ fn eip155_tx_sign_rom_table_rows() -> Vec { (Data, ChainId, N_BYTES_CALLDATA, vec![7]), (ChainId, Zero1, N_BYTES_U64, vec![8]), (Zero1, Zero2, 1, vec![9]), - (Zero2, EndList, 1, vec![10]), - (EndList, EndList, 0, vec![11]), + (Zero2, EndObject, 1, vec![10]), + (EndObject, EndObject, 0, vec![11]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -249,7 +259,7 @@ fn eip155_tx_sign_rom_table_rows() -> Vec { fn eip155_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), + (BeginObject, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -258,10 +268,10 @@ fn eip155_tx_hash_rom_table_rows() -> Vec { (Data, SigV, N_BYTES_CALLDATA, vec![7]), (SigV, SigR, N_BYTES_U64, vec![8]), (SigR, SigS, N_BYTES_WORD, vec![9]), - (SigS, EndList, N_BYTES_WORD, vec![10]), - (EndList, EndList, 0, vec![11]), + (SigS, EndObject, N_BYTES_WORD, vec![10]), + (EndObject, EndObject, 0, vec![11]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -271,16 +281,16 @@ fn eip155_tx_hash_rom_table_rows() -> Vec { pub fn pre_eip155_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), + (BeginObject, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![5]), (TxValue, Data, N_BYTES_WORD, vec![6]), - (Data, EndList, N_BYTES_CALLDATA, vec![7]), - (EndList, EndList, 0, vec![8]), + (Data, EndObject, N_BYTES_CALLDATA, vec![7]), + (EndObject, EndObject, 0, vec![8]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -290,7 +300,7 @@ pub fn pre_eip155_tx_sign_rom_table_rows() -> Vec { pub fn pre_eip155_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), + (BeginObject, Nonce, MAX_TAG_LENGTH_OF_LIST, vec![1]), (Nonce, GasPrice, N_BYTES_U64, vec![2]), (GasPrice, Gas, N_BYTES_WORD, vec![3]), (Gas, To, N_BYTES_U64, vec![4]), @@ -299,10 +309,10 @@ pub fn pre_eip155_tx_hash_rom_table_rows() -> Vec { (Data, SigV, N_BYTES_CALLDATA, vec![7]), (SigV, SigR, N_BYTES_U64, vec![8]), (SigR, SigS, N_BYTES_WORD, vec![9]), - (SigS, EndList, N_BYTES_WORD, vec![10]), - (EndList, EndList, 0, vec![11]), + (SigS, EndObject, N_BYTES_WORD, vec![10]), + (EndObject, EndObject, 0, vec![11]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -312,8 +322,8 @@ pub fn pre_eip155_tx_hash_rom_table_rows() -> Vec { pub fn eip2930_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (TxType, BeginList, 1, vec![1]), - (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), + (TxType, BeginObject, 1, vec![1]), + (BeginObject, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), (ChainId, Nonce, N_BYTES_U64, vec![3]), (Nonce, GasPrice, N_BYTES_U64, vec![4]), (GasPrice, Gas, N_BYTES_WORD, vec![5]), @@ -322,9 +332,9 @@ pub fn eip2930_tx_sign_rom_table_rows() -> Vec { (TxValue, Data, N_BYTES_WORD, vec![8]), (Data, BeginVector, N_BYTES_CALLDATA, vec![9, 10]), (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![20]), // access_list is none - (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![11]), + (BeginVector, BeginObject, MAX_TAG_LENGTH_OF_LIST, vec![11]), ( - BeginList, + BeginObject, AccessListAddress, MAX_TAG_LENGTH_OF_LIST, vec![12], @@ -350,13 +360,13 @@ pub fn eip2930_tx_sign_rom_table_rows() -> Vec { N_BYTES_WORD, vec![15, 16], ), // keep parsing storage_keys - (EndVector, EndList, 0, vec![18, 19]), - (EndList, EndVector, 0, vec![20]), // finished parsing access_list - (EndList, BeginList, 0, vec![11]), // parse another access_list entry - (EndVector, EndList, 0, vec![21]), - (EndList, EndList, 0, vec![22]), + (EndVector, EndObject, 0, vec![18, 19]), + (EndObject, EndVector, 0, vec![20]), // finished parsing access_list + (EndObject, BeginObject, 0, vec![11]), // parse another access_list entry + (EndVector, EndObject, 0, vec![21]), + (EndObject, EndObject, 0, vec![22]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -366,8 +376,8 @@ pub fn eip2930_tx_sign_rom_table_rows() -> Vec { pub fn eip2930_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (TxType, BeginList, 1, vec![1]), - (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), + (TxType, BeginObject, 1, vec![1]), + (BeginObject, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), (ChainId, Nonce, N_BYTES_U64, vec![3]), (Nonce, GasPrice, N_BYTES_U64, vec![4]), (GasPrice, Gas, N_BYTES_WORD, vec![5]), @@ -376,9 +386,9 @@ pub fn eip2930_tx_hash_rom_table_rows() -> Vec { (TxValue, Data, N_BYTES_WORD, vec![8]), (Data, BeginVector, N_BYTES_CALLDATA, vec![9, 10]), (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![20]), // access_list is none - (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![11]), + (BeginVector, BeginObject, MAX_TAG_LENGTH_OF_LIST, vec![11]), ( - BeginList, + BeginObject, AccessListAddress, MAX_TAG_LENGTH_OF_LIST, vec![12], @@ -404,16 +414,16 @@ pub fn eip2930_tx_hash_rom_table_rows() -> Vec { N_BYTES_WORD, vec![15, 16], ), // keep parsing storage_keys - (EndVector, EndList, 0, vec![18, 19]), - (EndList, EndVector, 0, vec![20]), // finished parsing access_list - (EndList, BeginList, 0, vec![11]), // parse another access_list entry + (EndVector, EndObject, 0, vec![18, 19]), + (EndObject, EndVector, 0, vec![20]), // finished parsing access_list + (EndObject, BeginObject, 0, vec![11]), // parse another access_list entry (EndVector, SigV, 0, vec![21]), (SigV, SigR, N_BYTES_U64, vec![22]), (SigR, SigS, N_BYTES_WORD, vec![23]), - (SigS, EndList, N_BYTES_WORD, vec![24]), - (EndList, EndList, 0, vec![25]), + (SigS, EndObject, N_BYTES_WORD, vec![24]), + (EndObject, EndObject, 0, vec![25]), // used to exit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -423,8 +433,8 @@ pub fn eip2930_tx_hash_rom_table_rows() -> Vec { pub fn eip1559_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (TxType, BeginList, 1, vec![1]), - (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), + (TxType, BeginObject, 1, vec![1]), + (BeginObject, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), (ChainId, Nonce, N_BYTES_U64, vec![3]), (Nonce, MaxPriorityFeePerGas, N_BYTES_U64, vec![4]), (MaxPriorityFeePerGas, MaxFeePerGas, N_BYTES_WORD, vec![5]), @@ -434,9 +444,9 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { (TxValue, Data, N_BYTES_WORD, vec![9]), (Data, BeginVector, N_BYTES_CALLDATA, vec![10, 11]), (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![21]), // access_list is none - (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![12]), + (BeginVector, BeginObject, MAX_TAG_LENGTH_OF_LIST, vec![12]), ( - BeginList, + BeginObject, AccessListAddress, MAX_TAG_LENGTH_OF_LIST, vec![13], @@ -462,16 +472,16 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { N_BYTES_WORD, vec![16, 17], ), // keep parsing storage_keys - (EndVector, EndList, 0, vec![19, 20]), - (EndList, EndVector, 0, vec![21]), // finished parsing access_list - (EndList, BeginList, 0, vec![12]), // parse another access_list entry + (EndVector, EndObject, 0, vec![19, 20]), + (EndObject, EndVector, 0, vec![21]), // finished parsing access_list + (EndObject, BeginObject, 0, vec![12]), // parse another access_list entry (EndVector, SigV, 0, vec![22]), (SigV, SigR, N_BYTES_U64, vec![23]), (SigR, SigS, N_BYTES_WORD, vec![24]), - (SigS, EndList, N_BYTES_WORD, vec![25]), - (EndList, EndList, 0, vec![26]), + (SigS, EndObject, N_BYTES_WORD, vec![25]), + (EndObject, EndObject, 0, vec![26]), // used to exit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -481,8 +491,8 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { pub fn eip1559_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (TxType, BeginList, 1, vec![1]), - (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), + (TxType, BeginObject, 1, vec![1]), + (BeginObject, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![2]), (ChainId, Nonce, N_BYTES_U64, vec![3]), (Nonce, MaxPriorityFeePerGas, N_BYTES_U64, vec![4]), (MaxPriorityFeePerGas, MaxFeePerGas, N_BYTES_WORD, vec![5]), @@ -492,9 +502,9 @@ pub fn eip1559_tx_sign_rom_table_rows() -> Vec { (TxValue, Data, N_BYTES_WORD, vec![9]), (Data, BeginVector, N_BYTES_CALLDATA, vec![10, 11]), (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![21]), // access_list is none - (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![12]), + (BeginVector, BeginObject, MAX_TAG_LENGTH_OF_LIST, vec![12]), ( - BeginList, + BeginObject, AccessListAddress, MAX_TAG_LENGTH_OF_LIST, vec![13], @@ -520,13 +530,13 @@ pub fn eip1559_tx_sign_rom_table_rows() -> Vec { N_BYTES_WORD, vec![16, 17], ), // keep parsing storage_keys - (EndVector, EndList, 0, vec![19, 20]), - (EndList, EndVector, 0, vec![21]), // finished parsing access_list - (EndList, BeginList, 0, vec![12]), // parse another access_list entry - (EndVector, EndList, 0, vec![22]), - (EndList, EndList, 0, vec![23]), + (EndVector, EndObject, 0, vec![19, 20]), + (EndObject, EndVector, 0, vec![21]), // finished parsing access_list + (EndObject, BeginObject, 0, vec![12]), // parse another access_list entry + (EndVector, EndObject, 0, vec![22]), + (EndObject, EndObject, 0, vec![23]), // used to emit TxGasCostInL1 - (EndList, BeginList, 0, vec![]), + (EndObject, BeginObject, 0, vec![]), ]; rows.into_iter() @@ -599,6 +609,11 @@ impl From for usize { value as usize } } +impl From for u64 { + fn from(value: Format) -> Self { + value as u64 + } +} impl Format { /// The ROM table for format @@ -706,6 +721,14 @@ pub struct RlpTable { pub is_output: bool, /// If current tag's value is None. pub is_none: bool, + /// The index of access list address + /// Corresponding tag is AccessListAddress + pub access_list_idx: u64, + /// The index of the storage key + /// The combination (access_list_idx, storage_key_idx) + /// uniquely identifies a storage key value + /// Corresponding tag is AccessListStorageKey + pub storage_key_idx: u64, } /// State Machine @@ -740,6 +763,150 @@ pub struct StateMachine { pub gas_cost_acc: Value, } +#[derive(Clone, Debug, Default)] +pub enum StackOp { + #[default] + Init, + Push, + Pop, + Update, +} + +/// Rlp Decoding Witness +/// Using simulated stack constraints to make sure all bytes in nested structure are correctly +/// decoded +#[derive(Clone, Debug, Default)] +pub struct RlpStackOp { + /// Key: rlc of (tx_id, format, depth, al_idx, sk_idx) + pub id: Value, + /// Transaction Id + pub tx_id: u64, + /// Format + pub format: Format, + /// depth + pub depth: usize, + /// Op Counter, similar to rw counter + pub byte_idx: usize, + /// Value + pub value: usize, + /// Value Previous + pub value_prev: usize, + /// The stack operation performed at step. + pub stack_op: StackOp, + /// Access list index + pub al_idx: u64, + /// Storage key index + pub sk_idx: u64, +} + +fn stack_op_id(components: [u64; 5], challenge: Value) -> Value { + components + .into_iter() + .fold(Value::known(F::ZERO), |mut rlc, num| { + rlc = rlc * challenge + Value::known(F::from(num)); + rlc + }) +} + +impl RlpStackOp { + pub fn init(tx_id: u64, format: Format, value: usize, challenge: Value) -> Self { + Self { + id: stack_op_id([tx_id, format as u64, 0, 0, 0], challenge), + tx_id, + format, + depth: 0, + byte_idx: 0, + value, + value_prev: 0, + stack_op: StackOp::Init, + al_idx: 0, + sk_idx: 0, + } + } + #[allow(clippy::too_many_arguments)] + pub fn push( + tx_id: u64, + format: Format, + byte_idx: usize, + depth: usize, + value: usize, + al_idx: u64, + sk_idx: u64, + challenge: Value, + ) -> Self { + Self { + id: stack_op_id( + [tx_id, format as u64, depth as u64, al_idx, sk_idx], + challenge, + ), + tx_id, + format, + depth, + byte_idx, + value, + value_prev: 0, + stack_op: StackOp::Push, + al_idx, + sk_idx, + } + } + #[allow(clippy::too_many_arguments)] + pub fn pop( + tx_id: u64, + format: Format, + byte_idx: usize, + depth: usize, + value: usize, + value_prev: usize, + al_idx: u64, + sk_idx: u64, + challenge: Value, + ) -> Self { + Self { + id: stack_op_id( + [tx_id, format as u64, depth as u64, al_idx, sk_idx], + challenge, + ), + tx_id, + format, + depth, + byte_idx, + value, + value_prev, + stack_op: StackOp::Pop, + al_idx, + sk_idx, + } + } + #[allow(clippy::too_many_arguments)] + pub fn update( + tx_id: u64, + format: Format, + byte_idx: usize, + depth: usize, + value: usize, + al_idx: u64, + sk_idx: u64, + challenge: Value, + ) -> Self { + Self { + id: stack_op_id( + [tx_id, format as u64, depth as u64, al_idx, sk_idx], + challenge, + ), + tx_id, + format, + depth, + byte_idx, + value, + value_prev: value + 1, + stack_op: StackOp::Update, + al_idx, + sk_idx, + } + } +} + /// Represents the witness in a single row of the RLP circuit. #[derive(Clone, Debug)] pub struct RlpFsmWitnessRow { @@ -747,6 +914,8 @@ pub struct RlpFsmWitnessRow { pub rlp_table: RlpTable, /// The state machine witness. pub state_machine: StateMachine, + /// The rlp decoding table witness + pub rlp_decoding_table: RlpStackOp, } /// The RlpFsmWitnessGen trait is implemented by data types who's RLP encoding can diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 5680194c1b..de00a176e1 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -3,7 +3,7 @@ use crate::{ table::TxContextFieldTag, util::{rlc_be_bytes, Challenges}, witness::{ - rlp_fsm::SmState, + rlp_fsm::{RlpStackOp, SmState}, DataTable, Format, Format::{ L1MsgHash, TxHashEip155, TxHashEip1559, TxHashEip2930, TxHashPreEip155, TxSignEip155, @@ -12,12 +12,12 @@ use crate::{ RlpFsmWitnessGen, RlpFsmWitnessRow, RlpTable, RlpTag, State, State::DecodeTagStart, StateMachine, - Tag::{EndList, EndVector}, + Tag::{EndObject, EndVector}, }, }; use bus_mapping::circuit_input_builder::{self, get_dummy_tx_hash, TxL1Fee}; use eth_types::{ - evm_types::gas_utils::tx_data_gas_cost, + evm_types::gas_utils::{tx_access_list_gas_cost, tx_data_gas_cost}, geth_types::{access_list_size, TxType, TxType::PreEip155}, sign_types::{ biguint_to_32bytes_le, ct_option_ok_or, get_dummy_tx, recover_pk2, SignData, SECP256K1_Q, @@ -57,6 +57,10 @@ pub struct Transaction { pub gas: u64, /// The gas price pub gas_price: Word, + /// Max fee per gas (EIP1559) + pub max_fee_per_gas: Word, + /// Max priority fee per gas (EIP1559) + pub max_priority_fee_per_gas: Word, /// The caller address pub caller_address: Address, /// The callee address @@ -71,6 +75,8 @@ pub struct Transaction { pub call_data_length: usize, /// The gas cost for transaction call data pub call_data_gas_cost: u64, + /// The gas cost for access list (EIP 2930) + pub access_list_gas_cost: u64, /// The gas cost for rlp-encoded bytes of unsigned tx pub tx_data_gas_cost: u64, /// Chain ID as per EIP-155. @@ -158,7 +164,7 @@ impl Transaction { pub fn table_assignments_fixed( &self, challenges: Challenges>, - ) -> Vec<[Value; 4]> { + ) -> Vec<[Value; 5]> { let tx_hash_be_bytes = keccak256(&self.rlp_signed); let tx_sign_hash_be_bytes = keccak256(&self.rlp_unsigned); let (access_list_address_size, access_list_storage_key_size) = @@ -170,6 +176,7 @@ impl Transaction { Value::known(F::from(TxContextFieldTag::Nonce as u64)), Value::known(F::zero()), Value::known(F::from(self.nonce)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), @@ -178,18 +185,21 @@ impl Transaction { challenges .evm_word() .map(|challenge| rlc::value(&self.gas_price.to_le_bytes(), challenge)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::Gas as u64)), Value::known(F::zero()), Value::known(F::from(self.gas)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::CallerAddress as u64)), Value::known(F::zero()), Value::known(self.caller_address.to_scalar().unwrap()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), @@ -201,12 +211,14 @@ impl Transaction { .to_scalar() .unwrap(), ), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::IsCreate as u64)), Value::known(F::zero()), Value::known(F::from(self.is_create as u64)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), @@ -215,114 +227,131 @@ impl Transaction { challenges .evm_word() .map(|challenge| rlc::value(&self.value.to_le_bytes(), challenge)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::CallDataRLC as u64)), Value::known(F::zero()), rlc_be_bytes(&self.call_data, challenges.keccak_input()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::CallDataLength as u64)), Value::known(F::zero()), Value::known(F::from(self.call_data_length as u64)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::CallDataGasCost as u64)), Value::known(F::zero()), Value::known(F::from(self.call_data_gas_cost)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxDataGasCost as u64)), Value::known(F::zero()), Value::known(F::from(self.tx_data_gas_cost)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::ChainID as u64)), Value::known(F::zero()), Value::known(F::from(self.chain_id)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::SigV as u64)), Value::known(F::zero()), Value::known(F::from(self.v)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::SigR as u64)), Value::known(F::zero()), rlc_be_bytes(&self.r.to_be_bytes(), challenges.evm_word()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::SigS as u64)), Value::known(F::zero()), rlc_be_bytes(&self.s.to_be_bytes(), challenges.evm_word()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxSignLength as u64)), Value::known(F::zero()), Value::known(F::from(self.rlp_unsigned.len() as u64)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxSignRLC as u64)), Value::known(F::zero()), rlc_be_bytes(&self.rlp_unsigned, challenges.keccak_input()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxSignHash as u64)), Value::known(F::zero()), rlc_be_bytes(&tx_sign_hash_be_bytes, challenges.evm_word()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxHashLength as u64)), Value::known(F::zero()), Value::known(F::from(self.rlp_signed.len() as u64)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxHashRLC as u64)), Value::known(F::zero()), rlc_be_bytes(&self.rlp_signed, challenges.keccak_input()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxHash as u64)), Value::known(F::zero()), rlc_be_bytes(&tx_hash_be_bytes, challenges.evm_word()), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::TxType as u64)), Value::known(F::zero()), Value::known(F::from(self.tx_type as u64)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::AccessListAddressesLen as u64)), Value::known(F::zero()), Value::known(F::from(access_list_address_size)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::AccessListStorageKeysLen as u64)), Value::known(F::zero()), Value::known(F::from(access_list_storage_key_size)), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::AccessListRLC as u64)), Value::known(F::zero()), - // TODO: need to check if it's correct with RLP. rlc_be_bytes( &self .access_list @@ -331,12 +360,32 @@ impl Transaction { .unwrap_or_default(), challenges.keccak_input(), ), + Value::known(F::zero()), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::MaxFeePerGas as u64)), + Value::known(F::zero()), + challenges + .evm_word() + .map(|challenge| rlc::value(&self.max_fee_per_gas.to_le_bytes(), challenge)), + Value::known(F::zero()), + ], + [ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::MaxPriorityFeePerGas as u64)), + Value::known(F::zero()), + challenges.evm_word().map(|challenge| { + rlc::value(&self.max_priority_fee_per_gas.to_le_bytes(), challenge) + }), + Value::known(F::zero()), ], [ Value::known(F::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::BlockNumber as u64)), Value::known(F::zero()), Value::known(F::from(self.block_number)), + Value::known(F::zero()), ], ]; @@ -347,7 +396,7 @@ impl Transaction { pub fn table_assignments_dyn( &self, _challenges: Challenges>, - ) -> Vec<[Value; 4]> { + ) -> Vec<[Value; 5]> { self.call_data .iter() .enumerate() @@ -357,11 +406,44 @@ impl Transaction { Value::known(F::from(TxContextFieldTag::CallData as u64)), Value::known(F::from(idx as u64)), Value::known(F::from(*byte as u64)), + Value::known(F::zero()), ] }) .collect() } + /// Assignments for tx table access list + pub fn table_assignments_access_list_dyn( + &self, + challenges: Challenges>, + ) -> Vec<[Value; 5]> { + let mut assignments: Vec<[Value; 5]> = vec![]; + + if self.access_list.is_some() { + for (al_idx, al) in self.access_list.as_ref().unwrap().0.iter().enumerate() { + assignments.push([ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::AccessListAddress as u64)), + Value::known(F::from((al_idx + 1) as u64)), + Value::known(al.address.to_scalar().unwrap()), + Value::known(al.address.to_scalar().unwrap()), + ]); + + for (sk_idx, sk) in al.storage_keys.iter().enumerate() { + assignments.push([ + Value::known(F::from(self.id as u64)), + Value::known(F::from(TxContextFieldTag::AccessListStorageKey as u64)), + Value::known(F::from(sk_idx as u64)), + rlc_be_bytes(&sk.to_fixed_bytes(), challenges.evm_word()), + Value::known(al.address.to_scalar().unwrap()), + ]); + } + } + } + + assignments + } + pub(crate) fn gen_rlp_witness( &self, is_hash: bool, @@ -423,12 +505,28 @@ impl Transaction { byte_idx: 0, depth: 0, }; + + // Queue up stack operations + let mut stack_ops: Vec> = vec![]; + // This variable tracks how many bytes were left on a depth level before the last UPDATE. + // Unlike the remaining_bytes variable, it tracks the byte count before the UPDATE + // operation took place. This is a utility variable for correctly filling the + // value_prev field in a POP record. + let mut prev_bytes_on_depth: [usize; 4] = [0, 0, 0, 0]; + // When we are decoding a vector of element type `t`, at the beginning // we actually do not know the next tag is `EndVector` or not. After we // parsed the current tag, if the remaining bytes to decode in this layer // is zero, then the next tag is `EndVector`. let mut cur_rom_row = vec![0]; let mut remaining_bytes = vec![rlp_bytes.len()]; + // initialize stack + stack_ops.push(RlpStackOp::init( + tx_id, + format, + rlp_bytes.len(), + keccak_rand, + )); let mut witness_table_idx = 0; // This map keeps track @@ -440,6 +538,10 @@ impl Transaction { let mut is_none; let mut rlp_tag; let mut lb_len = 0; + // These two variables keep track + // unique identifier of addresses and storage keys included in access list + let mut access_list_idx: u64 = 0; + let mut storage_key_idx: u64 = 0; loop { // default behavior @@ -458,18 +560,47 @@ impl Transaction { .expect("remaining_bytes shall not be empty"), 0 ); + if cur.depth == 1 { assert_eq!(remaining_bytes.len(), 1); assert_eq!(remaining_bytes[0], 0); assert_eq!(cur.byte_idx, rlp_bytes.len() - 1); is_output = true; rlp_tag = RlpTag::RLC; + } else if cur.depth == 4 { + // end of storage keys list + // note: depth alone currently is sufficient to ascertain + // the end of a storage keys list as there's no other nested + // structure at depth 4 specified in EIP standards + storage_key_idx = 0; + } else if cur.depth == 2 { + // end of access list + // note: depth alone currently is sufficient to ascertain + // the end of an access list as there's no other nested + // structure at depth 2 specified in EIP standards + access_list_idx = 0; } else if cur.depth == 0 { // emit GasCost is_output = true; rlp_tag = RlpTag::GasCost; } + if !remaining_bytes.is_empty() { + let byte_remained = *remaining_bytes.last().unwrap(); + + stack_ops.push(RlpStackOp::pop( + tx_id, + format, + cur.byte_idx + 1, + cur.depth - 1, + byte_remained, + prev_bytes_on_depth[cur.depth - 1], + access_list_idx, + storage_key_idx, + keccak_rand, + )); + } + // state transitions // if cur.depth == 0 then we are at the end of decoding if cur.depth > 0 { @@ -478,11 +609,42 @@ impl Transaction { next.state = DecodeTagStart; } else { let byte_value = rlp_bytes[cur.byte_idx]; + + if byte_value > 0x80 && byte_value < 0xb8 { + // detect start of access list address + if cur.tag.is_access_list_address() { + access_list_idx += 1; + } + // detect start of access list storage key + if cur.tag.is_access_list_storage_key() { + storage_key_idx += 1; + } + } + if let Some(rem) = remaining_bytes.last_mut() { // read one more byte assert!(*rem >= 1); + + if !(0xc0..=0xf7).contains(&byte_value) { + // Note: if the byte_value is in the range [0xc0..=0xf7], + // then we anticipate a PUSH onto a higher depth. + + // add stack op on same depth + stack_ops.push(RlpStackOp::update( + tx_id, + format, + cur.byte_idx + 1, + cur.depth, + *rem - 1, + access_list_idx, + storage_key_idx, + keccak_rand, + )); + } + *rem -= 1; } + if byte_value < 0x80 { // assertions assert!(!cur.tag.is_list()); @@ -543,9 +705,26 @@ impl Transaction { // current list should be subtracted by // the number of bytes of the new list. assert!(*rem >= num_bytes_of_new_list); + prev_bytes_on_depth[cur.depth] = *rem + 1; + *rem -= num_bytes_of_new_list; } remaining_bytes.push(num_bytes_of_new_list); + + let al_inc: u64 = if cur.depth == 2 { 1 } else { 0 }; + let sk_inc: u64 = if cur.depth == 3 { 1 } else { 0 }; + + stack_ops.push(RlpStackOp::push( + tx_id, + format, + cur.byte_idx + 1, + cur.depth + 1, + num_bytes_of_new_list, + access_list_idx + al_inc, + storage_key_idx + sk_inc, + keccak_rand, + )); + next.depth = cur.depth + 1; next.state = DecodeTagStart; } else { @@ -565,6 +744,26 @@ impl Transaction { State::Bytes => { if let Some(rem) = remaining_bytes.last_mut() { assert!(*rem >= 1); + + let sk_inc = + if cur.depth == 4 && cur.tag_idx >= cur.tag_length && *rem - 1 > 0 { + 1 + } else { + 0 + }; + + // add stack op on same depth + stack_ops.push(RlpStackOp::update( + tx_id, + format, + cur.byte_idx + 1, + cur.depth, + *rem - 1, + access_list_idx, + storage_key_idx + sk_inc, + keccak_rand, + )); + *rem -= 1; } if cur.tag_idx < cur.tag_length { @@ -591,6 +790,19 @@ impl Transaction { State::LongBytes => { if let Some(rem) = remaining_bytes.last_mut() { assert!(*rem >= 1); + + // add stack op on same depth + stack_ops.push(RlpStackOp::update( + tx_id, + format, + cur.byte_idx + 1, + cur.depth, + *rem - 1, + access_list_idx, + storage_key_idx, + keccak_rand, + )); + *rem -= 1; } @@ -615,6 +827,21 @@ impl Transaction { if let Some(rem) = remaining_bytes.last_mut() { // read one more byte assert!(*rem >= 1); + + // add stack op on same depth + if cur.tag_idx < cur.tag_length { + stack_ops.push(RlpStackOp::update( + tx_id, + format, + cur.byte_idx + 1, + cur.depth, + *rem - 1, + access_list_idx, + storage_key_idx, + keccak_rand, + )); + } + *rem -= 1; } if cur.tag_idx < cur.tag_length { @@ -630,9 +857,25 @@ impl Transaction { } if let Some(rem) = remaining_bytes.last_mut() { assert!(*rem >= lb_len); + prev_bytes_on_depth[cur.depth] = *rem + 1; + *rem -= lb_len; } remaining_bytes.push(lb_len); + + let al_inc: u64 = if cur.depth == 2 { 1 } else { 0 }; + let sk_inc: u64 = if cur.depth == 3 { 1 } else { 0 }; + + stack_ops.push(RlpStackOp::push( + tx_id, + format, + cur.byte_idx + 1, + cur.depth + 1, + lb_len, + access_list_idx + al_inc, + storage_key_idx + sk_inc, + keccak_rand, + )); next.depth = cur.depth + 1; next.state = DecodeTagStart; } @@ -669,7 +912,7 @@ impl Transaction { cur_rom_row = rom_table[row].tag_next_idx.clone(); if next.tag.is_end() { - // Since the EndList or EndVector tag does not read any byte from the data + // Since the EndObject or EndVector tag does not read any byte from the data // table. next.byte_idx = cur.byte_idx; } else { @@ -710,6 +953,8 @@ impl Transaction { tag_length, is_output, is_none, + access_list_idx, + storage_key_idx, }, state_machine: StateMachine { state: cur.state, @@ -725,14 +970,53 @@ impl Transaction { bytes_rlc, gas_cost_acc, }, + // The stack operations will be later sorted and assigned + rlp_decoding_table: RlpStackOp::default(), }); + witness_table_idx += 1; - if cur.tag == EndList && cur.depth == 0 { + if cur.tag == EndObject && cur.depth == 0 { break; } cur = next; } + + assert_eq!( + stack_ops.len(), + witness.len(), + "Number of stack_ops must be equal to witness length" + ); + + // Sort the RlpStackOps and assign to the RlpDecodingTable part of witness + stack_ops.sort_by(|a, b| { + if ( + a.tx_id, + a.format as u64, + a.depth, + a.al_idx, + a.sk_idx, + a.byte_idx, + a.stack_op.clone() as u64, + ) > ( + b.tx_id, + b.format as u64, + b.depth, + a.al_idx, + a.sk_idx, + b.byte_idx, + b.stack_op.clone() as u64, + ) { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Less + } + }); + + for (idx, op) in stack_ops.into_iter().enumerate() { + witness[idx].rlp_decoding_table = op; + } + // filling up the `tag_next` col of the witness table let mut idx = 0; for (witness_idx, rom_table_row) in tag_rom_row_map { @@ -748,12 +1032,13 @@ impl Transaction { #[cfg(test)] pub(crate) fn new_from_rlp_bytes( + tx_id: usize, tx_type: TxType, signed_bytes: Vec, unsigned_bytes: Vec, ) -> Self { Self { - id: 1, + id: tx_id, tx_type, rlp_signed: signed_bytes, rlp_unsigned: unsigned_bytes, @@ -791,12 +1076,12 @@ impl RlpFsmWitnessGen for Transaction { }; log::debug!( - "{}th tx sign witness rows len = {}", + "tx (id: {}) sign witness rows len = {}", self.id, sign_wit.len() ); log::debug!( - "{}th tx hash witness rows len = {}", + "tx (id: {}) tx hash witness rows len = {}", self.id, hash_wit.len() ); @@ -886,6 +1171,8 @@ impl From for Transaction { nonce: mock_tx.nonce.as_u64(), gas: mock_tx.gas.as_u64(), gas_price: mock_tx.gas_price, + max_fee_per_gas: mock_tx.max_fee_per_gas, + max_priority_fee_per_gas: mock_tx.max_priority_fee_per_gas, caller_address: mock_tx.from.address(), callee_address: mock_tx.to.as_ref().map(|to| to.address()), is_create, @@ -893,6 +1180,7 @@ impl From for Transaction { call_data: mock_tx.input.to_vec(), call_data_length: mock_tx.input.len(), call_data_gas_cost: tx_data_gas_cost(&mock_tx.input), + access_list_gas_cost: tx_access_list_gas_cost(&access_list), tx_data_gas_cost: tx_data_gas_cost(&rlp_signed), chain_id: mock_tx.chain_id, rlp_unsigned, @@ -938,6 +1226,16 @@ pub(super) fn tx_convert( nonce: tx.nonce, gas: tx.gas, gas_price: tx.gas_price, + max_fee_per_gas: if tx.tx_type.is_eip1559() { + tx.gas_fee_cap + } else { + tx.gas_price + }, + max_priority_fee_per_gas: if tx.tx_type.is_eip1559() { + tx.gas_tip_cap + } else { + tx.gas_price + }, caller_address: tx.from, callee_address, is_create: tx.is_create(), @@ -945,6 +1243,7 @@ pub(super) fn tx_convert( call_data: tx.input.clone(), call_data_length: tx.input.len(), call_data_gas_cost: tx_data_gas_cost(&tx.input), + access_list_gas_cost: tx_access_list_gas_cost(&tx.access_list), tx_data_gas_cost: tx_gas_cost, chain_id, rlp_unsigned: tx.rlp_unsigned_bytes.clone(),