Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.

Commit 9cbbd35

Browse files
naureAurélien Nicolas
andauthored
copy-fix-terminate: secure termination (#649)
* copy-fix-terminate: stronger termination requirements * copy-fix-terminate: add witness generation, doc, and fix tests --------- Co-authored-by: Aurélien Nicolas <[email protected]>
1 parent 156a3a5 commit 9cbbd35

File tree

5 files changed

+71
-60
lines changed

5 files changed

+71
-60
lines changed

gadgets/src/binary_number.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ where
5959
}
6060
}
6161

62+
/// Return the constant that represents a given value. To be compared with the value expression.
63+
pub fn constant_expr<F: Field>(&self, value: T) -> Expression<F> {
64+
let f = value
65+
.as_bits()
66+
.iter()
67+
.fold(F::zero(), |result, bit| F::from(*bit) + result * F::from(2));
68+
Expression::Constant(f)
69+
}
70+
6271
/// Returns a function that can evaluate to a binary expression, that
6372
/// evaluates to 1 if value is equal to value as bits. The returned
6473
/// expression is of degree N.

zkevm-circuits/src/copy_circuit.rs

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ const NEXT_ROW: Rotation = Rotation(1);
5757
/// The next step, processing the next byte. This connects reader-to-reader or writer-to-writer.
5858
const NEXT_STEP: Rotation = Rotation(2);
5959

60+
// Rows to enable but not use, that can be queried safely by the last event.
61+
const UNUSED_ROWS: usize = 2;
62+
// Rows to disable, so they do not query into Halo2 reserved rows.
63+
const DISABLED_ROWS: usize = 2;
64+
6065
/// The rw table shared between evm circuit and state circuit
6166
#[derive(Clone, Debug)]
6267
pub struct CopyCircuitConfig<F> {
@@ -208,7 +213,7 @@ impl<F: Field> SubCircuitConfig<F> for CopyCircuitConfig<F> {
208213
constrain_tag(
209214
meta,
210215
q_enable,
211-
tag,
216+
&tag,
212217
is_precompiled,
213218
is_tx_calldata,
214219
is_bytecode,
@@ -238,7 +243,7 @@ impl<F: Field> SubCircuitConfig<F> for CopyCircuitConfig<F> {
238243

239244
constrain_first_last(cb, is_reader.expr(), is_first.expr(), is_last.expr());
240245

241-
constrain_must_terminate(cb, meta, q_enable, is_last.expr());
246+
constrain_must_terminate(cb, meta, q_enable, &tag);
242247

243248
constrain_forward_parameters(cb, meta, is_continue.expr(), id, tag, src_addr_end);
244249

@@ -627,15 +632,13 @@ impl<F: Field> CopyCircuitConfig<F> {
627632
) -> Result<(), Error> {
628633
let copy_rows_needed = copy_events
629634
.iter()
630-
.map(|c| c.copy_bytes.bytes.len() * 2)
635+
.map(|c| c.full_length() as usize * 2)
631636
.sum::<usize>();
632-
633-
// The `+ 2` is used to take into account the two extra empty copy rows needed
634-
// to satisfy the queries at `NEXT_STEP`.
635637
assert!(
636-
copy_rows_needed + 2 <= max_copy_rows,
638+
copy_rows_needed + DISABLED_ROWS + UNUSED_ROWS <= max_copy_rows,
637639
"copy rows not enough {copy_rows_needed} vs {max_copy_rows}"
638640
);
641+
let filler_rows = max_copy_rows - copy_rows_needed - DISABLED_ROWS;
639642

640643
let tag_chip = BinaryNumberChip::construct(self.copy_table.tag);
641644
let is_src_end_chip = IsEqualChip::construct(self.is_src_end.clone());
@@ -662,7 +665,7 @@ impl<F: Field> CopyCircuitConfig<F> {
662665
"offset is {} before {}th copy event(bytes len: {}): {:?}",
663666
offset,
664667
ev_idx,
665-
copy_event.copy_bytes.bytes.len(),
668+
copy_event.full_length(),
666669
{
667670
let mut copy_event = copy_event.clone();
668671
copy_event.copy_bytes.bytes.clear();
@@ -681,33 +684,28 @@ impl<F: Field> CopyCircuitConfig<F> {
681684
log::trace!("offset after {}th copy event: {}", ev_idx, offset);
682685
}
683686

684-
for _ in 0..max_copy_rows - copy_rows_needed - 2 {
687+
for _ in 0..filler_rows {
685688
self.assign_padding_row(
686689
&mut region,
687690
&mut offset,
688-
false,
691+
true,
689692
&tag_chip,
690693
&is_src_end_chip,
691694
&lt_word_end_chip,
692695
)?;
693696
}
697+
assert_eq!(offset % 2, 0, "enabled rows must come in pairs");
694698

695-
self.assign_padding_row(
696-
&mut region,
697-
&mut offset,
698-
true,
699-
&tag_chip,
700-
&is_src_end_chip,
701-
&lt_word_end_chip,
702-
)?;
703-
self.assign_padding_row(
704-
&mut region,
705-
&mut offset,
706-
true,
707-
&tag_chip,
708-
&is_src_end_chip,
709-
&lt_word_end_chip,
710-
)?;
699+
for _ in 0..DISABLED_ROWS {
700+
self.assign_padding_row(
701+
&mut region,
702+
&mut offset,
703+
false,
704+
&tag_chip,
705+
&is_src_end_chip,
706+
&lt_word_end_chip,
707+
)?;
708+
}
711709

712710
Ok(())
713711
},
@@ -719,7 +717,7 @@ impl<F: Field> CopyCircuitConfig<F> {
719717
&self,
720718
region: &mut Region<F>,
721719
offset: &mut usize,
722-
is_last_two: bool,
720+
enabled: bool,
723721
tag_chip: &BinaryNumberChip<F, CopyDataType, 4>,
724722
is_src_end_chip: &IsEqualChip<F>,
725723
lt_word_end_chip: &IsEqualChip<F>,
@@ -729,13 +727,11 @@ impl<F: Field> CopyCircuitConfig<F> {
729727
|| "q_enable",
730728
self.q_enable,
731729
*offset,
732-
|| Value::known(F::from(!is_last_two)),
730+
|| Value::known(F::from(enabled)),
733731
)?;
734-
if !is_last_two {
735-
// q_step
736-
if *offset % 2 == 0 {
737-
self.q_step.enable(region, *offset)?;
738-
}
732+
// q_step
733+
if enabled && *offset % 2 == 0 {
734+
self.q_step.enable(region, *offset)?;
739735
}
740736

741737
// is_first
@@ -1012,9 +1008,10 @@ impl<F: Field> SubCircuit<F> for CopyCircuit<F> {
10121008
block
10131009
.copy_events
10141010
.iter()
1015-
.map(|c| c.copy_bytes.bytes.len() * 2)
1011+
.map(|c| c.full_length() as usize * 2)
10161012
.sum::<usize>()
1017-
+ 2,
1013+
+ UNUSED_ROWS
1014+
+ DISABLED_ROWS,
10181015
block.circuits_params.max_copy_rows,
10191016
)
10201017
}

zkevm-circuits/src/copy_circuit/copy_gadgets.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::evm_circuit::util::constraint_builder::{BaseConstraintBuilder, Constr
1414
pub fn constrain_tag<F: Field>(
1515
meta: &mut ConstraintSystem<F>,
1616
q_enable: Column<Fixed>,
17-
tag: BinaryNumberConfig<CopyDataType, 4>,
17+
tag: &BinaryNumberConfig<CopyDataType, 4>,
1818
is_precompiled: Column<Advice>,
1919
is_tx_calldata: Column<Advice>,
2020
is_bytecode: Column<Advice>,
@@ -85,17 +85,22 @@ pub fn constrain_must_terminate<F: Field>(
8585
cb: &mut BaseConstraintBuilder<F>,
8686
meta: &mut VirtualCells<'_, F>,
8787
q_enable: Column<Fixed>,
88-
is_last: Expression<F>,
88+
tag: &BinaryNumberConfig<CopyDataType, 4>,
8989
) {
90-
// Prevent an event from spilling into the rows where constraints are disabled.
91-
// This also ensures that eventually is_last=1, because eventually q_enable=0.
92-
cb.require_zero(
93-
"the next row is enabled",
94-
and::expr([
95-
not::expr(is_last),
96-
not::expr(meta.query_fixed(q_enable, NEXT_ROW)),
97-
]),
98-
);
90+
// If an event has started (tag != Padding on reader and writer rows), require q_enable=1 at the
91+
// next step. This prevents querying rows where constraints are disabled.
92+
//
93+
// The tag is then copied to the next step by constrain_forward_parameters. Eventually,
94+
// q_enable=0. By that point the tag must have switched to Padding, which is only possible with
95+
// is_last=1. This guarantees that all the final conditions are checked.
96+
let is_event = tag.value(CURRENT)(meta) - tag.constant_expr::<F>(CopyDataType::Padding);
97+
cb.condition(is_event, |cb| {
98+
cb.require_equal(
99+
"the next step is enabled",
100+
meta.query_fixed(q_enable, NEXT_STEP),
101+
1.expr(),
102+
);
103+
});
99104
}
100105

101106
/// Copy the parameters of the event through all rows of the event.

zkevm-circuits/src/copy_circuit/test.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub fn test_copy_circuit_from_block<F: Field>(
6262
}
6363

6464
fn gen_calldatacopy_data() -> CircuitInputBuilder {
65-
let length = 0x0fffusize;
65+
let length = 512 / 2 - 32;
6666
let code = bytecode! {
6767
PUSH32(Word::from(length))
6868
PUSH32(Word::from(0x00))
@@ -87,9 +87,9 @@ fn gen_calldatacopy_data() -> CircuitInputBuilder {
8787
let mut builder = BlockData::new_from_geth_data_with_params(
8888
block.clone(),
8989
CircuitsParams {
90-
max_rws: 8192,
91-
max_copy_rows: 8192 + 2,
92-
max_calldata: 5000,
90+
max_rws: 512,
91+
max_copy_rows: 512,
92+
max_calldata: 512,
9393
..Default::default()
9494
},
9595
)
@@ -173,9 +173,9 @@ fn gen_returndatacopy_data() -> CircuitInputBuilder {
173173
let mut builder = BlockData::new_from_geth_data_with_params(
174174
block.clone(),
175175
CircuitsParams {
176-
max_rws: 8192,
177-
max_copy_rows: 8192 + 2,
178-
max_calldata: 5000,
176+
max_rws: 512,
177+
max_copy_rows: 512,
178+
max_calldata: 512,
179179
..Default::default()
180180
},
181181
)
@@ -224,14 +224,14 @@ fn gen_extcodecopy_data() -> CircuitInputBuilder {
224224
}
225225

226226
fn gen_sha3_data() -> CircuitInputBuilder {
227-
let (code, _) = gen_sha3_code(0x20, 0x200, MemoryKind::EqualToSize);
227+
let (code, _) = gen_sha3_code(0x20, 512 - 32, MemoryKind::EqualToSize);
228228
let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap();
229229
let block: GethData = test_ctx.into();
230230
let mut builder = BlockData::new_from_geth_data_with_params(
231231
block.clone(),
232232
CircuitsParams {
233-
max_rws: 2000,
234-
max_copy_rows: 0x250 * 2 + 2,
233+
max_rws: 1024,
234+
max_copy_rows: 1024,
235235
..Default::default()
236236
},
237237
)
@@ -308,7 +308,7 @@ fn gen_return_data() -> CircuitInputBuilder {
308308
fn copy_circuit_valid_calldatacopy() {
309309
let builder = gen_calldatacopy_data();
310310
let block = block_convert::<Fr>(&builder.block, &builder.code_db).unwrap();
311-
assert_eq!(test_copy_circuit_from_block(14, block), Ok(()));
311+
assert_eq!(test_copy_circuit_from_block(10, block), Ok(()));
312312
}
313313

314314
#[test]
@@ -322,7 +322,7 @@ fn copy_circuit_valid_codecopy() {
322322
fn copy_circuit_valid_returndatacopy() {
323323
let builder = gen_returndatacopy_data();
324324
let block = block_convert::<Fr>(&builder.block, &builder.code_db).unwrap();
325-
assert_eq!(test_copy_circuit_from_block(14, block), Ok(()));
325+
assert_eq!(test_copy_circuit_from_block(10, block), Ok(()));
326326
}
327327

328328
#[test]
@@ -373,7 +373,7 @@ fn copy_circuit_invalid_calldatacopy() {
373373
let block = block_convert::<Fr>(&builder.block, &builder.code_db).unwrap();
374374

375375
assert_error_matches(
376-
test_copy_circuit_from_block(14, block),
376+
test_copy_circuit_from_block(10, block),
377377
vec!["Memory word lookup", "Tx calldata lookup"],
378378
);
379379
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ mod test {
324324
CircuitTestBuilder::new_from_test_ctx(ctx)
325325
.params(CircuitsParams {
326326
max_rws: 2048,
327-
max_copy_rows: 1795,
327+
max_copy_rows: 1796,
328328
..Default::default()
329329
})
330330
.run();

0 commit comments

Comments
 (0)