From 521ed951588e3df169c7689c8dfa2082d88bf1e1 Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Mon, 16 Mar 2026 14:06:11 -0300 Subject: [PATCH 1/3] fix(aztec-nr): fix out-of-bounds index in extract_property_value_from_selector with nonzero offset --- noir-projects/aztec-nr/aztec/src/note/note_getter.nr | 2 +- .../aztec-nr/aztec/src/note/note_getter/test.nr | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr index 5ef40598bdc4..1ec70a120e34 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr @@ -33,7 +33,7 @@ fn extract_property_value_from_selector(packed_note: [Field; N], sel let mut acc: Field = 1; for i in 0..32 { if i < length { - value_field += value[(31 + offset - i) as u32] as Field * acc; + value_field += value[(31 - offset - i) as u32] as Field * acc; acc = acc * 256; } } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 9972fc632bee..417358d99d5d 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -323,3 +323,14 @@ unconstrained fn constrain_view_note_rejects_mismatched_owner() { let _ = confirm_hinted_note(context, note, Option::some(owner), storage_slot); }); } + +#[test] +unconstrained fn extract_property_value_from_selector_with_nonzero_offset() { + // 256 as a Field in big-endian bytes is [0, 0, ..., 0, 1, 0] (byte[30]=1, byte[31]=0). + // With offset: 1 and length: 1, we skip 1 byte from the least-significant end, + // so we should read byte[30] which is 1. + let packed = [256 as Field]; + let selector = PropertySelector { index: 0, offset: 1, length: 1 }; + let result = super::extract_property_value_from_selector(packed, selector); + assert_eq(result, 1); +} From 3c2598e296f20fb5c8f6bb3e69b1aacd9aa5435b Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Tue, 17 Mar 2026 15:03:31 -0300 Subject: [PATCH 2/3] fix(aztec-nr): add boundary assert and tests for extract_property_value_from_selector --- .../aztec-nr/aztec/src/note/note_getter.nr | 4 ++ .../aztec/src/note/note_getter/test.nr | 57 +++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr index 1ec70a120e34..3a354205f937 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter.nr @@ -29,10 +29,14 @@ fn extract_property_value_from_selector(packed_note: [Field; N], sel let value: [u8; 32] = packed_note[selector.index as u32].to_be_bytes(); let offset = selector.offset; let length = selector.length; + assert(offset as u32 + length as u32 <= 32, "PropertySelector offset + length exceeds field byte width"); let mut value_field = 0 as Field; let mut acc: Field = 1; for i in 0..32 { if i < length { + // `value` is big-endian, so the last byte (index 31) holds the lowest value. + // offset shifts the starting point away from that last byte, and i walks + // through consecutive bytes. value_field += value[(31 - offset - i) as u32] as Field * acc; acc = acc * 256; } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 417358d99d5d..1f01ca0abc9f 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -323,14 +323,61 @@ unconstrained fn constrain_view_note_rejects_mismatched_owner() { let _ = confirm_hinted_note(context, note, Option::some(owner), storage_slot); }); } +#[test] +unconstrained fn extract_property_value_full_field_is_identity() { + let packed = [12345 as Field]; + let selector = PropertySelector { index: 0, offset: 0, length: 32 }; + let result = super::extract_property_value_from_selector(packed, selector); + assert_eq(result, 12345); +} + +#[test] +unconstrained fn extract_property_value_zero_length_returns_zero() { + let packed = [12345 as Field]; + let selector = PropertySelector { index: 0, offset: 0, length: 0 }; + let result = super::extract_property_value_from_selector(packed, selector); + assert_eq(result, 0); +} + +#[test] +unconstrained fn extract_property_value_with_zero_offset() { + // 0x030201 = [..., 0x03, 0x02, 0x01]. offset=0, length=2 reads [0x01, 0x02] -> 0x0201. + let packed = [0x030201 as Field]; + let selector = PropertySelector { index: 0, offset: 0, length: 2 }; + let result = super::extract_property_value_from_selector(packed, selector); + assert_eq(result, 0x0201); +} #[test] -unconstrained fn extract_property_value_from_selector_with_nonzero_offset() { - // 256 as a Field in big-endian bytes is [0, 0, ..., 0, 1, 0] (byte[30]=1, byte[31]=0). - // With offset: 1 and length: 1, we skip 1 byte from the least-significant end, - // so we should read byte[30] which is 1. - let packed = [256 as Field]; +unconstrained fn extract_property_value_with_nonzero_offset() { + // 0x0100 = [..., 0x01, 0x00]. offset=1 skips 0x00, length=1 reads 0x01. + let packed = [0x0100 as Field]; let selector = PropertySelector { index: 0, offset: 1, length: 1 }; let result = super::extract_property_value_from_selector(packed, selector); assert_eq(result, 1); } + +#[test] +unconstrained fn extract_property_value_nonzero_index() { + // index=1 skips the first Field (999) and reads from the second one (0x030201). + let packed = [999 as Field, 0x030201 as Field]; + let selector = PropertySelector { index: 1, offset: 1, length: 2 }; + let result = super::extract_property_value_from_selector(packed, selector); + assert_eq(result, 0x0302); +} + +#[test] +unconstrained fn extract_property_value_max_offset_reads_first_byte() { + // 256^31 = byte[0]=1, rest=0. offset=31, length=1 reads byte[0]. + let packed = [Field::pow_32(256, 31)]; + let selector = PropertySelector { index: 0, offset: 31, length: 1 }; + let result = super::extract_property_value_from_selector(packed, selector); + assert_eq(result, 1); +} + +#[test(should_fail_with = "PropertySelector offset + length exceeds field byte width")] +unconstrained fn extract_property_value_rejects_offset_plus_length_exceeding_32() { + let packed = [1 as Field]; + let selector = PropertySelector { index: 0, offset: 31, length: 2 }; + let _ = super::extract_property_value_from_selector(packed, selector); +} From 988fd0156d2b61d87b33248f83e70007fe188bad Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Tue, 17 Mar 2026 16:03:24 -0300 Subject: [PATCH 3/3] fix(aztec-nr): address review -- import fn, add OOB index test --- .../aztec/src/note/note_getter/test.nr | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 1f01ca0abc9f..f26c7bc20006 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -2,7 +2,7 @@ use crate::{ note::{ ConfirmedNote, HintedNote, - note_getter::{confirm_hinted_note, confirm_hinted_notes}, + note_getter::{confirm_hinted_note, confirm_hinted_notes, extract_property_value_from_selector}, note_getter_options::{NoteGetterOptions, PropertySelector, SortOrder}, }, oracle::random::random, @@ -327,7 +327,7 @@ unconstrained fn constrain_view_note_rejects_mismatched_owner() { unconstrained fn extract_property_value_full_field_is_identity() { let packed = [12345 as Field]; let selector = PropertySelector { index: 0, offset: 0, length: 32 }; - let result = super::extract_property_value_from_selector(packed, selector); + let result = extract_property_value_from_selector(packed, selector); assert_eq(result, 12345); } @@ -335,7 +335,7 @@ unconstrained fn extract_property_value_full_field_is_identity() { unconstrained fn extract_property_value_zero_length_returns_zero() { let packed = [12345 as Field]; let selector = PropertySelector { index: 0, offset: 0, length: 0 }; - let result = super::extract_property_value_from_selector(packed, selector); + let result = extract_property_value_from_selector(packed, selector); assert_eq(result, 0); } @@ -344,7 +344,7 @@ unconstrained fn extract_property_value_with_zero_offset() { // 0x030201 = [..., 0x03, 0x02, 0x01]. offset=0, length=2 reads [0x01, 0x02] -> 0x0201. let packed = [0x030201 as Field]; let selector = PropertySelector { index: 0, offset: 0, length: 2 }; - let result = super::extract_property_value_from_selector(packed, selector); + let result = extract_property_value_from_selector(packed, selector); assert_eq(result, 0x0201); } @@ -353,7 +353,7 @@ unconstrained fn extract_property_value_with_nonzero_offset() { // 0x0100 = [..., 0x01, 0x00]. offset=1 skips 0x00, length=1 reads 0x01. let packed = [0x0100 as Field]; let selector = PropertySelector { index: 0, offset: 1, length: 1 }; - let result = super::extract_property_value_from_selector(packed, selector); + let result = extract_property_value_from_selector(packed, selector); assert_eq(result, 1); } @@ -362,7 +362,7 @@ unconstrained fn extract_property_value_nonzero_index() { // index=1 skips the first Field (999) and reads from the second one (0x030201). let packed = [999 as Field, 0x030201 as Field]; let selector = PropertySelector { index: 1, offset: 1, length: 2 }; - let result = super::extract_property_value_from_selector(packed, selector); + let result = extract_property_value_from_selector(packed, selector); assert_eq(result, 0x0302); } @@ -371,7 +371,7 @@ unconstrained fn extract_property_value_max_offset_reads_first_byte() { // 256^31 = byte[0]=1, rest=0. offset=31, length=1 reads byte[0]. let packed = [Field::pow_32(256, 31)]; let selector = PropertySelector { index: 0, offset: 31, length: 1 }; - let result = super::extract_property_value_from_selector(packed, selector); + let result = extract_property_value_from_selector(packed, selector); assert_eq(result, 1); } @@ -379,5 +379,12 @@ unconstrained fn extract_property_value_max_offset_reads_first_byte() { unconstrained fn extract_property_value_rejects_offset_plus_length_exceeding_32() { let packed = [1 as Field]; let selector = PropertySelector { index: 0, offset: 31, length: 2 }; - let _ = super::extract_property_value_from_selector(packed, selector); + let _ = extract_property_value_from_selector(packed, selector); +} + +#[test(should_fail_with = "Index out of bounds")] +unconstrained fn extract_property_value_fails_on_oob_index() { + let packed = [1 as Field]; + let selector = PropertySelector { index: 1, offset: 0, length: 1 }; + let _ = extract_property_value_from_selector(packed, selector); }