1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use miden_crypto::merkle::smt::{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::{AccountIdKey, AccountWitness};
24use crate::block::{BlockHeader, BlockNumber};
25use crate::crypto::merkle::SparseMerklePath;
26use crate::errors::{TransactionInputError, TransactionInputsExtractionError};
27use crate::note::{Note, NoteInclusionProof};
28use crate::transaction::{TransactionArgs, TransactionScript};
29use crate::utils::serde::{
30 ByteReader,
31 ByteWriter,
32 Deserializable,
33 DeserializationError,
34 Serializable,
35};
36use crate::{Felt, Word};
37
38#[cfg(test)]
39mod tests;
40
41mod account;
42pub use account::AccountInputs;
43
44mod notes;
45pub use notes::{InputNote, InputNotes, ToInputNoteCommitments};
46
47use crate::vm::AdviceInputs;
48
49#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct TransactionInputs {
55 account: PartialAccount,
56 block_header: BlockHeader,
57 blockchain: PartialBlockchain,
58 input_notes: InputNotes<InputNote>,
59 tx_args: TransactionArgs,
60 advice_inputs: AdviceInputs,
61 foreign_account_code: Vec<AccountCode>,
62 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
64}
65
66impl TransactionInputs {
67 pub fn new(
78 account: PartialAccount,
79 block_header: BlockHeader,
80 blockchain: PartialBlockchain,
81 input_notes: InputNotes<InputNote>,
82 ) -> Result<Self, TransactionInputError> {
83 if blockchain.chain_length() != block_header.block_num() {
85 return Err(TransactionInputError::InconsistentChainLength {
86 expected: block_header.block_num(),
87 actual: blockchain.chain_length(),
88 });
89 }
90 if blockchain.peaks().hash_peaks() != block_header.chain_commitment() {
91 return Err(TransactionInputError::InconsistentChainCommitment {
92 expected: block_header.chain_commitment(),
93 actual: blockchain.peaks().hash_peaks(),
94 });
95 }
96 for note in input_notes.iter() {
98 if let InputNote::Authenticated { note, proof } = note {
99 let note_block_num = proof.location().block_num();
100 let block_header = if note_block_num == block_header.block_num() {
101 &block_header
102 } else {
103 blockchain.get_block(note_block_num).ok_or(
104 TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
105 )?
106 };
107 validate_is_in_block(note, proof, block_header)?;
108 }
109 }
110
111 Ok(Self {
112 account,
113 block_header,
114 blockchain,
115 input_notes,
116 tx_args: TransactionArgs::default(),
117 advice_inputs: AdviceInputs::default(),
118 foreign_account_code: Vec::new(),
119 foreign_account_slot_names: BTreeMap::new(),
120 })
121 }
122
123 pub fn with_asset_witnesses(mut self, witnesses: Vec<AssetWitness>) -> Self {
125 for witness in witnesses {
126 self.advice_inputs.store.extend(witness.authenticated_nodes());
127 let smt_proof = SmtProof::from(witness);
128 self.advice_inputs.map.extend([(
129 smt_proof.leaf().hash(),
130 smt_proof.leaf().to_elements().collect::<Arc<[Felt]>>(),
131 )]);
132 }
133
134 self
135 }
136
137 pub fn with_foreign_account_code(mut self, foreign_account_code: Vec<AccountCode>) -> Self {
139 self.foreign_account_code = foreign_account_code;
140 self
141 }
142
143 pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
145 self.set_tx_args_inner(tx_args);
146 self
147 }
148
149 pub fn with_foreign_account_slot_names(
151 mut self,
152 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
153 ) -> Self {
154 self.foreign_account_slot_names = foreign_account_slot_names;
155 self
156 }
157
158 pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
160 self.set_advice_inputs(advice_inputs);
161 self
162 }
163
164 pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
169 self.input_notes = new_notes.into();
170 }
171
172 pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
176 let AdviceInputs { map, store, .. } = new_advice_inputs;
177 self.advice_inputs = AdviceInputs { stack: Default::default(), map, store };
178 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
179 }
180
181 #[cfg(feature = "testing")]
183 pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
184 self.set_tx_args_inner(tx_args);
185 }
186
187 pub fn account(&self) -> &PartialAccount {
192 &self.account
193 }
194
195 pub fn block_header(&self) -> &BlockHeader {
197 &self.block_header
198 }
199
200 pub fn blockchain(&self) -> &PartialBlockchain {
203 &self.blockchain
204 }
205
206 pub fn input_notes(&self) -> &InputNotes<InputNote> {
208 &self.input_notes
209 }
210
211 pub fn ref_block(&self) -> BlockNumber {
213 self.block_header.block_num()
214 }
215
216 pub fn tx_script(&self) -> Option<&TransactionScript> {
218 self.tx_args.tx_script()
219 }
220
221 pub fn foreign_account_code(&self) -> &[AccountCode] {
223 &self.foreign_account_code
224 }
225
226 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
228 &self.foreign_account_slot_names
229 }
230
231 pub fn advice_inputs(&self) -> &AdviceInputs {
233 &self.advice_inputs
234 }
235
236 pub fn tx_args(&self) -> &TransactionArgs {
238 &self.tx_args
239 }
240
241 pub fn read_storage_map_witness(
246 &self,
247 map_root: Word,
248 map_key: StorageMapKey,
249 ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
250 let leaf_index = map_key.hash().to_leaf_index();
252
253 let merkle_path = self.advice_inputs.store.get_path(map_root, leaf_index.into())?;
255 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
256
257 let merkle_node = self.advice_inputs.store.get_node(map_root, leaf_index.into())?;
259 let smt_leaf_elements = self
260 .advice_inputs
261 .map
262 .get(&merkle_node)
263 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
264 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, leaf_index)?;
265
266 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
268 let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
269
270 Ok(storage_witness)
271 }
272
273 pub fn read_vault_asset_witnesses(
281 &self,
282 vault_root: Word,
283 vault_keys: BTreeSet<AssetVaultKey>,
284 ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
285 let mut asset_witnesses = Vec::new();
286 for vault_key in vault_keys {
287 let smt_index = vault_key.to_leaf_index();
288 let merkle_path = self.advice_inputs.store.get_path(vault_root, smt_index.into())?;
290 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
291
292 let merkle_node = self.advice_inputs.store.get_node(vault_root, smt_index.into())?;
294 let smt_leaf_elements = self
295 .advice_inputs
296 .map
297 .get(&merkle_node)
298 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
299 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
300
301 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
303 let asset_witness = AssetWitness::new(smt_proof)?;
304 asset_witnesses.push(asset_witness);
305 }
306 Ok(asset_witnesses)
307 }
308
309 pub fn has_vault_asset_witness(&self, vault_root: Word, asset_key: &AssetVaultKey) -> bool {
314 let smt_index: NodeIndex = asset_key.to_leaf_index().into();
315
316 if !self.advice_inputs.store.has_path(vault_root, smt_index) {
318 return false;
319 }
320
321 match self.advice_inputs.store.get_node(vault_root, smt_index) {
323 Ok(node) => self.advice_inputs.map.contains_key(&node),
324 Err(_) => false,
325 }
326 }
327
328 pub fn read_vault_asset(
336 &self,
337 vault_root: Word,
338 asset_key: AssetVaultKey,
339 ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
340 let smt_index = asset_key.to_leaf_index();
342 let merkle_node = match self.advice_inputs.store.get_node(vault_root, smt_index.into()) {
343 Ok(node) => node,
344 Err(MerkleError::NodeIndexNotFoundInStore(..)) => return Ok(None),
345 Err(err) => return Err(err.into()),
346 };
347
348 let smt_leaf_elements = self
350 .advice_inputs
351 .map
352 .get(&merkle_node)
353 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
354 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
355
356 let asset = smt_leaf
358 .entries()
359 .iter()
360 .find(|(key, _value)| key == &asset_key.to_word())
361 .map(|(_key, value)| Asset::from_key_value(asset_key, *value))
362 .transpose()?;
363
364 Ok(asset)
365 }
366
367 pub fn read_foreign_account_inputs(
374 &self,
375 account_id: AccountId,
376 ) -> Result<AccountInputs, TransactionInputsExtractionError> {
377 if account_id == self.account().id() {
378 return Err(TransactionInputsExtractionError::AccountNotForeign);
379 }
380
381 let account_id_key = AccountIdKey::from(account_id);
383 let header_elements = self
384 .advice_inputs
385 .map
386 .get(&account_id_key.as_word())
387 .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
388
389 let header = AccountHeader::try_from_elements(header_elements)?;
391
392 let partial_account = self.read_foreign_partial_account(&header)?;
394 let witness = self.read_foreign_account_witness(&header)?;
395 Ok(AccountInputs::new(partial_account, witness))
396 }
397
398 fn read_foreign_partial_account(
401 &self,
402 header: &AccountHeader,
403 ) -> Result<PartialAccount, TransactionInputsExtractionError> {
404 let partial_vault = PartialVault::new(header.vault_root());
406
407 let account_code = self
409 .foreign_account_code
410 .iter()
411 .find(|code| code.commitment() == header.code_commitment())
412 .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
413 .clone();
414
415 let storage_header_elements = self
417 .advice_inputs
418 .map
419 .get(&header.storage_commitment())
420 .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
421
422 let storage_header = AccountStorageHeader::try_from_elements(
424 storage_header_elements,
425 self.foreign_account_slot_names(),
426 )?;
427
428 let partial_storage = PartialStorage::new(storage_header, [])?;
430
431 let partial_account = PartialAccount::new(
433 header.id(),
434 header.nonce(),
435 account_code,
436 partial_storage,
437 partial_vault,
438 None, )?;
440
441 Ok(partial_account)
442 }
443
444 fn read_foreign_account_witness(
447 &self,
448 header: &AccountHeader,
449 ) -> Result<AccountWitness, TransactionInputsExtractionError> {
450 let account_tree_root = self.block_header.account_root();
452 let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
453
454 let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
456
457 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
459
460 let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
462
463 Ok(witness)
464 }
465
466 pub fn into_parts(
471 self,
472 ) -> (
473 PartialAccount,
474 BlockHeader,
475 PartialBlockchain,
476 InputNotes<InputNote>,
477 TransactionArgs,
478 ) {
479 (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
480 }
481
482 fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
490 self.tx_args = tx_args;
491 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
492 }
493}
494
495impl Serializable for TransactionInputs {
499 fn write_into<W: ByteWriter>(&self, target: &mut W) {
500 self.account.write_into(target);
501 self.block_header.write_into(target);
502 self.blockchain.write_into(target);
503 self.input_notes.write_into(target);
504 self.tx_args.write_into(target);
505 self.advice_inputs.write_into(target);
506 self.foreign_account_code.write_into(target);
507 self.foreign_account_slot_names.write_into(target);
508 }
509}
510
511impl Deserializable for TransactionInputs {
512 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
513 let account = PartialAccount::read_from(source)?;
514 let block_header = BlockHeader::read_from(source)?;
515 let blockchain = PartialBlockchain::read_from(source)?;
516 let input_notes = InputNotes::read_from(source)?;
517 let tx_args = TransactionArgs::read_from(source)?;
518 let advice_inputs = AdviceInputs::read_from(source)?;
519 let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
520 let foreign_account_slot_names =
521 BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
522
523 Ok(TransactionInputs {
524 account,
525 block_header,
526 blockchain,
527 input_notes,
528 tx_args,
529 advice_inputs,
530 foreign_account_code,
531 foreign_account_slot_names,
532 })
533 }
534}
535
536fn validate_is_in_block(
541 note: &Note,
542 proof: &NoteInclusionProof,
543 block_header: &BlockHeader,
544) -> Result<(), TransactionInputError> {
545 let note_index = proof.location().node_index_in_block().into();
546 let note_commitment = note.commitment();
547 proof
548 .note_path()
549 .verify(note_index, note_commitment, &block_header.note_root())
550 .map_err(|_| {
551 TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
552 })
553}