1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3use core::fmt::Debug;
4
5use miden_core::utils::{Deserializable, Serializable};
6use miden_crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof};
7use miden_crypto::merkle::{MerkleError, NodeIndex};
8
9use super::PartialBlockchain;
10use crate::account::{
11 AccountCode,
12 AccountHeader,
13 AccountId,
14 AccountStorageHeader,
15 PartialAccount,
16 PartialStorage,
17 StorageMapKey,
18 StorageMapWitness,
19 StorageSlotId,
20 StorageSlotName,
21};
22use crate::asset::{Asset, AssetVaultKey, AssetWitness, PartialVault};
23use crate::block::account_tree::{AccountWitness, account_id_to_smt_index};
24use crate::block::{BlockHeader, BlockNumber};
25use crate::crypto::merkle::SparseMerklePath;
26use crate::errors::{TransactionInputError, TransactionInputsExtractionError};
27use crate::note::{Note, NoteInclusionProof};
28use crate::transaction::{TransactionAdviceInputs, TransactionArgs, TransactionScript};
29use crate::{Felt, Word};
30
31#[cfg(test)]
32mod tests;
33
34mod account;
35pub use account::AccountInputs;
36
37mod notes;
38use miden_processor::{AdviceInputs, SMT_DEPTH};
39pub use notes::{InputNote, InputNotes, ToInputNoteCommitments};
40
41#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct TransactionInputs {
47 account: PartialAccount,
48 block_header: BlockHeader,
49 blockchain: PartialBlockchain,
50 input_notes: InputNotes<InputNote>,
51 tx_args: TransactionArgs,
52 advice_inputs: AdviceInputs,
53 foreign_account_code: Vec<AccountCode>,
54 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
56}
57
58impl TransactionInputs {
59 pub fn new(
70 account: PartialAccount,
71 block_header: BlockHeader,
72 blockchain: PartialBlockchain,
73 input_notes: InputNotes<InputNote>,
74 ) -> Result<Self, TransactionInputError> {
75 if blockchain.chain_length() != block_header.block_num() {
77 return Err(TransactionInputError::InconsistentChainLength {
78 expected: block_header.block_num(),
79 actual: blockchain.chain_length(),
80 });
81 }
82 if blockchain.peaks().hash_peaks() != block_header.chain_commitment() {
83 return Err(TransactionInputError::InconsistentChainCommitment {
84 expected: block_header.chain_commitment(),
85 actual: blockchain.peaks().hash_peaks(),
86 });
87 }
88 for note in input_notes.iter() {
90 if let InputNote::Authenticated { note, proof } = note {
91 let note_block_num = proof.location().block_num();
92 let block_header = if note_block_num == block_header.block_num() {
93 &block_header
94 } else {
95 blockchain.get_block(note_block_num).ok_or(
96 TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
97 )?
98 };
99 validate_is_in_block(note, proof, block_header)?;
100 }
101 }
102
103 Ok(Self {
104 account,
105 block_header,
106 blockchain,
107 input_notes,
108 tx_args: TransactionArgs::default(),
109 advice_inputs: AdviceInputs::default(),
110 foreign_account_code: Vec::new(),
111 foreign_account_slot_names: BTreeMap::new(),
112 })
113 }
114
115 pub fn with_asset_witnesses(mut self, witnesses: Vec<AssetWitness>) -> Self {
117 for witness in witnesses {
118 self.advice_inputs.store.extend(witness.authenticated_nodes());
119 let smt_proof = SmtProof::from(witness);
120 self.advice_inputs
121 .map
122 .extend([(smt_proof.leaf().hash(), smt_proof.leaf().to_elements())]);
123 }
124
125 self
126 }
127
128 pub fn with_foreign_account_code(mut self, foreign_account_code: Vec<AccountCode>) -> Self {
130 self.foreign_account_code = foreign_account_code;
131 self
132 }
133
134 pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
136 self.set_tx_args_inner(tx_args);
137 self
138 }
139
140 pub fn with_foreign_account_slot_names(
142 mut self,
143 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
144 ) -> Self {
145 self.foreign_account_slot_names = foreign_account_slot_names;
146 self
147 }
148
149 pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
151 self.set_advice_inputs(advice_inputs);
152 self
153 }
154
155 pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
160 self.input_notes = new_notes.into();
161 }
162
163 pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
167 let AdviceInputs { map, store, .. } = new_advice_inputs;
168 self.advice_inputs = AdviceInputs { stack: Default::default(), map, store };
169 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
170 }
171
172 #[cfg(feature = "testing")]
174 pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
175 self.set_tx_args_inner(tx_args);
176 }
177
178 pub fn account(&self) -> &PartialAccount {
183 &self.account
184 }
185
186 pub fn block_header(&self) -> &BlockHeader {
188 &self.block_header
189 }
190
191 pub fn blockchain(&self) -> &PartialBlockchain {
194 &self.blockchain
195 }
196
197 pub fn input_notes(&self) -> &InputNotes<InputNote> {
199 &self.input_notes
200 }
201
202 pub fn ref_block(&self) -> BlockNumber {
204 self.block_header.block_num()
205 }
206
207 pub fn tx_script(&self) -> Option<&TransactionScript> {
209 self.tx_args.tx_script()
210 }
211
212 pub fn foreign_account_code(&self) -> &[AccountCode] {
214 &self.foreign_account_code
215 }
216
217 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
219 &self.foreign_account_slot_names
220 }
221
222 pub fn advice_inputs(&self) -> &AdviceInputs {
224 &self.advice_inputs
225 }
226
227 pub fn tx_args(&self) -> &TransactionArgs {
229 &self.tx_args
230 }
231
232 pub fn read_storage_map_witness(
237 &self,
238 map_root: Word,
239 map_key: StorageMapKey,
240 ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
241 let leaf_index = map_key.hash().to_leaf_index();
243
244 let merkle_path = self.advice_inputs.store.get_path(map_root, leaf_index.into())?;
246 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
247
248 let merkle_node = self.advice_inputs.store.get_node(map_root, leaf_index.into())?;
250 let smt_leaf_elements = self
251 .advice_inputs
252 .map
253 .get(&merkle_node)
254 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
255 let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, leaf_index)?;
256
257 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
259 let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
260
261 Ok(storage_witness)
262 }
263
264 pub fn read_vault_asset_witnesses(
272 &self,
273 vault_root: Word,
274 vault_keys: BTreeSet<AssetVaultKey>,
275 ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
276 let mut asset_witnesses = Vec::new();
277 for vault_key in vault_keys {
278 let smt_index = vault_key.to_leaf_index();
279 let merkle_path = self.advice_inputs.store.get_path(vault_root, smt_index.into())?;
281 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
282
283 let merkle_node = self.advice_inputs.store.get_node(vault_root, smt_index.into())?;
285 let smt_leaf_elements = self
286 .advice_inputs
287 .map
288 .get(&merkle_node)
289 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
290 let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, smt_index)?;
291
292 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
294 let asset_witness = AssetWitness::new(smt_proof)?;
295 asset_witnesses.push(asset_witness);
296 }
297 Ok(asset_witnesses)
298 }
299
300 pub fn has_vault_asset_witness(&self, vault_root: Word, asset_key: &AssetVaultKey) -> bool {
305 let smt_index: NodeIndex = asset_key.to_leaf_index().into();
306
307 if !self.advice_inputs.store.has_path(vault_root, smt_index) {
309 return false;
310 }
311
312 match self.advice_inputs.store.get_node(vault_root, smt_index) {
314 Ok(node) => self.advice_inputs.map.contains_key(&node),
315 Err(_) => false,
316 }
317 }
318
319 pub fn read_vault_asset(
327 &self,
328 vault_root: Word,
329 asset_key: AssetVaultKey,
330 ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
331 let smt_index = asset_key.to_leaf_index();
333 let merkle_node = match self.advice_inputs.store.get_node(vault_root, smt_index.into()) {
334 Ok(node) => node,
335 Err(MerkleError::NodeIndexNotFoundInStore(..)) => return Ok(None),
336 Err(err) => return Err(err.into()),
337 };
338
339 let smt_leaf_elements = self
341 .advice_inputs
342 .map
343 .get(&merkle_node)
344 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
345 let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, smt_index)?;
346
347 let asset = smt_leaf
349 .entries()
350 .iter()
351 .find(|(key, _value)| key == asset_key.as_word())
352 .map(|(_key, value)| Asset::try_from(value))
353 .transpose()?;
354
355 Ok(asset)
356 }
357
358 pub fn read_foreign_account_inputs(
365 &self,
366 account_id: AccountId,
367 ) -> Result<AccountInputs, TransactionInputsExtractionError> {
368 if account_id == self.account().id() {
369 return Err(TransactionInputsExtractionError::AccountNotForeign);
370 }
371
372 let account_id_key = TransactionAdviceInputs::account_id_map_key(account_id);
374 let header_elements = self
375 .advice_inputs
376 .map
377 .get(&account_id_key)
378 .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
379
380 let header = AccountHeader::try_from_elements(header_elements)?;
382
383 let partial_account = self.read_foreign_partial_account(&header)?;
385 let witness = self.read_foreign_account_witness(&header)?;
386 Ok(AccountInputs::new(partial_account, witness))
387 }
388
389 fn read_foreign_partial_account(
392 &self,
393 header: &AccountHeader,
394 ) -> Result<PartialAccount, TransactionInputsExtractionError> {
395 let partial_vault = PartialVault::new(header.vault_root());
397
398 let account_code = self
400 .foreign_account_code
401 .iter()
402 .find(|code| code.commitment() == header.code_commitment())
403 .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
404 .clone();
405
406 let storage_header_elements = self
408 .advice_inputs
409 .map
410 .get(&header.storage_commitment())
411 .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
412
413 let storage_header = AccountStorageHeader::try_from_elements(
415 storage_header_elements,
416 self.foreign_account_slot_names(),
417 )?;
418
419 let partial_storage = PartialStorage::new(storage_header, [])?;
421
422 let partial_account = PartialAccount::new(
424 header.id(),
425 header.nonce(),
426 account_code,
427 partial_storage,
428 partial_vault,
429 None, )?;
431
432 Ok(partial_account)
433 }
434
435 fn read_foreign_account_witness(
438 &self,
439 header: &AccountHeader,
440 ) -> Result<AccountWitness, TransactionInputsExtractionError> {
441 let account_tree_root = self.block_header.account_root();
443 let leaf_index: NodeIndex = account_id_to_smt_index(header.id()).into();
444
445 let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
447
448 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
450
451 let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
453
454 Ok(witness)
455 }
456
457 pub fn into_parts(
462 self,
463 ) -> (
464 PartialAccount,
465 BlockHeader,
466 PartialBlockchain,
467 InputNotes<InputNote>,
468 TransactionArgs,
469 ) {
470 (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
471 }
472
473 fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
481 self.tx_args = tx_args;
482 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
483 }
484}
485
486impl Serializable for TransactionInputs {
490 fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
491 self.account.write_into(target);
492 self.block_header.write_into(target);
493 self.blockchain.write_into(target);
494 self.input_notes.write_into(target);
495 self.tx_args.write_into(target);
496 self.advice_inputs.write_into(target);
497 self.foreign_account_code.write_into(target);
498 self.foreign_account_slot_names.write_into(target);
499 }
500}
501
502impl Deserializable for TransactionInputs {
503 fn read_from<R: miden_core::utils::ByteReader>(
504 source: &mut R,
505 ) -> Result<Self, miden_core::utils::DeserializationError> {
506 let account = PartialAccount::read_from(source)?;
507 let block_header = BlockHeader::read_from(source)?;
508 let blockchain = PartialBlockchain::read_from(source)?;
509 let input_notes = InputNotes::read_from(source)?;
510 let tx_args = TransactionArgs::read_from(source)?;
511 let advice_inputs = AdviceInputs::read_from(source)?;
512 let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
513 let foreign_account_slot_names =
514 BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
515
516 Ok(TransactionInputs {
517 account,
518 block_header,
519 blockchain,
520 input_notes,
521 tx_args,
522 advice_inputs,
523 foreign_account_code,
524 foreign_account_slot_names,
525 })
526 }
527}
528
529pub fn smt_leaf_from_elements(
534 elements: &[Felt],
535 leaf_index: LeafIndex<SMT_DEPTH>,
536) -> Result<SmtLeaf, TransactionInputsExtractionError> {
537 use miden_crypto::merkle::smt::SmtLeaf;
538
539 if elements.is_empty() {
542 return Ok(SmtLeaf::new_empty(leaf_index));
543 }
544
545 if !elements.len().is_multiple_of(8) {
547 return Err(TransactionInputsExtractionError::LeafConversionError(
548 "invalid SMT leaf format: elements length must be divisible by 8".into(),
549 ));
550 }
551
552 let num_entries = elements.len() / 8;
553
554 if num_entries == 1 {
555 let key = Word::new([elements[0], elements[1], elements[2], elements[3]]);
557 let value = Word::new([elements[4], elements[5], elements[6], elements[7]]);
558 Ok(SmtLeaf::new_single(key, value))
559 } else {
560 let mut entries = Vec::with_capacity(num_entries);
562 for i in 0..num_entries {
564 let base_idx = i * 8;
565 let key = Word::new([
566 elements[base_idx],
567 elements[base_idx + 1],
568 elements[base_idx + 2],
569 elements[base_idx + 3],
570 ]);
571 let value = Word::new([
572 elements[base_idx + 4],
573 elements[base_idx + 5],
574 elements[base_idx + 6],
575 elements[base_idx + 7],
576 ]);
577 entries.push((key, value));
578 }
579 let leaf = SmtLeaf::new_multiple(entries)?;
580 Ok(leaf)
581 }
582}
583
584fn validate_is_in_block(
586 note: &Note,
587 proof: &NoteInclusionProof,
588 block_header: &BlockHeader,
589) -> Result<(), TransactionInputError> {
590 let note_index = proof.location().node_index_in_block().into();
591 let note_commitment = note.commitment();
592 proof
593 .note_path()
594 .verify(note_index, note_commitment, &block_header.note_root())
595 .map_err(|_| {
596 TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
597 })
598}