Skip to content

Commit e90ea7e

Browse files
lispcroynalnaruto
andauthored
pick privacy-scaling-explorations#1719 from upstream: fix begin-tx tx_id soundness (#1078)
* pick privacy-scaling-explorations#1719 from upstream: fix begin-tx tx_id soundness * fix transition from end_tx to end_inner_block --------- Co-authored-by: Rohit Narurkar <[email protected]>
1 parent 67f702d commit e90ea7e

File tree

7 files changed

+127
-44
lines changed

7 files changed

+127
-44
lines changed

bus-mapping/src/evm/opcodes/begin_end_tx.rs

+47-18
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{
44
};
55
use crate::{
66
circuit_input_builder::{
7-
CircuitInputStateRef, CopyAccessList, CopyBytes, CopyDataType, CopyEvent, ExecStep,
7+
Call, CircuitInputStateRef, CopyAccessList, CopyBytes, CopyDataType, CopyEvent, ExecStep,
88
NumberOrHash,
99
},
1010
l2_predeployed::l1_gas_price_oracle,
@@ -44,12 +44,15 @@ use ethers_core::utils::get_contract_address;
4444

4545
pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<Vec<ExecStep>, Error> {
4646
let mut exec_step = state.new_begin_tx_step();
47+
let call = state.call()?.clone();
48+
49+
// write tx_id
50+
begin_tx(state, &mut exec_step, &call)?;
4751

4852
// Add two copy-events for tx access-list addresses and storage keys for
4953
// EIP-1559 and EIP-2930.
5054
gen_tx_access_list_ops(state, &mut exec_step)?;
5155

52-
let call = state.call()?.clone();
5356
let caller_address = call.caller_address;
5457
if state.tx.tx_type.is_l1_msg() {
5558
// for l1 message, no need to add rw op, but we must check
@@ -114,7 +117,6 @@ pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<Vec<ExecSt
114117
// * write l1fee call context
115118

116119
for (field, value) in [
117-
(CallContextField::TxId, state.tx_ctx.id().into()),
118120
(
119121
CallContextField::RwCounterEndOfReversion,
120122
call.rw_counter_end_of_reversion.into(),
@@ -646,17 +648,53 @@ pub fn gen_end_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep, Er
646648
)?;
647649
}
648650

651+
end_tx(state, &mut exec_step, &call)?;
652+
653+
Ok(exec_step)
654+
}
655+
656+
pub(crate) fn begin_tx(
657+
state: &mut CircuitInputStateRef,
658+
exec_step: &mut ExecStep,
659+
call: &Call,
660+
) -> Result<(), Error> {
661+
// Write the transaction id
662+
state.call_context_write(
663+
exec_step,
664+
call.call_id,
665+
CallContextField::TxId,
666+
state.tx_ctx.id().into(),
667+
)?;
668+
Ok(())
669+
}
670+
671+
pub(crate) fn end_tx(
672+
state: &mut CircuitInputStateRef,
673+
exec_step: &mut ExecStep,
674+
call: &Call,
675+
) -> Result<(), Error> {
676+
// Write the tx receipt
677+
write_tx_receipt(state, exec_step, call.is_persistent)?;
678+
679+
Ok(())
680+
}
681+
682+
fn write_tx_receipt(
683+
state: &mut CircuitInputStateRef,
684+
exec_step: &mut ExecStep,
685+
is_persistent: bool,
686+
) -> Result<(), Error> {
649687
// handle tx receipt tag
650688
state.tx_receipt_write(
651-
&mut exec_step,
689+
exec_step,
652690
state.tx_ctx.id(),
653691
TxReceiptField::PostStateOrStatus,
654-
call.is_persistent as u64,
692+
is_persistent as u64,
655693
)?;
656694

657695
let log_id = exec_step.log_id;
658696
state.tx_receipt_write(
659-
&mut exec_step,
697+
exec_step,
660698
state.tx_ctx.id(),
661699
TxReceiptField::LogLength,
662700
log_id as u64,
@@ -665,7 +703,7 @@ pub fn gen_end_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep, Er
665703
if state.tx_ctx.id() > 1 {
666704
// query pre tx cumulative gas
667705
state.tx_receipt_read(
668-
&mut exec_step,
706+
exec_step,
669707
state.tx_ctx.id() - 1,
670708
TxReceiptField::CumulativeGasUsed,
671709
state.block_ctx.cumulative_gas_used,
@@ -674,22 +712,13 @@ pub fn gen_end_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep, Er
674712

675713
state.block_ctx.cumulative_gas_used += state.tx.gas - exec_step.gas_left.0;
676714
state.tx_receipt_write(
677-
&mut exec_step,
715+
exec_step,
678716
state.tx_ctx.id(),
679717
TxReceiptField::CumulativeGasUsed,
680718
state.block_ctx.cumulative_gas_used,
681719
)?;
682720

683-
if !state.tx_ctx.is_last_tx() {
684-
state.call_context_write(
685-
&mut exec_step,
686-
state.block_ctx.rwc.0 + 1,
687-
CallContextField::TxId,
688-
(state.tx_ctx.id() + 1).into(),
689-
)?;
690-
}
691-
692-
Ok(exec_step)
721+
Ok(())
693722
}
694723

695724
// Add 3 RW read operations for transaction L1 fee.

zkevm-circuits/src/evm_circuit/execution.rs

+1
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ impl<F: Field> ExecutionConfig<F> {
870870
vec![ExecutionState::EndInnerBlock, ExecutionState::EndBlock],
871871
),
872872
(
873+
// Empty block can result multiple EndInnerBlock states.
873874
"Only EndTx or EndInnerBlock can transit to EndInnerBlock",
874875
ExecutionState::EndInnerBlock,
875876
vec![ExecutionState::EndTx, ExecutionState::EndInnerBlock],

zkevm-circuits/src/evm_circuit/execution/begin_tx.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
116116
let call_id = cb.curr.state.rw_counter.clone();
117117

118118
let tx_id = cb.query_cell();
119+
cb.call_context_lookup(
120+
1.expr(),
121+
Some(call_id.expr()),
122+
CallContextFieldTag::TxId,
123+
tx_id.expr(),
124+
); // rwc_delta += 1
125+
119126
let sender_nonce = cb.query_cell();
120127

121128
let [tx_type, tx_nonce, tx_gas, tx_caller_address, tx_callee_address, tx_is_create, tx_call_data_length, tx_call_data_gas_cost, tx_data_gas_cost] =
@@ -163,12 +170,6 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
163170
l1_fee_cost.expr(),
164171
); // rwc_delta += 1
165172

166-
cb.call_context_lookup(
167-
1.expr(),
168-
Some(call_id.expr()),
169-
CallContextFieldTag::TxId,
170-
tx_id.expr(),
171-
); // rwc_delta += 1
172173
let mut reversion_info = cb.reversion_info_write(None); // rwc_delta += 2
173174
let is_persistent = reversion_info.is_persistent();
174175
cb.call_context_lookup(
@@ -841,6 +842,9 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
841842
*/
842843

843844
let mut rws = StepRws::new(block, step);
845+
let rw = rws.next();
846+
debug_assert_eq!(rw.tag(), RwTableTag::CallContext);
847+
debug_assert_eq!(rw.field_tag(), Some(CallContextFieldTag::TxId as u64));
844848

845849
let tx_type = tx.tx_type;
846850
let caller_code_hash = if tx_type.is_l1_msg() {
@@ -865,7 +869,6 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
865869
// KeccakCodeHash
866870
// else:
867871
// 3 l1 fee rw
868-
// TxId
869872
// RwCounterEndOfReversion
870873
// IsPersistent
871874
// IsSuccess
@@ -900,9 +903,6 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
900903
debug_assert_eq!(rw.tag(), RwTableTag::CallContext);
901904
debug_assert_eq!(rw.field_tag(), Some(CallContextFieldTag::L1Fee as u64));
902905

903-
let rw = rws.next();
904-
debug_assert_eq!(rw.tag(), RwTableTag::CallContext);
905-
debug_assert_eq!(rw.field_tag(), Some(CallContextFieldTag::TxId as u64));
906906
rws.offset_add(3);
907907

908908
let rw = rws.next();

zkevm-circuits/src/evm_circuit/execution/end_inner_block.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use crate::{
33
execution::ExecutionGadget,
44
step::ExecutionState,
55
util::{
6-
constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder},
6+
constraint_builder::{
7+
ConstrainBuilderCommon, EVMConstraintBuilder, StepStateTransition, Transition,
8+
},
79
math_gadget::IsZeroGadget,
810
CachedRegion, Cell,
911
},
@@ -39,6 +41,7 @@ impl<F: Field> ExecutionGadget<F> for EndInnerBlockGadget<F> {
3941
const EXECUTION_STATE: ExecutionState = ExecutionState::EndInnerBlock;
4042

4143
fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
44+
// `cb.curr.state.block_number` is constrained inside execution.rs
4245
// The number of txs in the inner block is also the ID of the last tx in the
4346
// block.
4447
let last_tx_id = cb.query_cell();
@@ -91,6 +94,14 @@ impl<F: Field> ExecutionGadget<F> for EndInnerBlockGadget<F> {
9194
);
9295
});
9396

97+
cb.require_step_state_transition(StepStateTransition {
98+
rw_counter: Transition::Same,
99+
// We propagate call_id so that EndBlock can get the last tx_id
100+
// in order to count processed txs.
101+
call_id: Transition::Same,
102+
..StepStateTransition::any()
103+
});
104+
94105
Self {
95106
last_tx_id,
96107
num_txs,

zkevm-circuits/src/evm_circuit/execution/end_tx.rs

+24-14
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
6969
let tx_id = cb.call_context(None, CallContextFieldTag::TxId);
7070
let is_persistent = cb.call_context(None, CallContextFieldTag::IsPersistent);
7171
let tx_l1_fee = cb.call_context(None, CallContextFieldTag::L1Fee);
72-
72+
// rwc_delta = 3
7373
let [tx_gas, tx_caller_address, tx_type, tx_data_gas_cost] = [
7474
TxContextFieldTag::Gas,
7575
TxContextFieldTag::CallerAddress,
@@ -91,6 +91,8 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
9191
);
9292
let refund = cb.query_cell();
9393
cb.tx_refund_read(tx_id.expr(), refund.expr());
94+
// rwc_delta = 4
95+
9496
let effective_refund = MinMaxGadget::construct(cb, max_refund.quotient(), refund.expr());
9597

9698
// Add effective_refund * tx_gas_price back to caller's balance
@@ -107,6 +109,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
107109
None,
108110
)
109111
});
112+
// rwc_delta = 4 + !tx_is_l1msg
110113

111114
// Add gas_used * effective_tip to coinbase's balance
112115
let coinbase = cb.query_cell();
@@ -155,6 +158,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
155158
AccountFieldTag::CodeHash,
156159
coinbase_codehash.expr(),
157160
);
161+
// rwc_delta = 5 + !tx_is_l1msg
158162
#[cfg(feature = "scroll")]
159163
let coinbase_keccak_codehash = cb.query_cell_phase2();
160164

@@ -177,6 +181,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
177181
None,
178182
)
179183
});
184+
// rwc_delta = 5 + !tx_is_l1msg + !tx_is_l1msg * coinbase_transfer.rw_delta
180185

181186
// constrain tx receipt fields
182187
cb.tx_receipt_lookup(
@@ -191,6 +196,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
191196
TxReceiptFieldTag::LogLength,
192197
cb.curr.state.log_id.expr(),
193198
);
199+
// rwc_delta = 7 + !tx_is_l1msg * (coinbase_transfer.rw_delta + 1)
194200

195201
let is_first_tx = IsEqualGadget::construct(cb, tx_id.expr(), 1.expr());
196202

@@ -202,50 +208,54 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
202208
);
203209
});
204210

205-
cb.condition(1.expr() - is_first_tx.expr(), |cb| {
211+
cb.condition(not::expr(is_first_tx.expr()), |cb| {
206212
cb.tx_receipt_lookup(
207213
0.expr(),
208214
tx_id.expr() - 1.expr(),
209215
TxReceiptFieldTag::CumulativeGasUsed,
210216
current_cumulative_gas_used.expr(),
211217
);
212218
});
219+
// rwc_delta = 8 - is_first_tx + !tx_is_l1msg * (coinbase_transfer.rw_delta + 1)
213220

214221
cb.tx_receipt_lookup(
215222
1.expr(),
216223
tx_id.expr(),
217224
TxReceiptFieldTag::CumulativeGasUsed,
218225
gas_used + current_cumulative_gas_used.expr(),
219226
);
227+
// rwc_delta = 9 - is_first_tx + !tx_is_l1msg * (coinbase_transfer.rw_delta + 1)
228+
229+
// The next state of `end_tx` can only be 'begin_tx' or 'end_inner_block'
220230

231+
let rw_counter_offset = 9.expr() - is_first_tx.expr()
232+
+ not::expr(tx_is_l1msg.expr()) * (coinbase_transfer.rw_delta() + 1.expr());
221233
cb.condition(
222234
cb.next.execution_state_selector([ExecutionState::BeginTx]),
223235
|cb| {
224-
cb.call_context_lookup(
225-
true.expr(),
226-
Some(cb.next.state.rw_counter.expr()),
236+
let next_step_rwc = cb.next.state.rw_counter.expr();
237+
// lookup use next step initial rwc, thus lead to same record on rw table
238+
cb.call_context_lookup_write_with_counter(
239+
next_step_rwc.clone(),
240+
Some(next_step_rwc),
227241
CallContextFieldTag::TxId,
242+
// tx_id has been lookup and range_check above
228243
tx_id.expr() + 1.expr(),
229244
);
230245

231246
cb.require_step_state_transition(StepStateTransition {
232-
rw_counter: Delta(
233-
10.expr() - is_first_tx.expr()
234-
+ not::expr(tx_is_l1msg.expr())
235-
* (coinbase_transfer.rw_delta() + 1.expr()),
236-
),
247+
rw_counter: Delta(rw_counter_offset.clone()),
237248
..StepStateTransition::any()
238249
});
239250
},
240251
);
241252

242-
let rw_counter_delta = 9.expr() - is_first_tx.expr()
243-
+ not::expr(tx_is_l1msg.expr()) * (coinbase_transfer.rw_delta() + 1.expr());
244253
cb.condition(
245-
cb.next.execution_state_selector([ExecutionState::EndBlock]),
254+
cb.next
255+
.execution_state_selector([ExecutionState::EndInnerBlock]),
246256
|cb| {
247257
cb.require_step_state_transition(StepStateTransition {
248-
rw_counter: Delta(rw_counter_delta),
258+
rw_counter: Delta(rw_counter_offset),
249259
// We propagate call_id so that EndBlock can get the last tx_id
250260
// in order to count processed txs.
251261
call_id: Same,

zkevm-circuits/src/evm_circuit/util/constraint_builder.rs

+27
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,33 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> {
11341134
word
11351135
}
11361136

1137+
// same as call_context_lookup_write with bypassing external rwc
1138+
// Note: will not bumping internal rwc
1139+
pub(crate) fn call_context_lookup_write_with_counter(
1140+
&mut self,
1141+
rw_counter: Expression<F>,
1142+
call_id: Option<Expression<F>>,
1143+
field_tag: CallContextFieldTag,
1144+
value: Expression<F>,
1145+
) {
1146+
self.rw_lookup_with_counter(
1147+
"CallContext lookup",
1148+
rw_counter,
1149+
1.expr(),
1150+
RwTableTag::CallContext,
1151+
RwValues::new(
1152+
call_id.unwrap_or_else(|| self.curr.state.call_id.expr()),
1153+
0.expr(),
1154+
field_tag.expr(),
1155+
0.expr(),
1156+
value,
1157+
0.expr(),
1158+
0.expr(),
1159+
0.expr(),
1160+
),
1161+
);
1162+
}
1163+
11371164
pub(crate) fn call_context_lookup(
11381165
&mut self,
11391166
is_write: Expression<F>,

zkevm-circuits/src/witness/tx.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1288,13 +1288,18 @@ pub(super) fn tx_convert(
12881288
.iter()
12891289
.map(|step| step_convert(step, tx.block_num))
12901290
.chain({
1291-
let rw_counter = tx.steps().last().unwrap().rwc.0 + 9 - (id == 1) as usize;
1291+
// TODO: it is a bit counter-intuitive to treat EndInnerBlock step, even multiple
1292+
// EndInnerBlock steps to belong to the last prev tx.
1293+
// We can change design later to make it easier to understand.
1294+
let last_step = tx.steps().last().unwrap();
1295+
let rw_counter = last_step.rwc.0 + last_step.bus_mapping_instance.len();
12921296
debug_assert!(next_block_num >= tx.block_num);
12931297
(tx.block_num..next_block_num)
12941298
.map(|block_num| ExecStep {
12951299
rw_counter,
12961300
execution_state: ExecutionState::EndInnerBlock,
12971301
block_num,
1302+
call_index: last_step.call_index,
12981303
..Default::default()
12991304
})
13001305
.collect::<Vec<ExecStep>>()

0 commit comments

Comments
 (0)