diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 344d8cd738..bfc3c6e73b 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -52,6 +52,11 @@ impl TxType { matches!(*self, TxType::L1Msg) } + /// If this type is Eip155 or not + pub fn is_eip155_tx(&self) -> bool { + matches!(*self, TxType::Eip155) + } + /// Get the type of transaction pub fn get_tx_type(tx: &crate::Transaction) -> Self { match tx.transaction_type { diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index 968e9c3924..b7079d17b7 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -1379,6 +1379,11 @@ impl PiCircuitConfig { Coinbase, Timestamp, Number, Difficulty, GasLimit, BaseFee, ChainId, NumTxs, CumNumTxs, NumAllTxs, ]; + + // index_cells of same block are equal to block_number. + let mut index_cells = vec![]; + let mut block_number_cell = None; + let mut cum_num_txs_field = F::from(cum_num_txs as u64); cum_num_txs += num_txs; for (row, tag) in block_ctx @@ -1392,9 +1397,6 @@ impl PiCircuitConfig { offset, || row[0], )?; - // index_cells of same block are equal to block_number. - let mut index_cells = vec![]; - let mut block_number_cell = None; for (column, value) in block_table_columns.iter().zip_eq(&row[1..]) { let cell = region.assign_advice( || format!("block table row {offset}"), @@ -1412,15 +1414,6 @@ impl PiCircuitConfig { block_value_cells.push(cell); } } - for i in 0..(index_cells.len() - 1) { - region.constrain_equal(index_cells[i].cell(), index_cells[i + 1].cell())?; - } - if *tag == Number { - region.constrain_equal( - block_number_cell.unwrap().cell(), - index_cells[0].cell(), - )?; - } region.assign_fixed( || "is_block_num_txs", @@ -1460,6 +1453,12 @@ impl PiCircuitConfig { } offset += 1; } + // block_num == index[0] + region.constrain_equal(block_number_cell.unwrap().cell(), index_cells[0].cell())?; + // index[i] == index[i+1] + for i in 0..(index_cells.len() - 1) { + region.constrain_equal(index_cells[i].cell(), index_cells[i + 1].cell())?; + } } Ok(block_value_cells) diff --git a/zkevm-circuits/src/rlp_circuit_fsm.rs b/zkevm-circuits/src/rlp_circuit_fsm.rs index 5796a64017..63d727d31a 100644 --- a/zkevm-circuits/src/rlp_circuit_fsm.rs +++ b/zkevm-circuits/src/rlp_circuit_fsm.rs @@ -186,6 +186,8 @@ impl RlpFsmRomTable { pub struct RlpCircuitConfig { /// Whether the row is the first row. q_first: Column, + /// Whether the row is the last row. + q_last: Column, /// The state of RLP verifier at the current row. state: Column, /// A utility gadget to compare/query what state we are at. @@ -215,8 +217,6 @@ pub struct RlpCircuitConfig { /// When the tag occupies several bytes, this index denotes the /// incremental index of the byte within this tag instance. tag_idx: Column, - /// The length of bytes that hold this tag's value. - tag_length: Column, /// The accumulated value of the tag's bytes up to `tag_idx`. tag_value_acc: Column, /// The depth at this row. Since RLP encoded data can be nested, we use @@ -269,10 +269,14 @@ pub struct RlpCircuitConfig { tidx_lte_tlength: ComparatorConfig, /// Check for max_length <= 32 mlength_lte_0x20: ComparatorConfig, + /// Check for tag_length <= max_length + tlength_lte_mlength: ComparatorConfig, /// Check for depth == 0 depth_check: IsEqualConfig, /// Check for depth == 1 depth_eq_one: IsEqualConfig, + /// Check for byte_value == 0 + byte_value_is_zero: IsZeroConfig, /// Internal tables /// Data table @@ -294,9 +298,11 @@ impl RlpCircuitConfig { challenges: &Challenges>, ) -> Self { let (tx_id, format) = (rlp_table.tx_id, rlp_table.format); + let tag_length = rlp_table.tag_length; let q_enabled = rlp_table.q_enable; let ( q_first, + q_last, byte_idx, byte_rev_idx, byte_value, @@ -307,7 +313,6 @@ impl RlpCircuitConfig { is_list, max_length, tag_idx, - tag_length, depth, is_tag_begin, is_tag_end, @@ -316,7 +321,7 @@ impl RlpCircuitConfig { is_same_rlp_instance, ) = ( meta.fixed_column(), - meta.advice_column(), + meta.fixed_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), @@ -499,7 +504,9 @@ impl RlpCircuitConfig { }, ); - // if (tx_id' == tx_id and format' != format) or (tx_id' != tx_id and tx_id' != 0) + // These two cases are not the very last non-padding RLP instance. + // 1. (tx_id' == tx_id and format' != format) + // 2. (tx_id' != tx_id and tx_id' != 0) cb.condition( sum::expr([ // case 1 @@ -535,13 +542,30 @@ impl RlpCircuitConfig { }, ); + // For the very last non-padding RLP instance, we have + // tx_id' != tx_id && tx_id' == 0 + cb.condition( + and::expr([ + is_padding_in_dt.expr(Rotation::next())(meta), + not::expr(tx_id_check_in_dt.is_equal_expression.expr()), + ]), + |cb| { + // byte_rev_idx == 1 + cb.require_equal( + "byte_rev_idx is 1 at the last index", + meta.query_advice(data_table.byte_rev_idx, Rotation::cur()), + 1.expr(), + ); + }, + ); + cb.gate(meta.query_fixed(q_enabled, Rotation::cur())) }); meta.lookup("byte value check", |meta| { let cond = and::expr([ meta.query_fixed(q_enabled, Rotation::cur()), - is_padding_in_dt.expr(Rotation::cur())(meta), + not::expr(is_padding_in_dt.expr(Rotation::cur())(meta)), ]); vec![( @@ -722,6 +746,13 @@ impl RlpCircuitConfig { |_meta| 0x20.expr(), u8_table.into(), ); + let tlength_lte_mlength = ComparatorChip::configure( + meta, + cmp_enabled, + |meta| meta.query_advice(tag_length, Rotation::cur()), + |meta| meta.query_advice(max_length, Rotation::cur()), + u8_table.into(), + ); let depth_check = IsEqualChip::configure( meta, cmp_enabled, @@ -746,6 +777,12 @@ impl RlpCircuitConfig { |meta| meta.query_advice(format, Rotation::cur()), |meta| meta.query_advice(format, Rotation::next()), ); + let byte_value_is_zero = IsZeroChip::configure( + meta, + |meta| meta.query_fixed(q_enabled, Rotation::cur()), + byte_value, + |meta| meta.advice_column(), + ); // constraints on the booleans that we use to reduce degree meta.create_gate("booleans for reducing degree (part one)", |meta| { @@ -832,6 +869,9 @@ impl RlpCircuitConfig { let tag_idx_expr = |meta: &mut VirtualCells| meta.query_advice(tag_idx, Rotation::cur()); let tag_value_acc_expr = |meta: &mut VirtualCells| meta.query_advice(tag_value_acc, Rotation::cur()); + let tag_bytes_rlc_expr = |meta: &mut VirtualCells| { + meta.query_advice(rlp_table.tag_bytes_rlc, Rotation::cur()) + }; let is_tag_next_end_expr = |meta: &mut VirtualCells| meta.query_advice(is_tag_end, Rotation::next()); let is_tag_end_expr = @@ -863,6 +903,8 @@ impl RlpCircuitConfig { let mut cb = BaseConstraintBuilder::default(); let tag = tag_expr(meta); + constrain_eq!(meta, cb, state, DecodeTagStart.expr()); + constrain_eq!(meta, cb, tx_id, 1.expr()); constrain_eq!(meta, cb, byte_idx, 1.expr()); cb.require_zero( "tag == TxType or tag == BeginList", @@ -891,6 +933,8 @@ impl RlpCircuitConfig { // is_list = false, tag_value_acc = byte_value constrain_eq!(meta, cb, is_list, false); constrain_eq!(meta, cb, rlp_table.tag_value, byte_value_expr); + constrain_eq!(meta, cb, rlp_table.tag_bytes_rlc, byte_value_expr); + constrain_eq!(meta, cb, rlp_table.tag_length, 1); // state transitions. update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -907,6 +951,8 @@ impl RlpCircuitConfig { constrain_eq!(meta, cb, is_list, false); constrain_eq!(meta, cb, rlp_table.tag_value, 0); + constrain_eq!(meta, cb, rlp_table.tag_bytes_rlc, 0); + constrain_eq!(meta, cb, rlp_table.tag_length, 0); // state transitions. update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -980,20 +1026,13 @@ impl RlpCircuitConfig { cb.condition( meta.query_advice(transit_to_new_rlp_instance, Rotation::cur()), |cb| { - let tx_id = meta.query_advice(rlp_table.tx_id, Rotation::cur()); - let tx_id_next = meta.query_advice(rlp_table.tx_id, Rotation::next()); - let format = meta.query_advice(rlp_table.format, Rotation::cur()); - let format_next = meta.query_advice(rlp_table.format, Rotation::next()); - let tag_next = tag_next_expr(meta); + let tag_next = meta.query_advice(tag, Rotation::next()); // state transition. update_state!(meta, cb, byte_idx, 1); update_state!(meta, cb, depth, 0); update_state!(meta, cb, state, DecodeTagStart); - cb.require_zero( - "(tx_id' == tx_id + 1) or (format' == format + 1)", - (tx_id_next - tx_id - 1.expr()) * (format_next - format - 1.expr()), - ); + cb.require_zero( "tag == TxType or tag == BeginList", (tag_next.expr() - TxType.expr()) @@ -1001,6 +1040,30 @@ impl RlpCircuitConfig { ); }, ); + // tx_id' == tx_id => format' == format + 1 + cb.condition( + and::expr([ + meta.query_advice(transit_to_new_rlp_instance, Rotation::cur()), + tx_id_check_in_sm.is_equal_expression.expr(), + ]), + |cb| { + let format = meta.query_advice(rlp_table.format, Rotation::cur()); + let format_next = meta.query_advice(rlp_table.format, Rotation::next()); + cb.require_equal("format' == format + 1", format_next, format + 1.expr()); + }, + ); + // tx_id' != tx_id => tx_id' == tx_id + 1 + cb.condition( + and::expr([ + meta.query_advice(transit_to_new_rlp_instance, Rotation::cur()), + not::expr(tx_id_check_in_sm.is_equal_expression.expr()), + ]), + |cb| { + let tx_id = meta.query_advice(rlp_table.tx_id, Rotation::cur()); + let tx_id_next = meta.query_advice(rlp_table.tx_id, Rotation::next()); + cb.require_equal("tx_id' == tx_id + 1", tx_id_next, tx_id + 1.expr()); + }, + ); cb.condition( and::expr([ case_4.expr(), @@ -1042,6 +1105,7 @@ impl RlpCircuitConfig { update_state!(meta, cb, tag_idx, 1); update_state!(meta, cb, tag_length, byte_value_expr(meta) - 0x80.expr()); update_state!(meta, cb, tag_value_acc, byte_value_next_expr(meta)); + update_state!(meta, cb, rlp_table.tag_bytes_rlc, byte_value_next_expr(meta)); update_state!(meta, cb, state, State::Bytes); // depth is unchanged. @@ -1066,7 +1130,7 @@ impl RlpCircuitConfig { let b = select::expr( mlen_lt_0x20, 256.expr(), - select::expr(mlen_eq_0x20, evm_word_rand, keccak_input_rand), + select::expr(mlen_eq_0x20, evm_word_rand, keccak_input_rand.expr()), ); // Bytes => Bytes @@ -1078,6 +1142,8 @@ impl RlpCircuitConfig { update_state!(meta, cb, tag_idx, tag_idx_expr(meta) + 1.expr()); update_state!(meta, cb, tag_value_acc, tag_value_acc_expr(meta) * b.expr() + byte_value_next_expr(meta)); + update_state!(meta, cb, rlp_table.tag_bytes_rlc, + tag_bytes_rlc_expr(meta) * keccak_input_rand.expr() + byte_value_next_expr(meta)); update_state!(meta, cb, state, State::Bytes); // depth, tag_length unchanged. @@ -1087,7 +1153,20 @@ impl RlpCircuitConfig { // Bytes => DecodeTagStart cb.condition(tidx_eq_tlen, |cb| { // assertions + let (lt, eq) = tlength_lte_mlength.expr(meta, Some(Rotation::cur())); + cb.require_equal( + "tag_length <= max_length", + // we can use `sum` instead of `or` for two reasons + // 1. both `lt` and `eq` are boolean and they cannot be true at the same time, + // therefore the result of `sum` is same as `or`. + // 2. sum has lower degree + sum::expr([ + lt, eq, + ]), + true.expr(), + ); emit_rlp_tag!(meta, cb, tag_expr(meta), false); + constrain_eq!(meta, cb, rlp_table.tag_value, tag_value_acc_expr(meta)); // state transitions. update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -1166,6 +1245,8 @@ impl RlpCircuitConfig { // state transition. update_state!(meta, cb, tag_length, tag_value_acc_expr(meta)); update_state!(meta, cb, tag_idx, 1); + update_state!(meta, cb, rlp_table.tag_bytes_rlc, byte_value_next_expr(meta)); + update_state!(meta, cb, tag_value_acc, byte_value_next_expr(meta)); update_state!(meta, cb, state, State::Bytes); // depth is unchanged. @@ -1190,6 +1271,7 @@ impl RlpCircuitConfig { ]); cb.condition(cond.expr(), |cb| { // assertions. + do_not_emit!(meta, cb); constrain_eq!(meta, cb, is_tag_begin, true); // state transitions @@ -1236,6 +1318,8 @@ impl RlpCircuitConfig { // LongList => DecodeTagStart cb.condition(tidx_eq_tlen.expr(), |cb| { // assertions + let (lt, eq) = tlength_lte_mlength.expr(meta, Some(Rotation::cur())); + cb.require_equal("tag_length <= max_length", sum::expr([lt, eq]), true.expr()); // state transitions update_state!(meta, cb, tag, tag_next_expr(meta)); @@ -1313,8 +1397,17 @@ impl RlpCircuitConfig { ])) }); + meta.create_gate("sm ends in End state", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + constrain_eq!(meta, cb, state, State::End); + + cb.gate(meta.query_fixed(q_last, Rotation::cur())) + }); + Self { q_first, + q_last, state, state_bits, rlp_table, @@ -1327,7 +1420,6 @@ impl RlpCircuitConfig { bytes_rlc, gas_cost_acc, tag_idx, - tag_length, tag_value_acc, is_list, max_length, @@ -1358,8 +1450,10 @@ impl RlpCircuitConfig { byte_value_gte_0xf8, tidx_lte_tlength, mlength_lte_0x20, + tlength_lte_mlength, depth_check, depth_eq_one, + byte_value_is_zero, // internal tables data_table, @@ -1411,6 +1505,18 @@ impl RlpCircuitConfig { row, || witness.rlp_table.tag_value, )?; + region.assign_advice( + || "rlp_table.tag_bytes_rlc", + self.rlp_table.tag_bytes_rlc, + row, + || witness.rlp_table.tag_bytes_rlc, + )?; + region.assign_advice( + || "rlp_table.tag_length", + self.rlp_table.tag_length, + row, + || Value::known(F::from(witness.rlp_table.tag_length as u64)), + )?; region.assign_advice( || "rlp_table.is_output", self.rlp_table.is_output, @@ -1461,12 +1567,6 @@ impl RlpCircuitConfig { row, || Value::known(F::from(witness.state_machine.tag_idx as u64)), )?; - region.assign_advice( - || "sm.tag_length", - self.tag_length, - row, - || Value::known(F::from(witness.state_machine.tag_length as u64)), - )?; region.assign_advice( || "sm.depth", self.depth, @@ -1577,7 +1677,14 @@ impl RlpCircuitConfig { region, row, F::from(witness.state_machine.tag_idx as u64), - F::from(witness.state_machine.tag_length as u64), + F::from(witness.rlp_table.tag_length as u64), + )?; + let tlength_lte_mlength_chip = ComparatorChip::construct(self.tlength_lte_mlength.clone()); + tlength_lte_mlength_chip.assign( + region, + row, + 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()); @@ -1636,6 +1743,8 @@ impl RlpCircuitConfig { for (chip, lhs, rhs) in byte_value_checks { chip.assign(region, row, lhs, rhs)?; } + let bv_chip = IsZeroChip::construct(self.byte_value_is_zero.clone()); + bv_chip.assign(region, row, Value::known(byte_value))?; Ok(()) } @@ -1764,6 +1873,13 @@ impl RlpCircuitConfig { for i in sm_rows.len()..last_row { self.assign_sm_end_row(&mut region, i)?; } + region.assign_fixed(|| "q_first", self.q_first, 0, || Value::known(F::one()))?; + region.assign_fixed( + || "q_last", + self.q_last, + last_row - 1, + || Value::known(F::one()), + )?; Ok(()) }, diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index c05162aaa9..7e6d0733b8 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -2081,6 +2081,10 @@ pub struct RlpFsmRlpTable { pub rlp_tag: Column, /// The actual value of the current tag being decoded. pub tag_value: Column, + /// RLC of the tag's big-endian bytes + pub tag_bytes_rlc: Column, + /// The actual length of bytes of the current tag being decoded. + pub tag_length: Column, /// Whether or not the row emits an output value. pub is_output: Column, /// Whether or not the current tag's value was nil. @@ -2095,6 +2099,8 @@ impl LookupTable for RlpFsmRlpTable { self.format.into(), self.rlp_tag.into(), self.tag_value.into(), + self.tag_bytes_rlc.into(), + self.tag_length.into(), self.is_output.into(), self.is_none.into(), ] @@ -2107,6 +2113,8 @@ impl LookupTable for RlpFsmRlpTable { String::from("format"), String::from("rlp_tag"), String::from("tag_value_acc"), + String::from("tag_bytes_rlc"), + String::from("tag_length"), String::from("is_output"), String::from("is_none"), ] @@ -2122,6 +2130,8 @@ impl RlpFsmRlpTable { format: meta.advice_column(), rlp_tag: meta.advice_column(), tag_value: meta.advice_column_in(SecondPhase), + tag_bytes_rlc: meta.advice_column_in(SecondPhase), + tag_length: meta.advice_column(), is_output: meta.advice_column(), is_none: meta.advice_column(), } @@ -2175,6 +2185,16 @@ impl RlpFsmRlpTable { Value::known(F::from(usize::from(row.rlp_tag) as u64)), ), ("tag_value", self.tag_value.into(), row.tag_value), + ( + "tag_bytes_rlc", + self.tag_bytes_rlc.into(), + row.tag_bytes_rlc, + ), + ( + "tag_length", + self.tag_length.into(), + Value::known(F::from(row.tag_length as u64)), + ), ("is_output", self.is_output.into(), Value::known(F::one())), ( "is_none", diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index a0622f5986..b05e1c2bce 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -16,7 +16,7 @@ use crate::{ evm_circuit::util::constraint_builder::{BaseConstraintBuilder, ConstrainBuilderCommon}, sig_circuit::SigCircuit, table::{ - BlockContextFieldTag::{CumNumTxs, NumAllTxs}, + BlockContextFieldTag::{CumNumTxs, NumAllTxs, NumTxs}, BlockTable, KeccakTable, LookupTable, RlpFsmRlpTable as RlpTable, SigTable, TxFieldTag, TxFieldTag::{ BlockNumber, CallData, CallDataGasCost, CallDataLength, CallDataRLC, CalleeAddress, @@ -31,7 +31,7 @@ use crate::{ }, witness, witness::{ - rlp_fsm::Tag, + rlp_fsm::{Tag, ValueTagLength}, Format::{L1MsgHash, TxHashEip155, TxHashPreEip155, TxSignEip155, TxSignPreEip155}, RlpTag, RlpTag::{GasCost, Len, Null, RLC}, @@ -46,12 +46,13 @@ use eth_types::{ TxType::{Eip155, L1Msg, PreEip155}, }, sign_types::SignData, - Address, Field, ToAddress, ToLittleEndian, ToScalar, + Address, Field, ToAddress, ToBigEndian, ToLittleEndian, ToScalar, }; use gadgets::{ binary_number::{BinaryNumberChip, BinaryNumberConfig}, comparator::{ComparatorChip, ComparatorConfig, ComparatorInstruction}, is_equal::{IsEqualChip, IsEqualConfig, IsEqualInstruction}, + less_than::{LtChip, LtConfig, LtInstruction}, util::{and, not, select, sum, Expr}, }; use halo2_proofs::{ @@ -72,6 +73,7 @@ use halo2_proofs::plonk::FirstPhase as SecondPhase; use halo2_proofs::plonk::Fixed; #[cfg(not(feature = "onephase"))] 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 = 23; @@ -97,8 +99,11 @@ enum LookupCondition { pub struct TxCircuitConfig { minimum_rows: usize, - /// Only the 2nd row of tx table is enabled (as the first row is empty). - q_second: Column, + // This is only true at the first row of calldata part of tx table + q_calldata_first: Column, + q_calldata_last: Column, + // A selector which is enabled at 1st row + q_first: Column, tx_table: TxTable, tx_tag_bits: BinaryNumberConfig, @@ -108,6 +113,8 @@ pub struct TxCircuitConfig { rlp_tag: Column, // Whether tag's RLP-encoded value is 0x80 = rlp([]) is_none: Column, + tx_value_length: Column, + tx_value_rlc: Column, u8_table: U8Table, u16_table: U16Table, @@ -129,7 +136,7 @@ pub struct TxCircuitConfig { is_chain_id: Column, lookup_conditions: HashMap>, - /// Columns for computing num_l1_msgs and num_l2_txs + /// Columns for computing num_all_txs tx_nonce: Column, block_num: Column, block_num_unchanged: IsEqualConfig, @@ -143,13 +150,22 @@ 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, + /// 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. + /// It's not safe to do RLC on columns of same phase. + calldata_byte: Column, /// Columns for ensuring that BlockNum is correct is_padding_tx: Column, /// Tx id must be no greater than cum_num_txs tx_id_cmp_cum_num_txs: ComparatorConfig, + tx_id_gt_prev_cnt: LtConfig, /// Cumulative number of txs up to a block cum_num_txs: Column, + /// Number of txs in a block + num_txs: Column, /// Address recovered by SignVerifyChip sv_address: Column, @@ -198,12 +214,14 @@ impl SubCircuitConfig for TxCircuitConfig { sig_table, u8_table, u16_table, - challenges: _, + challenges, }: Self::ConfigArgs, ) -> Self { let q_enable = tx_table.q_enable; - let q_second = meta.fixed_column(); + let q_first = meta.fixed_column(); + let q_calldata_first = meta.fixed_column(); + let q_calldata_last = meta.fixed_column(); // Since we allow skipping l1 txs that could cause potential circuit overflow, // the num_all_txs (num_l1_msgs + num_l2_txs) in the input to get chunk data hash // does not necessarily equal to num_txs (self.txs.len()) in block table. @@ -229,17 +247,23 @@ impl SubCircuitConfig for TxCircuitConfig { // tag, rlp_tag, tx_type, is_none let tx_type = meta.advice_column(); let rlp_tag = meta.advice_column(); + let tx_value_rlc = meta.advice_column_in(SecondPhase); + let tx_value_length = meta.advice_column(); let is_none = meta.advice_column(); let tag_bits = BinaryNumberChip::configure(meta, q_enable, Some(tx_table.tag.into())); let tx_type_bits = BinaryNumberChip::configure(meta, q_enable, Some(tx_type.into())); // columns for constraining BlockNum is valid let cum_num_txs = meta.advice_column(); + // num_of_txs that each block contains + let num_txs = meta.advice_column(); let is_padding_tx = meta.advice_column(); // 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 calldata_byte = meta.advice_column(); // booleans to reduce degree let is_l1_msg = meta.advice_column(); @@ -311,6 +335,13 @@ impl SubCircuitConfig for TxCircuitConfig { is_tx_tag!(is_block_num, BlockNumber); is_tx_tag!(is_tx_type, TxType); + let tx_id_unchanged = IsEqualChip::configure( + meta, + |meta| meta.query_fixed(q_enable, Rotation::cur()), + |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), + |meta| meta.query_advice(tx_table.tx_id, Rotation::next()), + ); + // testing if value is zero for tags let value_is_zero = IsZeroChip::configure( meta, @@ -332,6 +363,19 @@ impl SubCircuitConfig for TxCircuitConfig { ); // tx_id transition in the fixed part of tx table + meta.create_gate("tx_id starts with 1", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + // the first row in tx table are all-zero rows + cb.require_equal( + "tx_id == 1", + meta.query_advice(tx_table.tx_id, Rotation::next()), + 1.expr(), + ); + + cb.gate(meta.query_fixed(q_first, Rotation::cur())) + }); + meta.create_gate("tx_id transition in the fixed part of tx table", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -360,6 +404,8 @@ impl SubCircuitConfig for TxCircuitConfig { ("sv_address", sv_address), // extracted at ChainID row ("block_num", block_num), // extracted at BlockNum row ("total_l1_popped_before", total_l1_popped_before), + ("num_txs", num_txs), + ("cum_num_txs", cum_num_txs), ("num_all_txs_acc", num_all_txs_acc), // is_l1_msg does not need to spread out as it's extracted from tx_type @@ -412,7 +458,7 @@ impl SubCircuitConfig for TxCircuitConfig { (is_hash(meta), Null), (is_data(meta), Null), (is_block_num(meta), Null), - (is_chain_id_expr(meta), Null), + (is_chain_id_expr(meta), Tag::ChainId.into()), (is_tx_type(meta), Null), ]; @@ -507,7 +553,7 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_calldata", - tag_bits.value_equals(CallData, Rotation::cur())(meta), + is_data(meta), meta.query_advice(is_calldata, Rotation::cur()), ); @@ -519,7 +565,7 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_caller_address", - tag_bits.value_equals(CallerAddress, Rotation::cur())(meta), + is_caller_addr(meta), meta.query_advice(is_caller_address, Rotation::cur()), ); @@ -531,7 +577,7 @@ impl SubCircuitConfig for TxCircuitConfig { cb.require_equal( "is_chain_id", - tag_bits.value_equals(ChainID, Rotation::cur())(meta), + is_chain_id_expr(meta), meta.query_advice(is_chain_id, Rotation::cur()), ); @@ -590,6 +636,10 @@ impl SubCircuitConfig for TxCircuitConfig { 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), + ]), is_sign_length(meta), is_sign_rlc(meta), ]); @@ -694,14 +744,19 @@ impl SubCircuitConfig for TxCircuitConfig { meta, q_enable, rlp_tag, + tx_value_rlc, + tx_value_length, tx_type_bits, + tx_id_is_zero.clone(), is_none, &lookup_conditions, is_final, + is_calldata, is_chain_id, is_l1_msg, sv_address, calldata_gas_cost_acc, + calldata_rlc, tx_table.clone(), keccak_table.clone(), rlp_table, @@ -792,7 +847,7 @@ impl SubCircuitConfig for TxCircuitConfig { let mut cb = BaseConstraintBuilder::default(); let queue_index = tx_nonce; // first tx in tx table - cb.condition(meta.query_fixed(q_second, Rotation::cur()), |cb| { + cb.condition(meta.query_fixed(q_first, Rotation::next()), |cb| { cb.require_equal( "num_all_txs_acc = is_l1_msg ? queue_index - total_l1_popped_before + 1 : 1", meta.query_advice(num_all_txs_acc, Rotation::cur()), @@ -934,6 +989,44 @@ impl SubCircuitConfig for TxCircuitConfig { cb.gate(meta.query_fixed(q_enable, Rotation::cur())) }); + // prev block's cum_num_txs < tx_id + let tx_id_gt_prev_cnt = LtChip::configure( + meta, + |meta| meta.query_fixed(q_enable, Rotation::cur()), + |meta| { + let num_txs = meta.query_advice(num_txs, Rotation::cur()); + let cum_num_txs = meta.query_advice(cum_num_txs, Rotation::cur()); + + cum_num_txs - num_txs + }, + |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), + u8_table.into(), + ); + + // last non-padding tx must have tx_id == cum_num_txs + meta.create_gate( + "last non-padding tx must have tx_id == cum_num_txs", + |meta| { + let mut cb = BaseConstraintBuilder::default(); + let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); + let is_cur_tx_non_padding = + not::expr(meta.query_advice(is_padding_tx, Rotation::cur())); + let is_next_tx_padding = meta.query_advice(is_padding_tx, Rotation::next()); + let cum_num_txs = meta.query_advice(cum_num_txs, Rotation::cur()); + let tx_id = meta.query_advice(tx_table.tx_id, Rotation::cur()); + + // tag == BlockNum && cur tx is the last non-padding tx + cb.condition( + and::expr([is_tag_block_num, is_cur_tx_non_padding, is_next_tx_padding]), + |cb| { + cb.require_equal("tx_id == cum_num_txs", tx_id, cum_num_txs); + }, + ); + + cb.gate(meta.query_fixed(tx_table.q_enable, Rotation::cur())) + }, + ); + // tx_id <= cum_num_txs let tx_id_cmp_cum_num_txs = ComparatorChip::configure( meta, @@ -957,6 +1050,26 @@ impl SubCircuitConfig for TxCircuitConfig { ])) }); + meta.lookup_any("num_txs in block table", |meta| { + let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); + let block_num = meta.query_advice(tx_table.value, Rotation::cur()); + let num_txs = meta.query_advice(num_txs, Rotation::cur()); + + let input_expr = vec![NumTxs.expr(), block_num, num_txs]; + let table_expr = block_table.table_exprs(meta); + let condition = and::expr([ + is_tag_block_num, + not::expr(meta.query_advice(is_padding_tx, Rotation::cur())), + meta.query_fixed(q_enable, Rotation::cur()), + ]); + + input_expr + .into_iter() + .zip(table_expr.into_iter()) + .map(|(input, table)| (input * condition.clone(), table)) + .collect::>() + }); + meta.lookup_any("cum_num_txs in block table", |meta| { let is_tag_block_num = meta.query_advice(is_tag_block_num, Rotation::cur()); let block_num = meta.query_advice(tx_table.value, Rotation::cur()); @@ -980,13 +1093,6 @@ impl SubCircuitConfig for TxCircuitConfig { //////////////////////////////////////////////////////////////////////// /////////// CallData length and gas_cost calculation ///////////////// //////////////////////////////////////////////////////////////////////// - let tx_id_unchanged = IsEqualChip::configure( - meta, - |meta| meta.query_fixed(q_enable, Rotation::cur()), - |meta| meta.query_advice(tx_table.tx_id, Rotation::cur()), - |meta| meta.query_advice(tx_table.tx_id, Rotation::next()), - ); - meta.lookup("tx_id_diff must in u16", |meta| { let q_enable = meta.query_fixed(q_enable, Rotation::next()); let is_calldata = meta.query_advice(is_calldata, Rotation::cur()); @@ -1000,6 +1106,55 @@ impl SubCircuitConfig for TxCircuitConfig { vec![(lookup_condition * (tx_id_next - tx_id), u16_table.into())] }); + meta.create_gate("last row of call data", |meta| { + let q_calldata_last = meta.query_fixed(q_calldata_last, Rotation::cur()); + let is_final = meta.query_advice(is_final, Rotation::cur()); + + vec![(q_calldata_last * (is_final - true.expr()))] + }); + meta.create_gate("calldata_byte == tx_table.value", |meta| { + let mut cb = BaseConstraintBuilder::default(); + let is_calldata = meta.query_advice(is_calldata, Rotation::cur()); + + cb.condition(is_calldata, |cb| { + cb.require_equal( + "calldata_byte == tx_table.value", + meta.query_advice(calldata_byte, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ); + }); + + cb.gate(meta.query_fixed(tx_table.q_enable, Rotation::cur())) + }); + + meta.create_gate("tx call data init", |meta| { + let mut cb = BaseConstraintBuilder::default(); + + let value_is_zero = value_is_zero.expr(Rotation::cur())(meta); + let gas_cost = select::expr(value_is_zero, 4.expr(), 16.expr()); + + cb.require_equal( + "index == 0", + meta.query_advice(tx_table.index, Rotation::cur()), + 0.expr(), + ); + cb.require_equal( + "calldata_gas_cost_acc == gas_cost", + meta.query_advice(calldata_gas_cost_acc, Rotation::cur()), + gas_cost, + ); + cb.require_equal( + "calldata_rlc == byte", + meta.query_advice(calldata_rlc, Rotation::cur()), + meta.query_advice(tx_table.value, Rotation::cur()), + ); + + cb.gate(and::expr([ + meta.query_fixed(q_calldata_first, Rotation::cur()), + not::expr(tx_id_is_zero.expr(Rotation::cur())(meta)), + ])) + }); + meta.create_gate("tx call data bytes", |meta| { let mut cb = BaseConstraintBuilder::default(); @@ -1027,16 +1182,49 @@ impl SubCircuitConfig for TxCircuitConfig { meta.query_advice(calldata_gas_cost_acc, Rotation::next()), 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() + + meta.query_advice(tx_table.value, Rotation::next()), + ); }); // on the final call data byte, tx_id must change. - cb.condition(is_final_cur, |cb| { + cb.condition(is_final_cur.expr(), |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, + not::expr(tx_id_is_zero.expr(Rotation::next())(meta)), + ]), + |cb| { + let value_next_is_zero = value_is_zero.expr(Rotation::next())(meta); + let gas_cost_next = select::expr(value_next_is_zero, 4.expr(), 16.expr()); + + cb.require_equal( + "index' == 0", + meta.query_advice(tx_table.index, Rotation::next()), + 0.expr(), + ); + cb.require_equal( + "calldata_gas_cost_acc' == gas_cost_next", + meta.query_advice(calldata_gas_cost_acc, Rotation::next()), + gas_cost_next, + ); + cb.require_equal( + "calldata_rlc' == byte'", + meta.query_advice(calldata_rlc, Rotation::next()), + meta.query_advice(tx_table.value, Rotation::next()), + ); + }, + ); + cb.gate(and::expr(vec![ meta.query_fixed(q_enable, Rotation::cur()), meta.query_advice(is_calldata, Rotation::cur()), @@ -1124,13 +1312,17 @@ impl SubCircuitConfig for TxCircuitConfig { log_deg("tx_circuit", meta); Self { - q_second, minimum_rows: meta.minimum_rows(), + q_first, + q_calldata_first, + q_calldata_last, tx_tag_bits: tag_bits, tx_type, tx_type_bits, rlp_tag, is_none, + tx_value_rlc, + tx_value_length, u8_table, u16_table, tx_id_is_zero, @@ -1139,6 +1331,7 @@ impl SubCircuitConfig for TxCircuitConfig { is_calldata, is_caller_address, tx_id_cmp_cum_num_txs, + tx_id_gt_prev_cnt, cum_num_txs, is_padding_tx, lookup_conditions, @@ -1151,6 +1344,8 @@ impl SubCircuitConfig for TxCircuitConfig { is_chain_id, is_final, calldata_gas_cost_acc, + calldata_rlc, + calldata_byte, sv_address, sig_table, block_table, @@ -1159,6 +1354,7 @@ impl SubCircuitConfig for TxCircuitConfig { rlp_table, is_tag_block_num, _marker: PhantomData, + num_txs, } } } @@ -1169,14 +1365,19 @@ impl TxCircuitConfig { meta: &mut ConstraintSystem, q_enable: Column, rlp_tag: Column, + tx_value_rlc: Column, + tx_value_length: Column, tx_type_bits: BinaryNumberConfig, + tx_id_is_zero: IsZeroConfig, is_none: Column, lookup_conditions: &HashMap>, is_final: Column, + is_calldata: Column, is_chain_id: Column, is_l1_msg_col: Column, sv_address: Column, calldata_gas_cost_acc: Column, + calldata_rlc: Column, tx_table: TxTable, keccak_table: KeccakTable, rlp_table: RlpTable, @@ -1259,6 +1460,33 @@ impl TxCircuitConfig { .map(|(arg, table)| (enable.clone() * arg, table)) .collect() }); + 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 enable = and::expr([ + meta.query_fixed(tx_table.q_enable, Rotation::cur()), + is_call_data, + not::expr(tx_id_is_zero.expr(Rotation::cur())(meta)), + meta.query_advice(is_final, Rotation::cur()), + ]); + + let input_exprs = vec![ + meta.query_advice(tx_table.tx_id, Rotation::cur()), + CallDataRLC.expr(), + calldata_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.into_iter()) + .map(|(input, table)| (input * enable.expr(), table)) + .collect() + }); ///////////////////////////////////////////////////////////////// ///////////////// RLP table lookups ////////////////////// @@ -1272,6 +1500,8 @@ impl TxCircuitConfig { let enable = and::expr([meta.query_fixed(q_enable, Rotation::cur()), is_l1_msg(meta)]); let hash_format = L1MsgHash.expr(); let tag_value = 0x7E.expr(); + let tag_bytes_rlc = 0x7E.expr(); + let tag_length = 1.expr(); let input_exprs = vec![ 1.expr(), // q_enable = true @@ -1279,6 +1509,8 @@ impl TxCircuitConfig { hash_format, RLPTxType.expr(), tag_value, + tag_bytes_rlc, + tag_length, 1.expr(), // is_output = true 0.expr(), // is_none = false ]; @@ -1312,11 +1544,13 @@ impl TxCircuitConfig { sign_format, rlp_tag, meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(tx_value_rlc, Rotation::cur()), + meta.query_advice(tx_value_length, Rotation::cur()), 1.expr(), // is_output = true is_none, ] .into_iter() - .zip(rlp_table.table_exprs(meta).into_iter()) // tag_length_eq_one is the 6th column in rlp table + .zip_eq(rlp_table.table_exprs(meta).into_iter()) .map(|(arg, table)| (enable.clone() * arg, table)) .collect() }); @@ -1348,11 +1582,13 @@ impl TxCircuitConfig { hash_format, rlp_tag, meta.query_advice(tx_table.value, Rotation::cur()), + meta.query_advice(tx_value_rlc, Rotation::cur()), + meta.query_advice(tx_value_length, Rotation::cur()), 1.expr(), // is_output = true is_none, ] .into_iter() - .zip(rlp_table.table_exprs(meta).into_iter()) + .zip_eq(rlp_table.table_exprs(meta).into_iter()) .map(|(arg, table)| (enable.clone() * arg, table)) .collect() }); @@ -1447,12 +1683,16 @@ impl TxCircuitConfig { tx_id_next: usize, tag: TxFieldTag, value: Value, + be_bytes_rlc: Value, + be_bytes_length: Option, rlp_tag: Option, is_none: Option, is_padding_tx: Option, cum_num_txs: Option, + num_txs: Option, is_final: Option, calldata_gas_cost_acc: Option, + calldata_rlc: Option>, cur_block_num: Option, next_block_number: Option, num_all_txs_acc: Option, @@ -1464,6 +1704,7 @@ impl TxCircuitConfig { let tx_type = tx.map_or(Default::default(), |tx| tx.tx_type); let tx_type_chip = BinaryNumberChip::construct(self.tx_type_bits); tx_type_chip.assign(region, *offset, &tx_type)?; + region.assign_advice( || "tx_type", self.tx_type, @@ -1482,6 +1723,18 @@ impl TxCircuitConfig { *offset, || Value::known(F::from(is_none.unwrap_or(false) as u64)), )?; + region.assign_advice( + || "value_be_bytes_rlc", + self.tx_value_rlc, + *offset, + || be_bytes_rlc, + )?; + region.assign_advice( + || "value_be_bytes_length", + self.tx_value_length, + *offset, + || Value::known(F::from(be_bytes_length.unwrap_or(0) as u64)), + )?; let block_num_unchanged_chip = IsEqualChip::construct(self.block_num_unchanged.clone()); block_num_unchanged_chip.assign( @@ -1552,7 +1805,9 @@ impl TxCircuitConfig { TxSignRLC, ]; let is_tag_in_set = sign_set.into_iter().filter(|_tag| tag == *_tag).count() == 1; - Value::known(F::from((is_tag_in_set && !is_l1_msg) as u64)) + let case1 = is_tag_in_set && !is_l1_msg; + let case2 = (tag == ChainID) && tx.map_or(false, |tx| tx.tx_type.is_eip155_tx()); + Value::known(F::from((case1 || case2) as u64)) }); // lookup to RLP table for hashing (non L1 msg) conditions.insert(LookupCondition::RlpHashTag, { @@ -1665,6 +1920,15 @@ impl TxCircuitConfig { *offset, || Value::known(F::from(calldata_gas_cost_acc.unwrap_or_default())), )?; + region.assign_advice( + || "calldata_rlc", + self.calldata_rlc, + *offset, + || calldata_rlc.unwrap_or(Value::known(F::zero())), + )?; + if tag == CallData { + region.assign_advice(|| "calldata_byte", self.calldata_byte, *offset, || value)?; + } // assign to region.assign_advice( @@ -1680,12 +1944,25 @@ impl TxCircuitConfig { F::from(tx_id as u64), F::from(cum_num_txs.unwrap_or_default() as u64), )?; + let tx_id_gt_prev_cnt = LtChip::construct(self.tx_id_gt_prev_cnt); + tx_id_gt_prev_cnt.assign( + region, + *offset, + F::from((cum_num_txs.unwrap_or_default() - num_txs.unwrap_or_default()) as u64), + F::from(tx_id as u64), + )?; region.assign_advice( || "cum_num_txs", self.cum_num_txs, *offset, || Value::known(F::from(cum_num_txs.unwrap_or_default() as u64)), )?; + region.assign_advice( + || "num_txs", + self.num_txs, + *offset, + || Value::known(F::from(num_txs.unwrap_or_default() as u64)), + )?; *offset += 1; @@ -1912,21 +2189,26 @@ impl TxCircuit { layouter.assign_region( || "dev block table", |mut region| { - for (offset, (block_num, cum_num_txs, num_all_txs)) in iter::once((0, 0, 0)) - .chain(block_nums.iter().scan(0, |cum_num_txs, block_num| { - let num_all_txs = num_all_txs_in_blocks[block_num]; - *cum_num_txs += num_txs_in_blocks[block_num]; - - Some((*block_num, *cum_num_txs, num_all_txs)) - })) - .enumerate() + for (offset, (block_num, num_txs, cum_num_txs, num_all_txs)) in + iter::once((0, 0, 0, 0)) + .chain(block_nums.iter().scan(0, |cum_num_txs, block_num| { + let num_txs = num_txs_in_blocks[block_num]; + let num_all_txs = num_all_txs_in_blocks[block_num]; + *cum_num_txs += num_txs; + + Some((*block_num, num_txs, *cum_num_txs, num_all_txs)) + })) + .enumerate() { - for (j, (tag, value)) in - [(CumNumTxs, cum_num_txs as u64), (NumAllTxs, num_all_txs)] - .into_iter() - .enumerate() + for (j, (tag, value)) in [ + (NumTxs, num_txs as u64), + (CumNumTxs, cum_num_txs as u64), + (NumAllTxs, num_all_txs), + ] + .into_iter() + .enumerate() { - let row = offset * 2 + j; + let row = offset * 3 + j; region.assign_fixed( || "block_table.tag", config.block_table.tag, @@ -1971,6 +2253,7 @@ impl TxCircuit { debug_assert_eq!(padding_txs.len() + self.txs.len(), sigs.len()); let mut cum_num_txs; + let mut num_txs; let mut is_padding_tx; let mut num_all_txs_acc = 0; let mut total_l1_popped_before = start_l1_queue_index; @@ -1983,6 +2266,10 @@ impl TxCircuit { !sigs.is_empty() as usize, // tx_id_next TxFieldTag::Null, Value::known(F::zero()), + challenges.keccak_input().map(|_| F::zero()), + None, + None, + None, None, None, None, @@ -1995,6 +2282,9 @@ impl TxCircuit { None, )?; + region.assign_fixed(|| "q_first", config.q_first, 0, || Value::known(F::one()))?; + let zero_rlc = challenges.keccak_input().map(|_| F::zero()); + // Assign all tx fields except for call data for (i, sign_data) in sigs.iter().enumerate() { let tx = if i < self.txs.len() { @@ -2002,14 +2292,21 @@ impl TxCircuit { } else { &padding_txs[i - self.txs.len()] }; + let block_num = tx.block_number; let rlp_unsigned_tx_be_bytes = tx.rlp_unsigned.clone(); let rlp_signed_tx_be_bytes = tx.rlp_signed.clone(); if i < self.txs.len() { cum_num_txs = self .txs .iter() - .filter(|tx| tx.block_number <= self.txs[i].block_number) + .filter(|tx| tx.block_number <= block_num) .count(); + num_txs = self + .txs + .iter() + .filter(|tx| tx.block_number == block_num) + .count(); + log::info!("num_txs: {}", num_txs); is_padding_tx = false; let mut init_new_block = |tx: &Transaction| { if tx.tx_type.is_l1_msg() { @@ -2037,6 +2334,7 @@ impl TxCircuit { } } else { cum_num_txs = 0; + num_txs = 0; is_padding_tx = true; // padding_tx is an l2 tx num_all_txs_acc = (i - self.txs.len() + 1) as u64; @@ -2052,38 +2350,55 @@ impl TxCircuit { }) }; log::debug!("calldata len: {}", tx.call_data.len()); - for (tag, rlp_tag, is_none, value) in [ + for (tag, rlp_tag, is_none, be_bytes_rlc, be_bytes_length, value) in [ // need to be in same order as that tx table load function uses ( Nonce, - Some(Tag::Nonce.into()), + Some(Tag::Nonce.into()), // lookup into RLP table Some(tx.nonce == 0), + rlc_be_bytes(&tx.nonce.to_be_bytes(), challenges.keccak_input()), + Some(tx.nonce.tag_length()), Value::known(F::from(tx.nonce)), ), - ( - Gas, - Some(Tag::Gas.into()), - Some(tx.gas == 0), - Value::known(F::from(tx.gas)), - ), ( GasPrice, Some(Tag::GasPrice.into()), Some(tx.gas_price.is_zero()), + rlc_be_bytes(&tx.gas_price.to_be_bytes(), challenges.keccak_input()), + Some(tx.gas_price.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.gas_price.to_le_bytes(), challenge)), ), + ( + Gas, + Some(Tag::Gas.into()), + Some(tx.gas == 0), + rlc_be_bytes(&tx.gas.to_be_bytes(), challenges.keccak_input()), + Some(tx.gas.tag_length()), + Value::known(F::from(tx.gas)), + ), ( CallerAddress, Some(Tag::Sender.into()), None, + rlc_be_bytes( + &tx.caller_address.to_fixed_bytes(), + challenges.keccak_input(), + ), + Some(tx.caller_address.tag_length()), Value::known(tx.caller_address.to_scalar().expect("tx.from too big")), ), ( CalleeAddress, Some(Tag::To.into()), Some(tx.callee_address.is_none()), + rlc_be_bytes( + &tx.callee_address + .map_or(vec![], |callee| callee.to_fixed_bytes().to_vec()), + challenges.keccak_input(), + ), + Some(tx.callee_address.tag_length()), Value::known( tx.callee_address .unwrap_or(Address::zero()) @@ -2093,7 +2408,9 @@ impl TxCircuit { ), ( IsCreate, + None, // do not lookup into RLP table None, + zero_rlc, None, Value::known(F::from(tx.is_create as u64)), ), @@ -2101,6 +2418,8 @@ impl TxCircuit { TxFieldTag::Value, Some(Tag::Value.into()), Some(tx.value.is_zero()), + rlc_be_bytes(&tx.value.to_be_bytes(), challenges.keccak_input()), + Some(tx.value.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.value.to_le_bytes(), challenge)), @@ -2110,36 +2429,55 @@ impl TxCircuit { Some(Tag::Data.into()), Some(tx.call_data.is_empty()), rlc_be_bytes(&tx.call_data, challenges.keccak_input()), + Some(tx.call_data.tag_length()), + rlc_be_bytes(&tx.call_data, challenges.keccak_input()), ), ( CallDataLength, None, None, + zero_rlc, + None, Value::known(F::from(tx.call_data.len() as u64)), ), ( CallDataGasCost, None, None, + zero_rlc, + None, Value::known(F::from(tx.call_data_gas_cost)), ), ( TxDataGasCost, Some(GasCost), None, + zero_rlc, + None, Value::known(F::from(tx.tx_data_gas_cost)), ), - (ChainID, None, None, Value::known(F::from(tx.chain_id))), + ( + ChainID, + Some(Tag::ChainId.into()), + Some(tx.chain_id.is_zero()), + rlc_be_bytes(&tx.chain_id.to_be_bytes(), challenges.keccak_input()), + Some(tx.chain_id.tag_length()), + Value::known(F::from(tx.chain_id)), + ), ( SigV, Some(Tag::SigV.into()), Some(tx.v.is_zero()), + rlc_be_bytes(&tx.v.to_be_bytes(), challenges.keccak_input()), + Some(tx.v.tag_length()), Value::known(F::from(tx.v)), ), ( SigR, Some(Tag::SigR.into()), Some(tx.r.is_zero()), + rlc_be_bytes(&tx.r.to_be_bytes(), challenges.keccak_input()), + Some(tx.r.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.r.to_le_bytes(), challenge)), @@ -2148,6 +2486,8 @@ impl TxCircuit { SigS, Some(Tag::SigS.into()), Some(tx.s.is_zero()), + rlc_be_bytes(&tx.s.to_be_bytes(), challenges.keccak_input()), + Some(tx.s.tag_length()), challenges .evm_word() .map(|challenge| rlc(tx.s.to_le_bytes(), challenge)), @@ -2156,29 +2496,37 @@ impl TxCircuit { TxSignLength, Some(Len), Some(false), + zero_rlc, + Some(rlp_unsigned_tx_be_bytes.len().tag_length()), Value::known(F::from(rlp_unsigned_tx_be_bytes.len() as u64)), ), ( TxSignRLC, Some(RLC), Some(false), + zero_rlc, + None, challenges.keccak_input().map(|rand| { rlp_unsigned_tx_be_bytes .iter() .fold(F::zero(), |acc, byte| acc * rand + F::from(*byte as u64)) }), ), - (TxSignHash, None, None, tx_sign_hash), + (TxSignHash, None, None, zero_rlc, None, tx_sign_hash), ( TxHashLength, Some(Len), Some(false), + zero_rlc, + Some(rlp_signed_tx_be_bytes.len().tag_length()), Value::known(F::from(rlp_signed_tx_be_bytes.len() as u64)), ), ( TxHashRLC, Some(RLC), Some(false), + zero_rlc, + None, challenges.keccak_input().map(|rand| { rlp_signed_tx_be_bytes .iter() @@ -2189,6 +2537,8 @@ impl TxCircuit { TxFieldTag::TxHash, None, None, + zero_rlc, + None, challenges.evm_word().map(|challenge| { tx.hash .to_fixed_bytes() @@ -2202,12 +2552,16 @@ impl TxCircuit { TxFieldTag::TxType, None, None, + zero_rlc, + None, Value::known(F::from(tx.tx_type as u64)), ), ( BlockNumber, None, None, + zero_rlc, + None, Value::known(F::from(tx.block_number)), ), ] { @@ -2250,10 +2604,14 @@ impl TxCircuit { tx_id_next, // tx_id_next tag, value, + be_bytes_rlc, + be_bytes_length, rlp_tag, is_none, Some(is_padding_tx), Some(cum_num_txs), + Some(num_txs), + None, None, None, Some(cur_block_num), @@ -2272,11 +2630,13 @@ impl TxCircuit { } log::debug!("assigning calldata, offset {}", offset); + assert_eq!(offset, self.max_txs * TX_LEN + 1); // Assign call data let mut calldata_count = 0; for (i, tx) in self.txs.iter().enumerate() { let mut calldata_gas_cost = 0; + let mut calldata_rlc = Value::known(F::zero()); let calldata_length = tx.call_data.len(); calldata_count += calldata_length; for (index, byte) in tx.call_data.iter().enumerate() { @@ -2300,6 +2660,17 @@ impl TxCircuit { (i + 1, false) }; calldata_gas_cost += if byte.is_zero() { 4 } else { 16 }; + if i == 0 && index == 0 { + region.assign_fixed( + || "q_calldata_first", + config.q_calldata_first, + offset, + || Value::known(F::one()), + )?; + } + calldata_rlc = calldata_rlc + .zip(challenges.keccak_input()) + .map(|(rlc, r)| rlc * r + F::from(*byte as u64)); config.assign_row( &mut region, &mut offset, @@ -2308,12 +2679,16 @@ impl TxCircuit { tx_id_next, // tx_id_next CallData, Value::known(F::from(*byte as u64)), + zero_rlc, + None, + None, None, None, None, None, Some(is_final), Some(calldata_gas_cost), + Some(calldata_rlc), None, None, None, @@ -2322,6 +2697,16 @@ impl TxCircuit { } } + assert!(calldata_count <= self.max_calldata); + let q_calldata_last_offset = self.max_txs * TX_LEN + self.max_calldata; + if offset == q_calldata_last_offset + 1 { + region.assign_fixed( + || "q_calldata_last", + config.q_calldata_last, + q_calldata_last_offset, + || Value::known(F::one()), + )?; + } debug_assert_eq!(offset, self.max_txs * TX_LEN + 1 + calldata_count); Ok(offset) @@ -2338,6 +2723,25 @@ impl TxCircuit { layouter.assign_region( || "tx table (calldata zeros and paddings)", |mut region| { + if last_off == self.max_txs * TX_LEN + 1 { + // The txs do not have any call data bytes + // Therefore q_calldata_first is not assigned in prev region. + region.assign_fixed( + || "q_calldata_first", + config.q_calldata_first, + 0, + || Value::known(F::one()), + )?; + } + if last_off < self.max_txs * TX_LEN + 1 + self.max_calldata { + let calldata_count = last_off - self.max_txs * TX_LEN - 1; + region.assign_fixed( + || "q_calldata_last", + config.q_calldata_last, + self.max_calldata - calldata_count - 1, + || Value::known(F::one()), + )?; + } config.assign_calldata_zeros( &mut region, 0, diff --git a/zkevm-circuits/src/tx_circuit/test.rs b/zkevm-circuits/src/tx_circuit/test.rs index f1608c708f..cc9bd9c9a4 100644 --- a/zkevm-circuits/src/tx_circuit/test.rs +++ b/zkevm-circuits/src/tx_circuit/test.rs @@ -138,7 +138,7 @@ fn run( #[cfg(feature = "scroll")] fn tx_circuit_2tx_2max_tx() { const NUM_TXS: usize = 2; - const MAX_TXS: usize = 4; + const MAX_TXS: usize = 2; const MAX_CALLDATA: usize = 32; assert_eq!( diff --git a/zkevm-circuits/src/witness/l1_msg.rs b/zkevm-circuits/src/witness/l1_msg.rs index 97c0633738..1eb13bb916 100644 --- a/zkevm-circuits/src/witness/l1_msg.rs +++ b/zkevm-circuits/src/witness/l1_msg.rs @@ -1,7 +1,7 @@ use crate::{ evm_circuit::param::{N_BYTES_ACCOUNT_ADDRESS, N_BYTES_U64, N_BYTES_WORD}, witness::{ - rlp_fsm::{N_BYTES_CALLDATA, N_BYTES_LIST}, + 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}, @@ -21,7 +21,7 @@ impl Encodable for L1MsgTx { pub fn rom_table_rows() -> Vec { let rows = vec![ (TxType, BeginList, 1, vec![1]), - (BeginList, Nonce, N_BYTES_LIST, vec![2]), + (BeginList, 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]), diff --git a/zkevm-circuits/src/witness/rlp_fsm.rs b/zkevm-circuits/src/witness/rlp_fsm.rs index ed0ba00794..487691fa8d 100644 --- a/zkevm-circuits/src/witness/rlp_fsm.rs +++ b/zkevm-circuits/src/witness/rlp_fsm.rs @@ -1,10 +1,59 @@ -use eth_types::Field; +use eth_types::{Address, Field, H160, U256}; use gadgets::{impl_expr, util::Expr}; use halo2_proofs::{arithmetic::FieldExt, circuit::Value, plonk::Expression}; use strum_macros::EnumIter; use crate::util::Challenges; +pub(crate) trait ValueTagLength { + fn tag_length(&self) -> u32; +} + +impl ValueTagLength for u64 { + fn tag_length(&self) -> u32 { + // note that 0_u64 is encoded as [0x80] in RLP + // see the relevant code at https://github.com/paritytech/parity-common/blob/master/rlp/src/impls.rs#L208 + (64 - self.leading_zeros() + 7) / 8 + } +} + +impl ValueTagLength for usize { + fn tag_length(&self) -> u32 { + // usize is treated as same as u64 + (*self as u64).tag_length() + } +} + +impl ValueTagLength for U256 { + fn tag_length(&self) -> u32 { + // note that U256::zero() is encoded as [0x80] in RLP + // see the relevant code at https://github.com/paritytech/parity-common/blob/impl-rlp-v0.3.0/primitive-types/src/lib.rs#L117 + (256 - self.leading_zeros() + 7) / 8 + } +} + +impl ValueTagLength for H160 { + fn tag_length(&self) -> u32 { + 20 + } +} + +impl ValueTagLength for Option
{ + fn tag_length(&self) -> u32 { + if self.is_none() { + 0 + } else { + self.unwrap().tag_length() + } + } +} + +impl ValueTagLength for Vec { + fn tag_length(&self) -> u32 { + self.len() as u32 + } +} + /// RLP tags #[derive(Default, Clone, Copy, Debug, EnumIter, PartialEq, Eq)] pub enum Tag { @@ -151,13 +200,15 @@ use crate::{ }, }; -// The number of bytes of list can not larger than 2^24. -pub(crate) const N_BYTES_LIST: usize = 1 << 24; +// The number of bytes of list can not be larger than 2^24 = 2^(8*3). +// This const is meant to be the maximum of tag_length for representing a `LongList`. +// For example, [0xf9, 0xff, 0xff] has tag_length = 2 and has 0xffff bytes inside. +pub(crate) const MAX_TAG_LENGTH_OF_LIST: usize = 3; pub(crate) const N_BYTES_CALLDATA: usize = 1 << 24; fn eip155_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, 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]), @@ -179,7 +230,7 @@ fn eip155_tx_sign_rom_table_rows() -> Vec { fn eip155_tx_hash_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, 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]), @@ -201,7 +252,7 @@ fn eip155_tx_hash_rom_table_rows() -> Vec { pub fn pre_eip155_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, Nonce, N_BYTES_LIST, vec![1]), + (BeginList, 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]), @@ -220,7 +271,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, N_BYTES_LIST, vec![1]), + (BeginList, 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]), @@ -243,7 +294,7 @@ pub fn pre_eip155_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, N_BYTES_LIST, vec![2]), + (BeginList, 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]), @@ -252,21 +303,26 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![8]), (TxValue, Data, N_BYTES_WORD, vec![9]), (Data, BeginVector, N_BYTES_CALLDATA, vec![10, 11]), - (BeginVector, EndVector, N_BYTES_LIST, vec![21]), // access_list is none - (BeginVector, BeginList, N_BYTES_LIST, vec![12]), - (BeginList, AccessListAddress, N_BYTES_LIST, vec![13]), + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![21]), // access_list is none + (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![12]), + ( + BeginList, + AccessListAddress, + MAX_TAG_LENGTH_OF_LIST, + vec![13], + ), ( AccessListAddress, BeginVector, N_BYTES_ACCOUNT_ADDRESS, vec![14, 15], ), - (BeginVector, EndVector, N_BYTES_LIST, vec![18]), /* access_list.storage_keys - * is none */ + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![18]), /* access_list.storage_keys + * is none */ ( BeginVector, AccessListStorageKey, - N_BYTES_LIST, + MAX_TAG_LENGTH_OF_LIST, vec![16, 17], ), (AccessListStorageKey, EndVector, N_BYTES_WORD, vec![18]), // finished parsing storage keys @@ -295,7 +351,7 @@ pub fn eip1559_tx_hash_rom_table_rows() -> Vec { pub fn eip1559_tx_sign_rom_table_rows() -> Vec { let rows = vec![ - (BeginList, ChainId, N_BYTES_LIST, vec![1]), + (BeginList, ChainId, MAX_TAG_LENGTH_OF_LIST, vec![1]), (ChainId, Nonce, N_BYTES_U64, vec![2]), (Nonce, MaxPriorityFeePerGas, N_BYTES_U64, vec![3]), (MaxPriorityFeePerGas, MaxFeePerGas, N_BYTES_WORD, vec![4]), @@ -304,20 +360,27 @@ pub fn eip1559_tx_sign_rom_table_rows() -> Vec { (To, TxValue, N_BYTES_ACCOUNT_ADDRESS, vec![7]), (TxValue, Data, N_BYTES_WORD, vec![8]), (Data, BeginVector, N_BYTES_CALLDATA, vec![9, 10]), - (BeginVector, EndVector, N_BYTES_LIST, vec![20]), // access_list is none - (BeginVector, BeginList, N_BYTES_LIST, vec![11]), - (BeginList, AccessListAddress, N_BYTES_LIST, vec![12]), + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![20]), // access_list is none + (BeginVector, BeginList, MAX_TAG_LENGTH_OF_LIST, vec![11]), + ( + BeginList, + AccessListAddress, + MAX_TAG_LENGTH_OF_LIST, + vec![12], + ), ( AccessListAddress, BeginVector, N_BYTES_ACCOUNT_ADDRESS, vec![13, 14], ), - (BeginVector, EndVector, N_BYTES_LIST, vec![17]), /* access_list.storage_keys is none */ + (BeginVector, EndVector, MAX_TAG_LENGTH_OF_LIST, vec![17]), /* access_list.storage_keys + * is + * none */ ( BeginVector, AccessListStorageKey, - N_BYTES_LIST, + MAX_TAG_LENGTH_OF_LIST, vec![15, 16], ), (AccessListStorageKey, EndVector, N_BYTES_WORD, vec![17]), // finished parsing storage keys @@ -497,6 +560,12 @@ pub struct RlpTable { pub rlp_tag: RlpTag, /// The tag's value pub tag_value: Value, + /// RLC of the tag's big-endian bytes + pub tag_bytes_rlc: Value, + /// Length of the tag's big-endian bytes + /// Note that we use (tag_bytes_rlc, tag_length) to identify + /// the tag's dynamic-sized big-endian bytes + pub tag_length: usize, /// If current row is for output pub is_output: bool, /// If current tag's value is None. @@ -522,8 +591,6 @@ pub struct StateMachine { pub byte_value: u8, /// The index of the actual bytes of tag pub tag_idx: usize, - /// The length of the actual bytes of tag - pub tag_length: usize, /// The accumulated value of bytes up to `tag_idx` of tag /// In most cases, RlpTable.tag_value == StateMachine.tag_value_acc. /// However, for RlpTag::Len, we have @@ -567,4 +634,5 @@ pub(crate) struct SmState { pub(crate) tag_idx: usize, pub(crate) tag_length: usize, pub(crate) tag_value_acc: Value, + pub(crate) tag_bytes_rlc: Value, } diff --git a/zkevm-circuits/src/witness/tx.rs b/zkevm-circuits/src/witness/tx.rs index 1da83b83c5..5074292945 100644 --- a/zkevm-circuits/src/witness/tx.rs +++ b/zkevm-circuits/src/witness/tx.rs @@ -170,12 +170,6 @@ impl Transaction { Value::known(F::zero()), Value::known(F::from(self.nonce)), ], - [ - 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::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::GasPrice as u64)), @@ -184,6 +178,12 @@ impl Transaction { .evm_word() .map(|challenge| rlc::value(&self.gas_price.to_le_bytes(), challenge)), ], + [ + 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::from(self.id as u64)), Value::known(F::from(TxContextFieldTag::CallerAddress as u64)), @@ -391,6 +391,7 @@ impl Transaction { tag_idx: 0, tag_length: 0, tag_value_acc: Value::known(F::zero()), + tag_bytes_rlc: Value::known(F::zero()), byte_idx: 0, depth: 0, }; @@ -459,6 +460,8 @@ impl Transaction { assert!(!cur.tag.is_list()); is_output = true; cur.tag_value_acc = Value::known(F::from(byte_value as u64)); + cur.tag_bytes_rlc = cur.tag_value_acc; + cur.tag_length = 1; // state transitions next.state = DecodeTagStart; @@ -468,6 +471,8 @@ impl Transaction { is_output = true; is_none = true; cur.tag_value_acc = Value::known(F::zero()); + cur.tag_bytes_rlc = cur.tag_value_acc; + cur.tag_length = 0; // state transitions next.state = DecodeTagStart; @@ -480,6 +485,7 @@ impl Transaction { next.tag_length = (byte_value - 0x80) as usize; next.tag_value_acc = Value::known(F::from(rlp_bytes[cur.byte_idx + 1] as u64)); + next.tag_bytes_rlc = next.tag_value_acc; next.state = State::Bytes; } else if byte_value < 0xc0 { // assertions @@ -499,6 +505,7 @@ impl Transaction { rlp_tag = RlpTag::Len; } cur.tag_value_acc = Value::known(F::from(u64::from(byte_value - 0xc0))); + cur.tag_length = 1; // state transitions let num_bytes_of_new_list = usize::from(byte_value - 0xc0); @@ -543,6 +550,8 @@ impl Transaction { next.tag_idx = cur.tag_idx + 1; next.tag_value_acc = cur.tag_value_acc * b + Value::known(F::from(rlp_bytes[cur.byte_idx + 1] as u64)); + next.tag_bytes_rlc = cur.tag_bytes_rlc * keccak_rand + + Value::known(F::from(rlp_bytes[cur.byte_idx + 1] as u64)); } else { // assertions is_output = true; @@ -570,6 +579,7 @@ impl Transaction { next.tag_length = lb_len; next.tag_value_acc = Value::known(F::from(u64::from(rlp_bytes[cur.byte_idx + 1]))); + next.tag_bytes_rlc = next.tag_value_acc; next.state = State::Bytes; } } @@ -654,6 +664,13 @@ impl Transaction { RlpTag::Tag(_) => cur.tag_value_acc, RlpTag::Null => unreachable!("Null is not used"), }; + let (tag_bytes_rlc, tag_length) = match rlp_tag { + // Len | RLC | GasCost are just meta-info extracted from keccak input bytes + RlpTag::Len => (Value::known(F::zero()), cur.tag_length), + RlpTag::RLC | RlpTag::GasCost => (Value::known(F::zero()), 0), + RlpTag::Tag(_) => (cur.tag_bytes_rlc, cur.tag_length), + RlpTag::Null => unreachable!("Null is not used"), + }; witness.push(RlpFsmWitnessRow { rlp_table: RlpTable { @@ -661,6 +678,8 @@ impl Transaction { format, rlp_tag, tag_value, + tag_bytes_rlc, + tag_length, is_output, is_none, }, @@ -673,7 +692,6 @@ impl Transaction { byte_rev_idx: rlp_bytes.len() - cur.byte_idx, byte_value, tag_idx: cur.tag_idx, - tag_length: cur.tag_length, tag_acc_value: cur.tag_value_acc, depth: cur.depth, bytes_rlc,