aztec_pxe/execution/pick_notes.rs
1//! Note filtering by select clauses — mirrors upstream `pick_notes.ts`.
2//!
3//! Both the private and utility oracles delegate to [`select_notes`] after
4//! fetching the raw note set from the note store.
5
6use crate::stores::note_store::StoredNote;
7use aztec_core::types::Fr;
8
9// ---------------------------------------------------------------------------
10// Select clause
11// ---------------------------------------------------------------------------
12
13/// A single select filter parsed from the oracle arguments.
14#[derive(Debug, Clone)]
15pub struct SelectClause {
16 /// Index of the field in the packed note content.
17 pub index: usize,
18 /// Byte offset within the field.
19 pub offset: usize,
20 /// Number of bytes to compare (0 means full field = 32 bytes).
21 pub length: usize,
22 /// Value to compare against.
23 pub value: Fr,
24 /// Comparator (1=EQ, 2=NEQ, 3=LT, 4=LTE, 5=GT, 6=GTE).
25 pub comparator: u8,
26}
27
28// ---------------------------------------------------------------------------
29// Comparator constants
30// ---------------------------------------------------------------------------
31
32const EQ: u8 = 1;
33const NEQ: u8 = 2;
34const LT: u8 = 3;
35const LTE: u8 = 4;
36const GT: u8 = 5;
37const GTE: u8 = 6;
38
39// ---------------------------------------------------------------------------
40// Parsing
41// ---------------------------------------------------------------------------
42
43/// Parse select clauses from the oracle `get_notes` arguments.
44///
45/// Layout:
46/// - `args[3]` → numSelects (single value)
47/// - `args[4]` → selectByIndexes (array)
48/// - `args[5]` → selectByOffsets (array)
49/// - `args[6]` → selectByLengths (array)
50/// - `args[7]` → selectValues (array)
51/// - `args[8]` → selectComparators (array)
52pub fn parse_select_clauses(args: &[Vec<Fr>]) -> Vec<SelectClause> {
53 let num_selects = args
54 .get(3)
55 .and_then(|v| v.first())
56 .map(Fr::to_usize)
57 .unwrap_or(0);
58
59 if num_selects == 0 {
60 return Vec::new();
61 }
62
63 let indexes = args.get(4).cloned().unwrap_or_default();
64 let offsets = args.get(5).cloned().unwrap_or_default();
65 let lengths = args.get(6).cloned().unwrap_or_default();
66 let values = args.get(7).cloned().unwrap_or_default();
67 let comparators = args.get(8).cloned().unwrap_or_default();
68
69 (0..num_selects)
70 .map(|i| SelectClause {
71 index: indexes.get(i).map(Fr::to_usize).unwrap_or(0),
72 offset: offsets.get(i).map(Fr::to_usize).unwrap_or(0),
73 length: lengths.get(i).map(Fr::to_usize).unwrap_or(0),
74 value: values.get(i).copied().unwrap_or(Fr::zero()),
75 comparator: comparators.get(i).map(|f| f.to_usize() as u8).unwrap_or(0),
76 })
77 .collect()
78}
79
80// ---------------------------------------------------------------------------
81// Filtering
82// ---------------------------------------------------------------------------
83
84/// Extract a property value from a note's packed content, applying the
85/// sub-field selector (offset/length). Returns an `Fr` suitable for
86/// comparison.
87fn extract_property(note_data: &[Fr], clause: &SelectClause) -> Option<Fr> {
88 let field = note_data.get(clause.index)?;
89
90 // Full-field comparison (most common case).
91 if clause.offset == 0 && (clause.length == 0 || clause.length >= 32) {
92 return Some(*field);
93 }
94
95 // Sub-field extraction: convert to big-endian bytes, slice, convert back.
96 let bytes = field.to_be_bytes();
97 let end = (clause.offset + clause.length).min(32);
98 let mut buf = [0u8; 32];
99 let slice = &bytes[clause.offset..end];
100 // Right-align in 32-byte buffer for correct numeric value.
101 buf[32 - slice.len()..].copy_from_slice(slice);
102 Some(Fr::from(buf))
103}
104
105/// Compare two field elements using the given comparator.
106fn compare(note_val: &Fr, select_val: &Fr, comparator: u8) -> bool {
107 match comparator {
108 EQ => note_val == select_val,
109 NEQ => note_val != select_val,
110 LT => note_val < select_val,
111 LTE => note_val <= select_val,
112 GT => note_val > select_val,
113 GTE => note_val >= select_val,
114 _ => true, // unknown comparator → don't filter
115 }
116}
117
118/// Filter notes by all select clauses (AND semantics).
119pub fn select_notes(notes: Vec<StoredNote>, clauses: &[SelectClause]) -> Vec<StoredNote> {
120 if clauses.is_empty() {
121 return notes;
122 }
123 notes
124 .into_iter()
125 .filter(|note| {
126 clauses.iter().all(|clause| {
127 extract_property(¬e.note_data, clause)
128 .map_or(false, |val| compare(&val, &clause.value, clause.comparator))
129 })
130 })
131 .collect()
132}