Skip to content

Commit 6940053

Browse files
fix: assert empty trimmed contract class logs (#20457)
The "trimmed" contract class logs should be completely empty, not just the fields.
2 parents b34c360 + a1eeb70 commit 6940053

File tree

3 files changed

+382
-15
lines changed

3 files changed

+382
-15
lines changed

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/components/previous_kernel_for_tail_validator/validate_accumulated_items.nr

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,28 @@ pub fn validate_accumulated_items(
3333
// L2 to L1 messages within the claimed length are guaranteed to have non empty contract address, which will be exposed to public.
3434
assert_trimmed_array(l2_to_l1_msgs, |msg| msg.expose_to_public().is_empty());
3535

36+
// The check for "denseness" (non-emptiness) of both private and contract class logs is different from the check for
37+
// emptiness, for efficiency reasons. Here, we perform separate checks: one for denseness, and one for trimmed
38+
// emptiness.
39+
// The tx base rollups add logs to the blob data by referencing their `.length`. Therefore, we only need to ensure
40+
// that logs within the claimed length have non-zero length. It does not matter if all of the fields in those
41+
// non-zero-length logs are zero (although that's not possible for private logs, because the reset circuit silos the
42+
// first field).
43+
// For logs beyond the claimed length, not only must they each have length 0, they also must be empty. Even though
44+
// the tx base rollups will not add logs with length 0 to the blob data, extra non-empty fields would be stored or
45+
// broadcasted on the sequencer/prover side. This would have been extra work that was not compensated, as we do not
46+
// charge for logs with zero length.
3647
assert_dense_array(private_logs, |log| log.innermost().log.length != 0);
48+
// We only check whether the *innermost* private log is empty, since the contract address and counter are not
49+
// exposed to the public.
3750
assert_trimmed_array(private_logs, |log| log.innermost().log.is_empty());
3851

3952
assert_dense_array(
4053
contract_class_logs_hashes,
4154
|log_hash| log_hash.innermost().length != 0,
4255
);
43-
assert_trimmed_array(
44-
contract_class_logs_hashes,
45-
|log_hash| log_hash.innermost().is_empty(),
46-
);
56+
// We require the entire contract class log hash to be empty because the contract address is exposed to the public.
57+
assert_trimmed_array(contract_class_logs_hashes, |log_hash| log_hash.is_empty());
4758

4859
// For `public_call_requests`, we only need to ensure that the trimmed items are nullish. Items emitted from private
4960
// functions are guaranteed to be non-empty - their `msg_sender` is either the caller's contract address (which

noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_tail/previous_kernel_validation_tests.nr

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use super::TestBuilder;
22
use types::{
3-
address::AztecAddress,
3+
address::{AztecAddress, EthAddress},
44
constants::{
55
CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, DOM_SEP__IVSK_M, PRIVATE_KERNEL_TAIL_VK_INDEX,
66
PRIVATE_LOG_SIZE_IN_FIELDS,
77
},
88
point::Point,
99
side_effect::Scoped,
10-
traits::Empty,
10+
traits::{Empty, FromField},
1111
};
1212

1313
// --- validation_requests ---
@@ -113,6 +113,19 @@ fn empty_nullifier_as_valid_item() {
113113
let _ = builder.execute();
114114
}
115115

116+
#[test(should_fail_with = "array is not dense trimmed")]
117+
fn non_empty_nullifier_beyond_claimed_length() {
118+
let mut builder = TestBuilder::new();
119+
120+
builder.previous_kernel.append_nullifiers(3);
121+
let mut array = builder.previous_kernel.nullifiers.storage();
122+
// Add a non-empty nullifier beyond the claimed length.
123+
array[5] = array[1];
124+
builder.previous_kernel.nullifiers = BoundedVec::from_parts_unchecked(array, 4);
125+
126+
let _ = builder.execute();
127+
}
128+
116129
#[test(should_fail_with = "Cannot link a note hash emitted after a nullifier")]
117130
fn nullifier_emitted_after_its_note_hash() {
118131
let mut builder = TestBuilder::new();
@@ -137,6 +150,56 @@ fn nullifiers_for_note_hashes_not_found() {
137150
let _ = builder.execute();
138151
}
139152

153+
#[test(should_fail_with = "call to assert_max_bit_size")]
154+
fn invalid_l2_to_l1_msg_recipient() {
155+
let mut builder = TestBuilder::new();
156+
builder.previous_kernel.append_l2_to_l1_msgs(1);
157+
let mut l2_to_l1_msg = builder.previous_kernel.l2_to_l1_msgs.pop();
158+
// More than 160 bits.
159+
l2_to_l1_msg.inner.inner.recipient = EthAddress::from_field(-1);
160+
builder.previous_kernel.l2_to_l1_msgs.push(l2_to_l1_msg);
161+
let _ = builder.execute();
162+
}
163+
164+
#[test(should_fail_with = "array is not trimmed")]
165+
fn non_empty_l2_to_l1_msg_recipient_beyond_claimed_length() {
166+
let mut builder = TestBuilder::new();
167+
168+
builder.previous_kernel.append_l2_to_l1_msgs(2);
169+
let mut array = builder.previous_kernel.l2_to_l1_msgs.storage();
170+
// Add a non-empty recipient to an L2 to L1 message beyond the claimed length.
171+
array[5].inner.inner.recipient = array[0].innermost().recipient;
172+
builder.previous_kernel.l2_to_l1_msgs = BoundedVec::from_parts_unchecked(array, 2);
173+
174+
let _ = builder.execute();
175+
}
176+
177+
#[test(should_fail_with = "array is not trimmed")]
178+
fn non_empty_l2_to_l1_msg_content_beyond_claimed_length() {
179+
let mut builder = TestBuilder::new();
180+
181+
builder.previous_kernel.append_l2_to_l1_msgs(2);
182+
let mut array = builder.previous_kernel.l2_to_l1_msgs.storage();
183+
// Add a non-empty content to an L2 to L1 message beyond the claimed length.
184+
array[5].inner.inner.content = array[0].innermost().content;
185+
builder.previous_kernel.l2_to_l1_msgs = BoundedVec::from_parts_unchecked(array, 2);
186+
187+
let _ = builder.execute();
188+
}
189+
190+
#[test(should_fail_with = "array is not trimmed")]
191+
fn non_empty_l2_to_l1_msg_contract_address_beyond_claimed_length() {
192+
let mut builder = TestBuilder::new();
193+
194+
builder.previous_kernel.append_l2_to_l1_msgs(2);
195+
let mut array = builder.previous_kernel.l2_to_l1_msgs.storage();
196+
// Add a non-empty contract address to an L2 to L1 message beyond the claimed length.
197+
array[5].contract_address = array[0].contract_address;
198+
builder.previous_kernel.l2_to_l1_msgs = BoundedVec::from_parts_unchecked(array, 2);
199+
200+
let _ = builder.execute();
201+
}
202+
140203
#[test(should_fail_with = "Private log length exceeds max")]
141204
fn private_log_length_exceeds_max() {
142205
let mut builder = TestBuilder::new();
@@ -151,6 +214,43 @@ fn private_log_length_exceeds_max() {
151214
let _ = builder.execute();
152215
}
153216

217+
#[test(should_fail_with = "array is not dense")]
218+
fn empty_private_log_as_valid_item() {
219+
let mut builder = TestBuilder::new();
220+
221+
builder.previous_kernel.append_private_logs(2);
222+
// Add an empty private log.
223+
builder.previous_kernel.private_logs.push(Scoped::empty());
224+
225+
let _ = builder.execute();
226+
}
227+
228+
#[test(should_fail_with = "array is not trimmed")]
229+
fn non_empty_private_log_length_beyond_claimed_length() {
230+
let mut builder = TestBuilder::new();
231+
232+
builder.previous_kernel.append_private_logs(2);
233+
let mut array = builder.previous_kernel.private_logs.storage();
234+
// Add a private log with non-zero length beyond the claimed length.
235+
array[5].inner.inner.log.length = 1;
236+
builder.previous_kernel.private_logs = BoundedVec::from_parts_unchecked(array, 2);
237+
238+
let _ = builder.execute();
239+
}
240+
241+
#[test(should_fail_with = "array is not trimmed")]
242+
fn non_empty_private_log_fields_beyond_claimed_length() {
243+
let mut builder = TestBuilder::new();
244+
245+
builder.previous_kernel.append_private_logs(2);
246+
let mut array = builder.previous_kernel.private_logs.storage();
247+
// Add a private log with non-empty fields beyond the claimed length.
248+
array[5].inner.inner.log.fields[2] = 1;
249+
builder.previous_kernel.private_logs = BoundedVec::from_parts_unchecked(array, 2);
250+
251+
let _ = builder.execute();
252+
}
253+
154254
#[test(should_fail_with = "Contract class log length exceeds max")]
155255
fn contract_class_log_length_exceeds_max() {
156256
let mut builder = TestBuilder::new();
@@ -160,6 +260,52 @@ fn contract_class_log_length_exceeds_max() {
160260
let _ = builder.execute();
161261
}
162262

263+
#[test(should_fail_with = "array is not dense")]
264+
fn empty_contract_class_log_as_valid_item() {
265+
let mut builder = TestBuilder::new();
266+
267+
// Add an empty contract class log hash as a valid item.
268+
builder.previous_kernel.contract_class_logs_hashes.push(Scoped::empty());
269+
270+
let _ = builder.execute();
271+
}
272+
273+
#[test(should_fail_with = "array is not trimmed")]
274+
fn non_empty_contract_class_log_length_beyond_claimed_length() {
275+
let mut builder = TestBuilder::new();
276+
277+
let mut array = builder.previous_kernel.contract_class_logs_hashes.storage();
278+
// Add a contract class log with non-zero length beyond the claimed length.
279+
array[0].inner.inner.length = 1;
280+
builder.previous_kernel.contract_class_logs_hashes = BoundedVec::from_parts_unchecked(array, 0);
281+
282+
let _ = builder.execute();
283+
}
284+
285+
#[test(should_fail_with = "array is not trimmed")]
286+
fn non_empty_contract_class_log_hash_beyond_claimed_length() {
287+
let mut builder = TestBuilder::new();
288+
289+
let mut array = builder.previous_kernel.contract_class_logs_hashes.storage();
290+
// Add a contract class log with non-empty log hash value beyond the claimed length.
291+
array[0].inner.inner.value = 1;
292+
builder.previous_kernel.contract_class_logs_hashes = BoundedVec::from_parts_unchecked(array, 0);
293+
294+
let _ = builder.execute();
295+
}
296+
297+
#[test(should_fail_with = "array is not trimmed")]
298+
fn non_empty_contract_class_log_contract_address_beyond_claimed_length() {
299+
let mut builder = TestBuilder::new();
300+
301+
let mut array = builder.previous_kernel.contract_class_logs_hashes.storage();
302+
// Add a contract class log with non-empty contract address beyond the claimed length.
303+
array[0].contract_address = AztecAddress::from_field(1);
304+
builder.previous_kernel.contract_class_logs_hashes = BoundedVec::from_parts_unchecked(array, 0);
305+
306+
let _ = builder.execute();
307+
}
308+
163309
#[test(should_fail_with = "Public call stack must be empty when executing the tail circuit")]
164310
fn non_empty_public_call_stack() {
165311
let mut builder = TestBuilder::new();

0 commit comments

Comments
 (0)