1use aztec_core::constants::domain_separator;
4use aztec_core::error::Error;
5use aztec_core::fee::GasFees;
6use aztec_core::grumpkin;
7use aztec_core::hash::poseidon2_hash;
8use aztec_core::kernel_types::{
9 AppendOnlyTreeSnapshot, BlockHeader, GlobalVariables, PartialStateReference, StateReference,
10};
11use aztec_core::tx::TxHash;
12use aztec_core::types::{AztecAddress, Fq, Fr};
13use aztec_node_client::AztecNode;
14
15use super::acvm_executor::OracleCallback;
16use crate::stores::note_store::{NoteFilter, NoteStatus, StoredNote};
17use crate::stores::{
18 AddressStore, AnchorBlockStore, CapsuleStore, ContractStore, KeyStore, NoteStore,
19 PrivateEventStore, RecipientTaggingStore, SenderStore, SenderTaggingStore,
20};
21use crate::sync::event_service::EventService;
22use crate::sync::log_service::{LogRetrievalRequest, LogService};
23use crate::sync::note_service::NoteService;
24
25pub struct UtilityExecutionOracle<'a, N: AztecNode> {
27 node: &'a N,
28 contract_store: &'a ContractStore,
29 key_store: &'a KeyStore,
30 note_store: &'a NoteStore,
31 address_store: &'a AddressStore,
32 capsule_store: &'a CapsuleStore,
33 sender_store: &'a SenderStore,
34 sender_tagging_store: &'a SenderTaggingStore,
35 recipient_tagging_store: &'a RecipientTaggingStore,
36 private_event_store: &'a PrivateEventStore,
37 anchor_block_store: &'a AnchorBlockStore,
38 block_header: serde_json::Value,
39 contract_address: AztecAddress,
40 scopes: Vec<AztecAddress>,
41 auth_witnesses: Vec<(Fr, Vec<Fr>)>,
43}
44
45fn decode_base64_sibling_path(encoded: &str) -> Result<Vec<Fr>, Error> {
46 use base64::Engine;
47
48 let bytes = base64::engine::general_purpose::STANDARD
49 .decode(encoded)
50 .map_err(|e| Error::InvalidData(format!("invalid siblingPath base64: {e}")))?;
51
52 let payload = if bytes.len() >= 4 {
53 let declared_len =
54 u32::from_be_bytes(bytes[..4].try_into().expect("length prefix is 4 bytes")) as usize;
55 let payload = &bytes[4..];
56 if payload.len() == declared_len.saturating_mul(32) {
57 payload
58 } else if bytes.len() % 32 == 0 {
59 bytes.as_slice()
60 } else {
61 return Err(Error::InvalidData(format!(
62 "siblingPath payload length mismatch: declared {declared_len} elements, got {} bytes",
63 payload.len()
64 )));
65 }
66 } else {
67 bytes.as_slice()
68 };
69
70 Ok(payload
71 .chunks(32)
72 .map(|chunk| {
73 let mut padded = [0u8; 32];
74 let start = 32usize.saturating_sub(chunk.len());
75 padded[start..].copy_from_slice(chunk);
76 Fr::from(padded)
77 })
78 .collect())
79}
80
81fn parse_field_string(value: &str) -> Result<Fr, Error> {
82 if value.starts_with("0x") {
83 Fr::from_hex(value)
84 } else {
85 value
86 .parse::<u128>()
87 .map(Fr::from)
88 .map_err(|_| Error::InvalidData(format!("unsupported field string value: {value}")))
89 }
90}
91
92impl<'a, N: AztecNode> UtilityExecutionOracle<'a, N> {
93 fn note_status_from_field(value: Fr) -> Result<NoteStatus, Error> {
95 match value.to_usize() as u64 {
96 1 => Ok(NoteStatus::Active),
97 2 => Ok(NoteStatus::ActiveOrNullified),
98 other => Err(Error::InvalidData(format!("unknown note status: {other}"))),
99 }
100 }
101
102 fn pack_hinted_note(note: &StoredNote) -> Result<Vec<Fr>, Error> {
103 let mut packed = note.note_data.clone();
104 packed.push(note.contract_address.0);
105 packed.push(note.owner.0);
106 packed.push(note.randomness);
107 packed.push(note.storage_slot);
108 let stage = if note.is_pending {
109 if note.note_nonce == Fr::zero() {
110 1u64
111 } else {
112 2u64
113 }
114 } else {
115 if note.note_nonce == Fr::zero() {
116 return Err(Error::InvalidData(
117 "cannot pack settled note with zero note_nonce".into(),
118 ));
119 }
120 3u64
121 };
122 packed.push(Fr::from(stage));
123 packed.push(note.note_nonce);
124 Ok(packed)
125 }
126
127 fn pack_bounded_vec_of_arrays(
128 arrays: &[Vec<Fr>],
129 max_len: usize,
130 nested_len: usize,
131 ) -> Result<Vec<Vec<Fr>>, Error> {
132 if arrays.len() > max_len {
133 return Err(Error::InvalidData(format!(
134 "bounded vec overflow: {} > {max_len}",
135 arrays.len()
136 )));
137 }
138
139 let mut flattened = Vec::with_capacity(max_len.saturating_mul(nested_len));
140 for array in arrays {
141 if array.len() != nested_len {
142 return Err(Error::InvalidData(format!(
143 "packed hinted note length mismatch: {} != {nested_len}",
144 array.len()
145 )));
146 }
147 flattened.extend_from_slice(array);
148 }
149
150 flattened.resize(max_len.saturating_mul(nested_len), Fr::zero());
151 Ok(vec![flattened, vec![Fr::from(arrays.len() as u64)]])
152 }
153
154 pub fn new(
155 node: &'a N,
156 contract_store: &'a ContractStore,
157 key_store: &'a KeyStore,
158 note_store: &'a NoteStore,
159 address_store: &'a AddressStore,
160 capsule_store: &'a CapsuleStore,
161 sender_store: &'a SenderStore,
162 sender_tagging_store: &'a SenderTaggingStore,
163 recipient_tagging_store: &'a RecipientTaggingStore,
164 private_event_store: &'a PrivateEventStore,
165 anchor_block_store: &'a AnchorBlockStore,
166 block_header: serde_json::Value,
167 contract_address: AztecAddress,
168 scopes: Vec<AztecAddress>,
169 ) -> Self {
170 Self {
171 node,
172 contract_store,
173 key_store,
174 note_store,
175 address_store,
176 capsule_store,
177 sender_store,
178 sender_tagging_store,
179 recipient_tagging_store,
180 private_event_store,
181 anchor_block_store,
182 block_header,
183 contract_address,
184 scopes,
185 auth_witnesses: Vec::new(),
186 }
187 }
188
189 pub fn set_auth_witnesses(&mut self, witnesses: Vec<(Fr, Vec<Fr>)>) {
191 self.auth_witnesses = witnesses;
192 }
193
194 pub async fn handle_foreign_call(
199 &self,
200 name: &str,
201 args: Vec<Vec<Fr>>,
202 ) -> Result<Vec<Vec<Fr>>, Error> {
203 let stripped = name
205 .strip_prefix("utility")
206 .or_else(|| name.strip_prefix("private"))
207 .unwrap_or(name);
208
209 let handler = if !stripped.is_empty() {
210 let mut chars = stripped.chars();
211 let first = chars.next().unwrap().to_lowercase().to_string();
212 format!("{first}{}", chars.as_str())
213 } else {
214 name.to_owned()
215 };
216
217 match handler.as_str() {
218 "getPublicStorageAt" | "storageRead" => self.get_public_storage_at(&args).await,
220 "getContractInstance" => self.get_contract_instance(&args).await,
221
222 "getNotes" => self.get_notes(&args).await,
224 "checkNullifierExists" => self.check_nullifier_exists(&args).await,
225
226 "getPublicKeysAndPartialAddress" | "tryGetPublicKeysAndPartialAddress" => {
228 self.get_public_keys_and_partial_address(&args).await
229 }
230 "getKeyValidationRequest" | "getSecretKey" => {
231 self.get_key_validation_request(&args).await
232 }
233
234 "getBlockHeader" => self.get_block_header(&args),
236 "getUtilityContext" => self.get_utility_context(),
237
238 "getAuthWitness" => self.get_auth_witness(&args),
240
241 "getNoteHashMembershipWitness" => Ok(vec![vec![]]),
243 "getNullifierMembershipWitness" => self.get_nullifier_membership_witness(&args).await,
244 "getLowNullifierMembershipWitness" => {
245 self.get_low_nullifier_membership_witness(&args).await
246 }
247 "getBlockHashMembershipWitness" => Ok(vec![vec![]]),
248 "getPublicDataWitness" => self.get_public_data_witness(&args).await,
249 "getL1ToL2MembershipWitness" => Ok(vec![vec![]]),
250
251 "getRandomField" => Ok(vec![vec![Fr::random()]]),
253 "assertCompatibleOracleVersion" => Ok(vec![]),
254 "log" => Ok(vec![]),
255 "aes128Decrypt" => self.aes128_decrypt(&args),
256 "getSharedSecret" => self.get_shared_secret(&args).await,
257
258 "loadCapsule" | "getCapsule" => self.load_capsule(&args).await,
260 "storeCapsule" => self.store_capsule(&args).await,
261 "deleteCapsule" => self.delete_capsule(&args).await,
262 "copyCapsule" => self.copy_capsule(&args).await,
263
264 "fetchTaggedLogs" => self.fetch_tagged_logs(&args).await,
266 "bulkRetrieveLogs" => self.bulk_retrieve_logs(&args).await,
267 "validateAndStoreEnqueuedNotesAndEvents" => {
268 self.validate_and_store_enqueued_notes_and_events(&args)
269 .await
270 }
271 "emitOffchainEffect" => Ok(vec![]),
272
273 _ => {
274 tracing::warn!(
275 oracle = name,
276 handler = handler.as_str(),
277 "unknown utility oracle call"
278 );
279 Ok(vec![])
280 }
281 }
282 }
283
284 fn fr_at(val: &serde_json::Value, path: &str) -> Fr {
285 match val.pointer(path) {
286 Some(serde_json::Value::String(s)) => Fr::from_hex(s).unwrap_or(Fr::zero()),
287 Some(serde_json::Value::Number(n)) => Fr::from(n.as_u64().unwrap_or(0)),
288 _ => Fr::zero(),
289 }
290 }
291
292 fn u64_at(val: &serde_json::Value, path: &str) -> u64 {
293 match val.pointer(path) {
294 Some(serde_json::Value::Number(n)) => n.as_u64().unwrap_or(0),
295 Some(serde_json::Value::String(s)) => {
296 if let Some(hex) = s.strip_prefix("0x") {
297 u64::from_str_radix(hex, 16).unwrap_or(0)
298 } else {
299 s.parse::<u64>().unwrap_or(0)
300 }
301 }
302 _ => 0,
303 }
304 }
305
306 fn u128_at(val: &serde_json::Value, path: &str) -> u128 {
307 match val.pointer(path) {
308 Some(serde_json::Value::Number(n)) => n.as_u64().unwrap_or(0) as u128,
309 Some(serde_json::Value::String(s)) => {
310 if let Some(hex) = s.strip_prefix("0x") {
311 u128::from_str_radix(hex, 16).unwrap_or(0)
312 } else {
313 s.parse::<u128>().unwrap_or(0)
314 }
315 }
316 _ => 0,
317 }
318 }
319
320 fn eth_at(val: &serde_json::Value, path: &str) -> aztec_core::types::EthAddress {
321 match val.pointer(path).and_then(|v| v.as_str()) {
322 Some(s) => {
323 let fr = Fr::from_hex(s).unwrap_or(Fr::zero());
324 let bytes = fr.to_be_bytes();
325 let mut addr = [0u8; 20];
326 addr.copy_from_slice(&bytes[12..32]);
327 aztec_core::types::EthAddress(addr)
328 }
329 None => aztec_core::types::EthAddress::default(),
330 }
331 }
332
333 fn snapshot_at(val: &serde_json::Value, prefix: &str) -> AppendOnlyTreeSnapshot {
334 AppendOnlyTreeSnapshot {
335 root: Self::fr_at(val, &format!("{prefix}/root")),
336 next_available_leaf_index: Self::u64_at(
337 val,
338 &format!("{prefix}/nextAvailableLeafIndex"),
339 ) as u32,
340 }
341 }
342
343 fn parse_block_header(&self) -> BlockHeader {
344 let h = &self.block_header;
345 BlockHeader {
346 last_archive: Self::snapshot_at(h, "/lastArchive"),
347 state: StateReference {
348 l1_to_l2_message_tree: Self::snapshot_at(h, "/state/l1ToL2MessageTree"),
349 partial: PartialStateReference {
350 note_hash_tree: Self::snapshot_at(h, "/state/partial/noteHashTree"),
351 nullifier_tree: Self::snapshot_at(h, "/state/partial/nullifierTree"),
352 public_data_tree: Self::snapshot_at(h, "/state/partial/publicDataTree"),
353 },
354 },
355 sponge_blob_hash: Self::fr_at(h, "/spongeBlobHash"),
356 global_variables: GlobalVariables {
357 chain_id: Self::fr_at(h, "/globalVariables/chainId"),
358 version: Self::fr_at(h, "/globalVariables/version"),
359 block_number: Self::u64_at(h, "/globalVariables/blockNumber"),
360 slot_number: Self::u64_at(h, "/globalVariables/slotNumber"),
361 timestamp: Self::u64_at(h, "/globalVariables/timestamp"),
362 coinbase: Self::eth_at(h, "/globalVariables/coinbase"),
363 fee_recipient: AztecAddress(Self::fr_at(h, "/globalVariables/feeRecipient")),
364 gas_fees: GasFees {
365 fee_per_da_gas: Self::u128_at(h, "/globalVariables/gasFees/feePerDaGas"),
366 fee_per_l2_gas: Self::u128_at(h, "/globalVariables/gasFees/feePerL2Gas"),
367 },
368 },
369 total_fees: Self::fr_at(h, "/totalFees"),
370 total_mana_used: Self::fr_at(h, "/totalManaUsed"),
371 }
372 }
373
374 fn get_utility_context(&self) -> Result<Vec<Vec<Fr>>, Error> {
375 let mut outputs: Vec<Vec<Fr>> = self
376 .parse_block_header()
377 .to_fields()
378 .into_iter()
379 .map(|f| vec![f])
380 .collect();
381 outputs.push(vec![self.contract_address.0]);
382 Ok(outputs)
383 }
384
385 fn get_block_header(&self, _args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
386 Ok(self
387 .parse_block_header()
388 .to_fields()
389 .into_iter()
390 .map(|f| vec![f])
391 .collect())
392 }
393
394 async fn get_public_storage_at(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
395 fn slot_with_offset(start_slot: Fr, offset: usize) -> Fr {
396 let mut bytes = start_slot.to_be_bytes();
397 let mut carry = offset as u128;
398 for byte in bytes.iter_mut().rev() {
399 if carry == 0 {
400 break;
401 }
402 let sum = u128::from(*byte) + (carry & 0xff);
403 *byte = (sum & 0xff) as u8;
404 carry = (carry >> 8) + (sum >> 8);
405 }
406 Fr::from(bytes)
407 }
408
409 let (block_hash, contract, start_slot, number_of_elements) = if args.len() >= 4 {
413 let block_hash = args
414 .first()
415 .and_then(|v| v.first())
416 .copied()
417 .unwrap_or_else(Fr::zero);
418 let contract = args
419 .get(1)
420 .and_then(|v| v.first())
421 .ok_or_else(|| Error::InvalidData("missing contract address".into()))?;
422 let start_slot = args
423 .get(2)
424 .and_then(|v| v.first())
425 .ok_or_else(|| Error::InvalidData("missing storage slot".into()))?;
426 let count = args
427 .get(3)
428 .and_then(|v| v.first())
429 .copied()
430 .unwrap_or_else(Fr::zero)
431 .to_usize();
432 (Some(block_hash), contract, start_slot, count.max(1))
433 } else {
434 let contract = args
435 .first()
436 .and_then(|v| v.first())
437 .ok_or_else(|| Error::InvalidData("missing contract address".into()))?;
438 let slot = args
439 .get(1)
440 .and_then(|v| v.first())
441 .ok_or_else(|| Error::InvalidData("missing storage slot".into()))?;
442 (None, contract, slot, 1)
443 };
444
445 let contract_addr = AztecAddress(*contract);
446 let mut values = Vec::with_capacity(number_of_elements);
447 for offset in 0..number_of_elements {
448 let slot = slot_with_offset(*start_slot, offset);
449 let value = match block_hash.as_ref() {
450 Some(block_hash) => {
451 self.node
452 .get_public_storage_at_by_hash(block_hash, &contract_addr, &slot)
453 .await?
454 }
455 None => {
456 self.node
457 .get_public_storage_at(0, &contract_addr, &slot)
458 .await?
459 }
460 };
461 values.push(value);
462 }
463
464 Ok(vec![values])
465 }
466
467 async fn get_contract_instance(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
468 let address = args
469 .first()
470 .and_then(|v| v.first())
471 .ok_or_else(|| Error::InvalidData("missing address".into()))?;
472 let addr = AztecAddress(*address);
473
474 let inst = self.contract_store.get_instance(&addr).await?;
475 let inst = match inst {
476 Some(i) => Some(i),
477 None => self.node.get_contract(&addr).await?,
478 };
479
480 match inst {
481 Some(inst) => Ok(super::oracle::contract_instance_to_fields(&inst.inner)),
482 None => Ok(vec![vec![Fr::zero()]; 16]),
483 }
484 }
485
486 async fn get_notes(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
487 let owner = match (
488 args.first()
489 .and_then(|v| v.first())
490 .copied()
491 .unwrap_or(Fr::zero()),
492 args.get(1)
493 .and_then(|v| v.first())
494 .copied()
495 .unwrap_or(Fr::zero()),
496 ) {
497 (flag, value) if flag != Fr::zero() => Some(AztecAddress(value)),
498 _ => None,
499 };
500 let storage_slot = args
501 .get(2)
502 .and_then(|v| v.first())
503 .copied()
504 .ok_or_else(|| Error::InvalidData("getNotes: missing storage_slot".into()))?;
505 let limit = args
506 .get(13)
507 .and_then(|v| v.first())
508 .copied()
509 .unwrap_or(Fr::zero())
510 .to_usize();
511 let offset = args
512 .get(14)
513 .and_then(|v| v.first())
514 .copied()
515 .unwrap_or(Fr::zero())
516 .to_usize();
517 let status = Self::note_status_from_field(
518 args.get(15)
519 .and_then(|v| v.first())
520 .copied()
521 .unwrap_or(Fr::zero()),
522 )?;
523 let max_notes = args
524 .get(16)
525 .and_then(|v| v.first())
526 .copied()
527 .unwrap_or(Fr::zero())
528 .to_usize();
529 let packed_hinted_note_length = args
530 .get(17)
531 .and_then(|v| v.first())
532 .copied()
533 .unwrap_or(Fr::zero())
534 .to_usize();
535
536 let mut notes = self
537 .note_store
538 .get_notes(&NoteFilter {
539 contract_address: Some(self.contract_address),
540 storage_slot: Some(storage_slot),
541 owner,
542 status,
543 scopes: self.scopes.clone(),
544 ..Default::default()
545 })
546 .await?;
547
548 let selects = super::pick_notes::parse_select_clauses(args);
550 notes = super::pick_notes::select_notes(notes, &selects);
551
552 tracing::trace!(
553 contract = %self.contract_address,
554 ?owner,
555 slot = %storage_slot,
556 scopes = self.scopes.len(),
557 found = notes.len(),
558 "utility_get_notes"
559 );
560 if offset >= notes.len() {
561 notes.clear();
562 } else if offset > 0 {
563 notes = notes.split_off(offset);
564 }
565
566 if limit > 0 && notes.len() > limit {
567 notes.truncate(limit);
568 }
569 if notes.len() > max_notes {
570 notes.truncate(max_notes);
571 }
572
573 let packed = notes
574 .iter()
575 .map(Self::pack_hinted_note)
576 .collect::<Result<Vec<_>, _>>()?;
577
578 Self::pack_bounded_vec_of_arrays(&packed, max_notes, packed_hinted_note_length)
579 }
580
581 async fn store_capsule(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
582 let contract_address =
583 AztecAddress(*args.first().and_then(|v| v.first()).ok_or_else(|| {
584 Error::InvalidData("storeCapsule: missing contract address".into())
585 })?);
586 let slot = *args
587 .get(1)
588 .and_then(|v| v.first())
589 .ok_or_else(|| Error::InvalidData("storeCapsule: missing slot".into()))?;
590 let capsule = args.get(2).cloned().unwrap_or_default();
591
592 self.ensure_contract_db_access(&contract_address)?;
593 self.capsule_store
594 .store_capsule(&contract_address, &slot, &capsule)
595 .await?;
596 Ok(vec![])
597 }
598
599 async fn load_capsule(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
600 let contract_address =
601 AztecAddress(*args.first().and_then(|v| v.first()).ok_or_else(|| {
602 Error::InvalidData("loadCapsule: missing contract address".into())
603 })?);
604 let slot = *args
605 .get(1)
606 .and_then(|v| v.first())
607 .ok_or_else(|| Error::InvalidData("loadCapsule: missing slot".into()))?;
608 let array_len = args
609 .get(2)
610 .and_then(|v| v.first())
611 .copied()
612 .unwrap_or_else(Fr::zero)
613 .to_usize();
614
615 self.ensure_contract_db_access(&contract_address)?;
616 let maybe_values = self
617 .capsule_store
618 .load_capsule(&contract_address, &slot)
619 .await?;
620 let is_some = maybe_values.is_some();
621 let mut values = maybe_values.unwrap_or_default();
622 values.resize(array_len, Fr::zero());
623 Ok(vec![vec![Fr::from(is_some)], values])
624 }
625
626 async fn delete_capsule(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
627 let contract_address =
628 AztecAddress(*args.first().and_then(|v| v.first()).ok_or_else(|| {
629 Error::InvalidData("deleteCapsule: missing contract address".into())
630 })?);
631 let slot = *args
632 .get(1)
633 .and_then(|v| v.first())
634 .ok_or_else(|| Error::InvalidData("deleteCapsule: missing slot".into()))?;
635
636 self.ensure_contract_db_access(&contract_address)?;
637 self.capsule_store
638 .delete_capsule(&contract_address, &slot)
639 .await?;
640 Ok(vec![])
641 }
642
643 async fn copy_capsule(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
644 let contract_address =
645 AztecAddress(*args.first().and_then(|v| v.first()).ok_or_else(|| {
646 Error::InvalidData("copyCapsule: missing contract address".into())
647 })?);
648 let src_slot = *args
649 .get(1)
650 .and_then(|v| v.first())
651 .ok_or_else(|| Error::InvalidData("copyCapsule: missing src slot".into()))?;
652 let dst_slot = *args
653 .get(2)
654 .and_then(|v| v.first())
655 .ok_or_else(|| Error::InvalidData("copyCapsule: missing dst slot".into()))?;
656 let num_entries = args
657 .get(3)
658 .and_then(|v| v.first())
659 .copied()
660 .unwrap_or_else(Fr::zero)
661 .to_usize();
662
663 self.ensure_contract_db_access(&contract_address)?;
664 self.capsule_store
665 .copy_capsule(&contract_address, &src_slot, &dst_slot, num_entries)
666 .await?;
667 Ok(vec![])
668 }
669
670 async fn fetch_tagged_logs(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
671 let pending_tagged_log_array_base_slot =
672 *args.first().and_then(|v| v.first()).ok_or_else(|| {
673 Error::InvalidData("fetchTaggedLogs: missing capsule array base slot".into())
674 })?;
675
676 if self.scopes.is_empty() {
677 return Ok(vec![]);
678 }
679
680 let log_service = LogService::new(
681 self.node,
682 self.sender_store,
683 self.sender_tagging_store,
684 self.recipient_tagging_store,
685 self.capsule_store,
686 );
687
688 for scope in &self.scopes {
689 let secrets = self.tagging_secrets_for_recipient(scope).await?;
690 if secrets.is_empty() {
691 continue;
692 }
693 let logs = log_service
694 .fetch_tagged_logs(&self.contract_address, scope, &secrets)
695 .await?;
696 if logs.is_empty() {
697 continue;
698 }
699 let serialized = logs
700 .into_iter()
701 .map(|log| serialize_pending_tagged_log(&log, scope))
702 .collect::<Result<Vec<_>, _>>()?;
703 self.capsule_store
704 .append_to_capsule_array(
705 &self.contract_address,
706 &pending_tagged_log_array_base_slot,
707 &serialized,
708 )
709 .await?;
710 }
711
712 Ok(vec![])
713 }
714
715 async fn bulk_retrieve_logs(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
716 let contract_address =
717 AztecAddress(*args.first().and_then(|v| v.first()).ok_or_else(|| {
718 Error::InvalidData("bulkRetrieveLogs: missing contract address".into())
719 })?);
720 let requests_slot = *args
721 .get(1)
722 .and_then(|v| v.first())
723 .ok_or_else(|| Error::InvalidData("bulkRetrieveLogs: missing requests slot".into()))?;
724 let responses_slot = *args
725 .get(2)
726 .and_then(|v| v.first())
727 .ok_or_else(|| Error::InvalidData("bulkRetrieveLogs: missing responses slot".into()))?;
728
729 self.ensure_contract_db_access(&contract_address)?;
730
731 let requests = self
732 .capsule_store
733 .read_capsule_array(&contract_address, &requests_slot)
734 .await?
735 .into_iter()
736 .map(parse_log_retrieval_request)
737 .collect::<Result<Vec<_>, _>>()?;
738
739 let log_service = LogService::new(
740 self.node,
741 self.sender_store,
742 self.sender_tagging_store,
743 self.recipient_tagging_store,
744 self.capsule_store,
745 );
746 let maybe_responses = log_service.bulk_retrieve_logs(&requests).await?;
747
748 self.capsule_store
749 .set_capsule_array(&contract_address, &requests_slot, &[])
750 .await?;
751
752 let serialized = maybe_responses
753 .into_iter()
754 .map(|logs| serialize_log_retrieval_option(logs.first()))
755 .collect::<Result<Vec<_>, _>>()?;
756 self.capsule_store
757 .set_capsule_array(&contract_address, &responses_slot, &serialized)
758 .await?;
759
760 Ok(vec![])
761 }
762
763 async fn validate_and_store_enqueued_notes_and_events(
764 &self,
765 args: &[Vec<Fr>],
766 ) -> Result<Vec<Vec<Fr>>, Error> {
767 let contract_address =
768 AztecAddress(*args.first().and_then(|v| v.first()).ok_or_else(|| {
769 Error::InvalidData(
770 "validateAndStoreEnqueuedNotesAndEvents: missing contract address".into(),
771 )
772 })?);
773 let note_requests_slot = *args.get(1).and_then(|v| v.first()).ok_or_else(|| {
774 Error::InvalidData(
775 "validateAndStoreEnqueuedNotesAndEvents: missing note requests slot".into(),
776 )
777 })?;
778 let event_requests_slot = *args.get(2).and_then(|v| v.first()).ok_or_else(|| {
779 Error::InvalidData(
780 "validateAndStoreEnqueuedNotesAndEvents: missing event requests slot".into(),
781 )
782 })?;
783
784 self.ensure_contract_db_access(&contract_address)?;
785
786 let note_requests = self
787 .capsule_store
788 .read_capsule_array(&contract_address, ¬e_requests_slot)
789 .await?;
790 let note_service = NoteService::new(self.node, self.note_store);
791 for fields in note_requests {
792 let request = parse_note_validation_request(&fields)?;
793 note_service
794 .validate_and_store_note(
795 &crate::stores::note_store::StoredNote {
796 contract_address: request.contract_address,
797 owner: request.owner,
798 storage_slot: request.storage_slot,
799 randomness: request.randomness,
800 note_nonce: request.note_nonce,
801 note_hash: request.note_hash,
802 siloed_nullifier: aztec_core::hash::silo_nullifier(
803 &request.contract_address,
804 &request.nullifier,
805 ),
806 note_data: request.content,
807 nullified: false,
808 is_pending: false,
809 nullification_block_number: None,
810 leaf_index: None,
811 block_number: None,
812 tx_index_in_block: None,
813 note_index_in_tx: None,
814 scopes: vec![request.recipient],
815 },
816 &request.recipient,
817 )
818 .await?;
819 }
820
821 let event_requests = self
822 .capsule_store
823 .read_capsule_array(&contract_address, &event_requests_slot)
824 .await?;
825 let event_service =
826 EventService::new(self.node, self.private_event_store, self.anchor_block_store);
827 for fields in event_requests {
828 let request = parse_event_validation_request(&fields)?;
829 event_service
830 .validate_and_store_event(
831 &request.contract_address,
832 &request.event_type_id,
833 request.randomness,
834 request.serialized_event,
835 request.event_commitment,
836 request.tx_hash,
837 &request.recipient,
838 )
839 .await?;
840 }
841
842 self.capsule_store
843 .set_capsule_array(&contract_address, ¬e_requests_slot, &[])
844 .await?;
845 self.capsule_store
846 .set_capsule_array(&contract_address, &event_requests_slot, &[])
847 .await?;
848
849 Ok(vec![])
850 }
851
852 async fn check_nullifier_exists(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
853 let inner_nullifier = args
854 .first()
855 .and_then(|v| v.first())
856 .ok_or_else(|| Error::InvalidData("missing nullifier".into()))?;
857 let siloed = aztec_core::hash::silo_nullifier(&self.contract_address, inner_nullifier);
859 let witness = self
860 .node
861 .get_nullifier_membership_witness(0, &siloed)
862 .await?;
863 Ok(vec![vec![Fr::from(witness.is_some())]])
864 }
865
866 async fn get_nullifier_membership_witness(
873 &self,
874 args: &[Vec<Fr>],
875 ) -> Result<Vec<Vec<Fr>>, Error> {
876 let _block_number = args
877 .first()
878 .and_then(|v| v.first())
879 .copied()
880 .unwrap_or(Fr::zero());
881 let nullifier = args.get(1).and_then(|v| v.first()).ok_or_else(|| {
882 Error::InvalidData("getNullifierMembershipWitness: missing nullifier".into())
883 })?;
884
885 let witness_json = self
886 .node
887 .get_nullifier_membership_witness(0, nullifier)
888 .await?;
889
890 if let Some(json) = witness_json {
891 let index = Self::parse_field_or_number(json.get("index"));
892
893 let preimage = json.get("leafPreimage").unwrap_or(&json);
894 let leaf = preimage.get("leaf").unwrap_or(preimage);
895 let nullifier_val = leaf
896 .get("nullifier")
897 .and_then(|v| v.as_str())
898 .and_then(|s| Fr::from_hex(s).ok())
899 .unwrap_or(Fr::zero());
900 let next_nullifier = preimage
901 .get("nextKey")
902 .or_else(|| preimage.get("nextNullifier"))
903 .and_then(|v| v.as_str())
904 .and_then(|s| Fr::from_hex(s).ok())
905 .unwrap_or(Fr::zero());
906 let next_index = Self::parse_field_or_number(preimage.get("nextIndex"));
907
908 let path = json
909 .get("siblingPath")
910 .and_then(|v| v.as_str())
911 .and_then(|s| decode_base64_sibling_path(s).ok())
912 .unwrap_or_else(|| vec![Fr::zero(); 42]);
913
914 let mut path = path;
915 path.resize(42, Fr::zero());
916
917 Ok(vec![
918 vec![index],
919 vec![nullifier_val],
920 vec![next_nullifier],
921 vec![next_index],
922 path,
923 ])
924 } else {
925 Ok(vec![
926 vec![Fr::zero()],
927 vec![Fr::zero()],
928 vec![Fr::zero()],
929 vec![Fr::zero()],
930 vec![Fr::zero(); 42],
931 ])
932 }
933 }
934
935 async fn get_public_data_witness(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
936 fn fr_at(value: &serde_json::Value, path: &str) -> Result<Fr, Error> {
937 let raw = value.pointer(path).ok_or_else(|| {
938 Error::InvalidData(format!("public data witness missing field at {path}"))
939 })?;
940 if let Some(s) = raw.as_str() {
941 return parse_field_string(s).map_err(|_| {
942 Error::InvalidData(format!(
943 "public data witness field at {path} has unsupported string value: {s}"
944 ))
945 });
946 }
947 if let Some(n) = raw.as_u64() {
948 return Ok(Fr::from(n));
949 }
950 Err(Error::InvalidData(format!(
951 "public data witness field at {path} has unsupported shape: {raw:?}"
952 )))
953 }
954
955 let block_hash = args
956 .first()
957 .and_then(|v| v.first())
958 .copied()
959 .unwrap_or_else(Fr::zero);
960 let leaf_slot = args
961 .get(1)
962 .and_then(|v| v.first())
963 .ok_or_else(|| Error::InvalidData("missing leaf slot".into()))?;
964 let witness = self
965 .node
966 .get_public_data_witness_by_hash(&block_hash, leaf_slot)
967 .await?;
968 let Some(witness) = witness else {
969 return Ok(vec![
970 vec![Fr::zero()],
971 vec![Fr::zero()],
972 vec![Fr::zero()],
973 vec![Fr::zero()],
974 vec![Fr::zero()],
975 vec![Fr::zero(); aztec_core::constants::PUBLIC_DATA_TREE_HEIGHT],
976 ]);
977 };
978
979 let sibling_path = match witness.pointer("/siblingPath") {
980 Some(serde_json::Value::Array(entries)) => entries
981 .iter()
982 .map(|entry| {
983 if let Some(s) = entry.as_str() {
984 parse_field_string(s).map_err(|_| {
985 Error::InvalidData(format!(
986 "public data witness siblingPath entry has unsupported string value: {s}"
987 ))
988 })
989 } else if let Some(n) = entry.as_u64() {
990 Ok(Fr::from(n))
991 } else {
992 Err(Error::InvalidData(format!(
993 "public data witness siblingPath entry has unsupported shape: {entry:?}"
994 )))
995 }
996 })
997 .collect::<Result<Vec<_>, _>>()?,
998 Some(serde_json::Value::String(encoded)) => {
999 decode_base64_sibling_path(encoded)?
1000 }
1001 _ => {
1002 return Err(Error::InvalidData(
1003 "public data witness missing siblingPath".into(),
1004 ))
1005 }
1006 };
1007
1008 let mut sibling_path = sibling_path;
1009 sibling_path.resize(aztec_core::constants::PUBLIC_DATA_TREE_HEIGHT, Fr::zero());
1010 sibling_path.truncate(aztec_core::constants::PUBLIC_DATA_TREE_HEIGHT);
1011
1012 Ok(vec![
1013 vec![fr_at(&witness, "/index")?],
1014 vec![fr_at(&witness, "/leafPreimage/leaf/slot")?],
1015 vec![fr_at(&witness, "/leafPreimage/leaf/value")?],
1016 vec![fr_at(&witness, "/leafPreimage/nextKey")?],
1017 vec![fr_at(&witness, "/leafPreimage/nextIndex")?],
1018 sibling_path,
1019 ])
1020 }
1021
1022 async fn get_public_keys_and_partial_address(
1024 &self,
1025 args: &[Vec<Fr>],
1026 ) -> Result<Vec<Vec<Fr>>, Error> {
1027 let address = AztecAddress(
1028 *args
1029 .first()
1030 .and_then(|v| v.first())
1031 .ok_or_else(|| Error::InvalidData("missing address arg".into()))?,
1032 );
1033
1034 let Some(complete) = self.address_store.get(&address).await? else {
1035 return Ok(vec![vec![Fr::zero()], vec![Fr::zero(); 13]]);
1036 };
1037
1038 let pk = &complete.public_keys;
1039 let mut fields = Vec::with_capacity(13);
1040 for point in [
1041 &pk.master_nullifier_public_key,
1042 &pk.master_incoming_viewing_public_key,
1043 &pk.master_outgoing_viewing_public_key,
1044 &pk.master_tagging_public_key,
1045 ] {
1046 fields.push(point.x);
1047 fields.push(point.y);
1048 fields.push(Fr::from(point.is_infinite));
1049 }
1050 fields.push(complete.partial_address);
1051 Ok(vec![vec![Fr::from(true)], fields])
1052 }
1053
1054 fn parse_field_or_number(val: Option<&serde_json::Value>) -> Fr {
1064 val.and_then(|v| {
1065 if let Some(s) = v.as_str() {
1066 parse_field_string(s).ok()
1067 } else {
1068 v.as_u64().map(Fr::from)
1069 }
1070 })
1071 .unwrap_or(Fr::zero())
1072 }
1073
1074 async fn get_low_nullifier_membership_witness(
1075 &self,
1076 args: &[Vec<Fr>],
1077 ) -> Result<Vec<Vec<Fr>>, Error> {
1078 let _block_hash = args
1079 .first()
1080 .and_then(|v| v.first())
1081 .copied()
1082 .unwrap_or(Fr::zero());
1083 let nullifier = args.get(1).and_then(|v| v.first()).ok_or_else(|| {
1084 Error::InvalidData("getLowNullifierMembershipWitness: missing nullifier".into())
1085 })?;
1086
1087 let witness_json = self
1088 .node
1089 .get_low_nullifier_membership_witness(0, nullifier)
1090 .await?;
1091
1092 if let Some(json) = witness_json {
1099 let index = Self::parse_field_or_number(json.get("index"));
1100
1101 let preimage = json.get("leafPreimage").unwrap_or(&json);
1102 let leaf = preimage.get("leaf").unwrap_or(preimage);
1103 let nullifier_val = leaf
1104 .get("nullifier")
1105 .and_then(|v| v.as_str())
1106 .and_then(|s| Fr::from_hex(s).ok())
1107 .unwrap_or(Fr::zero());
1108 let next_nullifier = preimage
1109 .get("nextKey")
1110 .or_else(|| preimage.get("nextNullifier"))
1111 .and_then(|v| v.as_str())
1112 .and_then(|s| Fr::from_hex(s).ok())
1113 .unwrap_or(Fr::zero());
1114 let next_index = Self::parse_field_or_number(preimage.get("nextIndex"));
1115
1116 let path = json
1118 .get("siblingPath")
1119 .and_then(|v| v.as_str())
1120 .and_then(|s| decode_base64_sibling_path(s).ok())
1121 .unwrap_or_else(|| vec![Fr::zero(); 42]);
1122
1123 let mut path = path;
1124 path.resize(42, Fr::zero());
1125
1126 Ok(vec![
1127 vec![index],
1128 vec![nullifier_val],
1129 vec![next_nullifier],
1130 vec![next_index],
1131 path,
1132 ])
1133 } else {
1134 Ok(vec![
1136 vec![Fr::zero()],
1137 vec![Fr::zero()],
1138 vec![Fr::zero()],
1139 vec![Fr::zero()],
1140 vec![Fr::zero(); 42],
1141 ])
1142 }
1143 }
1144
1145 async fn get_key_validation_request(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
1150 use aztec_core::hash::poseidon2_hash;
1151
1152 let pk_m_hash = *args
1153 .first()
1154 .and_then(|v| v.first())
1155 .ok_or_else(|| Error::InvalidData("missing pk_m_hash".into()))?;
1156
1157 let mut key_in_scope = false;
1159 for scope in &self.scopes {
1160 if let Some(complete) = self.address_store.get(scope).await? {
1161 let pk = &complete.public_keys;
1162 for point in [
1163 &pk.master_nullifier_public_key,
1164 &pk.master_incoming_viewing_public_key,
1165 &pk.master_outgoing_viewing_public_key,
1166 &pk.master_tagging_public_key,
1167 ] {
1168 let hash = poseidon2_hash(&[point.x, point.y, Fr::from(point.is_infinite)]);
1169 if hash == pk_m_hash {
1170 key_in_scope = true;
1171 break;
1172 }
1173 }
1174 if key_in_scope {
1175 break;
1176 }
1177 }
1178 }
1179 if !key_in_scope {
1180 return Err(Error::InvalidData("Key validation request denied".into()));
1181 }
1182
1183 match self
1184 .key_store
1185 .get_key_validation_request(&pk_m_hash, &self.contract_address)
1186 .await?
1187 {
1188 Some((pk_m, sk_app)) => Ok(vec![
1189 vec![pk_m.x],
1190 vec![pk_m.y],
1191 vec![Fr::from(pk_m.is_infinite)],
1192 vec![sk_app],
1193 ]),
1194 None => Ok(vec![
1195 vec![Fr::zero()],
1196 vec![Fr::zero()],
1197 vec![Fr::zero()],
1198 vec![Fr::zero()],
1199 ]),
1200 }
1201 }
1202
1203 async fn get_shared_secret(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
1205 use aztec_core::grumpkin;
1206
1207 let recipient = AztecAddress(
1208 *args
1209 .first()
1210 .and_then(|v| v.first())
1211 .ok_or_else(|| Error::InvalidData("getSharedSecret: missing recipient".into()))?,
1212 );
1213 let eph_pk_x = *args
1214 .get(1)
1215 .and_then(|v| v.first())
1216 .ok_or_else(|| Error::InvalidData("getSharedSecret: missing eph_pk.x".into()))?;
1217 let eph_pk_y = *args
1218 .get(2)
1219 .and_then(|v| v.first())
1220 .ok_or_else(|| Error::InvalidData("getSharedSecret: missing eph_pk.y".into()))?;
1221 let eph_pk_is_infinite = args
1222 .get(3)
1223 .and_then(|v| v.first())
1224 .map(|f| f.to_usize() != 0)
1225 .unwrap_or(false);
1226
1227 if eph_pk_is_infinite {
1228 return Ok(vec![
1229 vec![Fr::zero()],
1230 vec![Fr::zero()],
1231 vec![Fr::from(true)],
1232 ]);
1233 }
1234
1235 let Some(complete) = self.address_store.get(&recipient).await? else {
1236 return Err(Error::InvalidData(format!(
1237 "getSharedSecret: recipient {recipient} not in address store"
1238 )));
1239 };
1240 let pk_hash = complete.public_keys.hash();
1241 let Some(ivsk) = self
1242 .key_store
1243 .get_master_incoming_viewing_secret_key(&pk_hash)
1244 .await?
1245 else {
1246 return Err(Error::InvalidData(format!(
1247 "getSharedSecret: ivsk not found for {recipient}"
1248 )));
1249 };
1250
1251 let preaddress = aztec_core::hash::poseidon2_hash_with_separator(
1252 &[pk_hash, complete.partial_address],
1253 aztec_core::constants::domain_separator::CONTRACT_ADDRESS_V1,
1254 );
1255 let address_secret = compute_address_secret(preaddress, ivsk);
1256
1257 let eph_pk = aztec_core::types::Point {
1258 x: eph_pk_x,
1259 y: eph_pk_y,
1260 is_infinite: false,
1261 };
1262 let shared = grumpkin::scalar_mul(&address_secret, &eph_pk);
1263
1264 Ok(vec![
1265 vec![shared.x],
1266 vec![shared.y],
1267 vec![Fr::from(shared.is_infinite)],
1268 ])
1269 }
1270
1271 fn aes128_decrypt(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
1273 use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
1274 type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
1275
1276 let ct_storage = args.first().cloned().unwrap_or_default();
1277 let max_len = ct_storage.len();
1278 let ct_len = args
1279 .get(1)
1280 .and_then(|v| v.first())
1281 .map(|f| f.to_usize())
1282 .unwrap_or(0);
1283 let iv_fields = args.get(2).cloned().unwrap_or_default();
1284 let key_fields = args.get(3).cloned().unwrap_or_default();
1285
1286 let ciphertext: Vec<u8> = ct_storage
1287 .iter()
1288 .take(ct_len)
1289 .map(|f| f.to_usize() as u8)
1290 .collect();
1291 let iv: [u8; 16] = iv_fields
1292 .iter()
1293 .take(16)
1294 .map(|f| f.to_usize() as u8)
1295 .collect::<Vec<_>>()
1296 .try_into()
1297 .unwrap_or([0u8; 16]);
1298 let key: [u8; 16] = key_fields
1299 .iter()
1300 .take(16)
1301 .map(|f| f.to_usize() as u8)
1302 .collect::<Vec<_>>()
1303 .try_into()
1304 .unwrap_or([0u8; 16]);
1305
1306 let plaintext = match Aes128CbcDec::new(&key.into(), &iv.into())
1307 .decrypt_padded_vec_mut::<Pkcs7>(&ciphertext)
1308 {
1309 Ok(pt) => pt,
1310 Err(e) => {
1311 return Err(Error::InvalidData(format!("aes128 decrypt error: {e}")));
1312 }
1313 };
1314
1315 let pt_len = plaintext.len();
1316 let mut storage: Vec<Fr> = plaintext.iter().map(|&b| Fr::from(u64::from(b))).collect();
1317 storage.resize(max_len, Fr::zero());
1318 Ok(vec![storage, vec![Fr::from(pt_len as u64)]])
1319 }
1320
1321 fn get_auth_witness(&self, args: &[Vec<Fr>]) -> Result<Vec<Vec<Fr>>, Error> {
1322 let message_hash = args
1323 .first()
1324 .and_then(|v| v.first())
1325 .ok_or_else(|| Error::InvalidData("missing message hash".into()))?;
1326 for (hash, witness) in &self.auth_witnesses {
1327 if hash == message_hash {
1328 return Ok(vec![witness.clone()]);
1329 }
1330 }
1331 Err(Error::InvalidData(format!(
1332 "Unknown auth witness for message hash {message_hash}"
1333 )))
1334 }
1335
1336 fn ensure_contract_db_access(&self, contract_address: &AztecAddress) -> Result<(), Error> {
1337 if *contract_address != self.contract_address {
1338 return Err(Error::InvalidData(format!(
1339 "contract {} is not allowed to access {}'s PXE DB",
1340 contract_address, self.contract_address
1341 )));
1342 }
1343 Ok(())
1344 }
1345
1346 async fn tagging_secrets_for_recipient(
1347 &self,
1348 recipient: &AztecAddress,
1349 ) -> Result<Vec<Fr>, Error> {
1350 let Some(complete_address) = self.address_store.get(recipient).await? else {
1351 return Ok(vec![]);
1352 };
1353
1354 let pk_hash = complete_address.public_keys.hash();
1355 let Some(ivsk) = self
1356 .key_store
1357 .get_master_incoming_viewing_secret_key(&pk_hash)
1358 .await?
1359 else {
1360 return Ok(vec![]);
1361 };
1362
1363 let mut senders = self.sender_store.get_all().await?;
1364 for addr in self.address_store.get_all().await? {
1368 if !senders.contains(&addr.address) {
1369 senders.push(addr.address);
1370 }
1371 }
1372 if !senders.contains(recipient) {
1373 senders.push(*recipient);
1374 }
1375
1376 let mut secrets = Vec::with_capacity(senders.len());
1377 for sender in senders {
1378 secrets.push(compute_directional_tagging_secret(
1379 &complete_address,
1380 ivsk,
1381 &sender,
1382 &self.contract_address,
1383 recipient,
1384 )?);
1385 }
1386 Ok(secrets)
1387 }
1388}
1389
1390const MAX_NOTE_PACKED_LEN: usize = 8;
1391const MAX_EVENT_SERIALIZED_LEN: usize = 10;
1392const MAX_NOTE_HASHES_PER_TX: usize = 64;
1393const PRIVATE_LOG_SIZE_IN_FIELDS: usize = aztec_core::constants::PRIVATE_LOG_SIZE_IN_FIELDS;
1394const PRIVATE_LOG_CIPHERTEXT_LEN: usize = 15;
1395
1396#[derive(Debug)]
1397struct ParsedNoteValidationRequest {
1398 contract_address: AztecAddress,
1399 owner: AztecAddress,
1400 storage_slot: Fr,
1401 randomness: Fr,
1402 note_nonce: Fr,
1403 content: Vec<Fr>,
1404 note_hash: Fr,
1405 nullifier: Fr,
1406 #[allow(dead_code)]
1407 tx_hash: TxHash,
1408 recipient: AztecAddress,
1409}
1410
1411#[derive(Debug)]
1412struct ParsedEventValidationRequest {
1413 contract_address: AztecAddress,
1414 event_type_id: aztec_core::abi::EventSelector,
1415 randomness: Fr,
1416 serialized_event: Vec<Fr>,
1417 event_commitment: Fr,
1418 tx_hash: TxHash,
1419 recipient: AztecAddress,
1420}
1421
1422fn parse_log_retrieval_request(fields: Vec<Fr>) -> Result<LogRetrievalRequest, Error> {
1423 if fields.len() < 2 {
1424 return Err(Error::InvalidData("log retrieval request too short".into()));
1425 }
1426 Ok(LogRetrievalRequest {
1427 is_public: true,
1428 contract_address: Some(AztecAddress(fields[0])),
1429 tag: fields[1],
1430 })
1431}
1432
1433fn serialize_bounded_vec(values: &[Fr], max_length: usize) -> Result<Vec<Fr>, Error> {
1434 if values.len() > max_length {
1435 return Err(Error::InvalidData(format!(
1436 "bounded vec overflow: {} > {}",
1437 values.len(),
1438 max_length
1439 )));
1440 }
1441 let mut storage = values.to_vec();
1442 storage.resize(max_length, Fr::zero());
1443 storage.push(Fr::from(values.len() as u64));
1444 Ok(storage)
1445}
1446
1447fn serialize_log_retrieval_option(
1448 log: Option<&crate::sync::log_service::TaggedLog>,
1449) -> Result<Vec<Fr>, Error> {
1450 let mut out = Vec::new();
1451 match log {
1452 Some(log) => {
1453 out.push(Fr::from(true));
1454 let payload = if log.data.is_empty() {
1455 &[][..]
1456 } else {
1457 &log.data[1..]
1458 };
1459 out.extend(serialize_bounded_vec(
1460 payload,
1461 MAX_NOTE_PACKED_LEN.max(PRIVATE_LOG_CIPHERTEXT_LEN),
1462 )?);
1463 out.push(tx_hash_to_field(&log.tx_hash)?);
1464 out.extend(serialize_bounded_vec(
1465 &log.note_hashes,
1466 MAX_NOTE_HASHES_PER_TX,
1467 )?);
1468 out.push(log.first_nullifier);
1469 }
1470 None => {
1471 out.push(Fr::zero());
1472 out.extend(vec![
1473 Fr::zero();
1474 MAX_NOTE_PACKED_LEN.max(PRIVATE_LOG_CIPHERTEXT_LEN) + 1
1475 ]);
1476 out.push(Fr::zero());
1477 out.extend(vec![Fr::zero(); MAX_NOTE_HASHES_PER_TX + 1]);
1478 out.push(Fr::zero());
1479 }
1480 }
1481 Ok(out)
1482}
1483
1484fn serialize_pending_tagged_log(
1485 log: &crate::sync::log_service::TaggedLog,
1486 recipient: &AztecAddress,
1487) -> Result<Vec<Fr>, Error> {
1488 let mut out = serialize_bounded_vec(&log.data, PRIVATE_LOG_SIZE_IN_FIELDS)?;
1489 out.push(tx_hash_to_field(&log.tx_hash)?);
1490 out.extend(serialize_bounded_vec(
1491 &log.note_hashes,
1492 MAX_NOTE_HASHES_PER_TX,
1493 )?);
1494 out.push(log.first_nullifier);
1495 out.push(recipient.0);
1496 Ok(out)
1497}
1498
1499fn parse_note_validation_request(fields: &[Fr]) -> Result<ParsedNoteValidationRequest, Error> {
1500 if fields.len() < 5 + MAX_NOTE_PACKED_LEN + 5 {
1501 return Err(Error::InvalidData(
1502 "note validation request too short".into(),
1503 ));
1504 }
1505 let contract_address = AztecAddress(fields[0]);
1506 let owner = AztecAddress(fields[1]);
1507 let storage_slot = fields[2];
1508 let randomness = fields[3];
1509 let note_nonce = fields[4];
1510 let content_len = fields[5 + MAX_NOTE_PACKED_LEN]
1511 .to_usize()
1512 .min(MAX_NOTE_PACKED_LEN);
1513 let content = fields[5..5 + MAX_NOTE_PACKED_LEN][..content_len].to_vec();
1514 let note_hash = fields[5 + MAX_NOTE_PACKED_LEN + 1];
1515 let nullifier = fields[5 + MAX_NOTE_PACKED_LEN + 2];
1516 let tx_hash = tx_hash_from_field(fields[5 + MAX_NOTE_PACKED_LEN + 3]);
1517 let recipient = AztecAddress(fields[5 + MAX_NOTE_PACKED_LEN + 4]);
1518 Ok(ParsedNoteValidationRequest {
1519 contract_address,
1520 owner,
1521 storage_slot,
1522 randomness,
1523 note_nonce,
1524 content,
1525 note_hash,
1526 nullifier,
1527 tx_hash,
1528 recipient,
1529 })
1530}
1531
1532fn parse_event_validation_request(fields: &[Fr]) -> Result<ParsedEventValidationRequest, Error> {
1533 if fields.len() < 3 + MAX_EVENT_SERIALIZED_LEN + 4 {
1534 return Err(Error::InvalidData(
1535 "event validation request too short".into(),
1536 ));
1537 }
1538 let contract_address = AztecAddress(fields[0]);
1539 let event_type_id = aztec_core::abi::EventSelector(fields[1]);
1540 let randomness = fields[2];
1541 let event_len = fields[3 + MAX_EVENT_SERIALIZED_LEN]
1542 .to_usize()
1543 .min(MAX_EVENT_SERIALIZED_LEN);
1544 let serialized_event = fields[3..3 + MAX_EVENT_SERIALIZED_LEN][..event_len].to_vec();
1545 let event_commitment = fields[3 + MAX_EVENT_SERIALIZED_LEN + 1];
1546 let tx_hash = tx_hash_from_field(fields[3 + MAX_EVENT_SERIALIZED_LEN + 2]);
1547 let recipient = AztecAddress(fields[3 + MAX_EVENT_SERIALIZED_LEN + 3]);
1548 Ok(ParsedEventValidationRequest {
1549 contract_address,
1550 event_type_id,
1551 randomness,
1552 serialized_event,
1553 event_commitment,
1554 tx_hash,
1555 recipient,
1556 })
1557}
1558
1559fn tx_hash_from_field(field: Fr) -> TxHash {
1560 TxHash(field.to_be_bytes())
1561}
1562
1563fn tx_hash_to_field(tx_hash: &TxHash) -> Result<Fr, Error> {
1564 Fr::from_hex(&tx_hash.to_string())
1565}
1566
1567pub(crate) fn compute_directional_tagging_secret(
1568 local_address: &aztec_core::types::CompleteAddress,
1569 local_ivsk: Fq,
1570 external_address: &AztecAddress,
1571 app: &AztecAddress,
1572 recipient: &AztecAddress,
1573) -> Result<Fr, Error> {
1574 let public_keys_hash = local_address.public_keys.hash();
1575 let preaddress = aztec_core::hash::poseidon2_hash_with_separator(
1576 &[public_keys_hash, local_address.partial_address],
1577 domain_separator::CONTRACT_ADDRESS_V1,
1578 );
1579 let address_secret = compute_address_secret(preaddress, local_ivsk);
1580 let external_point = grumpkin::point_from_x(external_address.0)?;
1581 let shared_secret = grumpkin::scalar_mul(&address_secret, &external_point);
1582 let app_tagging_secret = poseidon2_hash(&[shared_secret.x, shared_secret.y, app.0]);
1583 Ok(poseidon2_hash(&[app_tagging_secret, recipient.0]))
1584}
1585
1586fn compute_address_secret(preaddress: Fr, ivsk: Fq) -> Fq {
1587 let candidate = Fq(ivsk.0 + Fq::from_be_bytes_mod_order(&preaddress.to_be_bytes()).0);
1588 let address_point_candidate = grumpkin::scalar_mul(&candidate, &grumpkin::generator());
1589 if grumpkin::has_positive_y(&address_point_candidate) {
1590 candidate
1591 } else {
1592 Fq(-candidate.0)
1593 }
1594}
1595
1596#[async_trait::async_trait]
1597impl<'a, N: AztecNode + Send + Sync + 'static> OracleCallback for UtilityExecutionOracle<'a, N> {
1598 async fn handle_foreign_call(
1599 &mut self,
1600 function: &str,
1601 inputs: Vec<Vec<Fr>>,
1602 ) -> Result<Vec<Vec<Fr>>, Error> {
1603 UtilityExecutionOracle::handle_foreign_call(self, function, inputs).await
1604 }
1605}