Skip to main content

miden_protocol/transaction/inputs/
mod.rs

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// TRANSACTION INPUTS
50// ================================================================================================
51
52/// Contains the data required to execute a transaction.
53#[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    /// Storage slot names for foreign accounts.
63    foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
64}
65
66impl TransactionInputs {
67    // CONSTRUCTOR
68    // --------------------------------------------------------------------------------------------
69
70    /// Returns new [`TransactionInputs`] instantiated with the specified parameters.
71    ///
72    /// # Errors
73    ///
74    /// Returns an error if:
75    /// - The partial blockchain does not track the block headers required to prove inclusion of any
76    ///   authenticated input note.
77    pub fn new(
78        account: PartialAccount,
79        block_header: BlockHeader,
80        blockchain: PartialBlockchain,
81        input_notes: InputNotes<InputNote>,
82    ) -> Result<Self, TransactionInputError> {
83        // Check that the partial blockchain and block header are consistent.
84        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        // Validate the authentication paths of the input notes.
97        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    /// Replaces the transaction inputs and assigns the given asset witnesses.
124    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    /// Replaces the transaction inputs and assigns the given foreign account code.
138    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    /// Replaces the transaction inputs and assigns the given transaction arguments.
144    pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
145        self.set_tx_args_inner(tx_args);
146        self
147    }
148
149    /// Replaces the transaction inputs and assigns the given foreign account slot names.
150    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    /// Replaces the transaction inputs and assigns the given advice inputs.
159    pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
160        self.set_advice_inputs(advice_inputs);
161        self
162    }
163
164    // MUTATORS
165    // --------------------------------------------------------------------------------------------
166
167    /// Replaces the input notes for the transaction.
168    pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
169        self.input_notes = new_notes.into();
170    }
171
172    /// Replaces the advice inputs for the transaction.
173    ///
174    /// Note: the advice stack from the provided advice inputs is discarded.
175    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    /// Updates the transaction arguments of the inputs.
182    #[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    // PUBLIC ACCESSORS
188    // --------------------------------------------------------------------------------------------
189
190    /// Returns the account against which the transaction is executed.
191    pub fn account(&self) -> &PartialAccount {
192        &self.account
193    }
194
195    /// Returns block header for the block referenced by the transaction.
196    pub fn block_header(&self) -> &BlockHeader {
197        &self.block_header
198    }
199
200    /// Returns partial blockchain containing authentication paths for all notes consumed by the
201    /// transaction.
202    pub fn blockchain(&self) -> &PartialBlockchain {
203        &self.blockchain
204    }
205
206    /// Returns the notes to be consumed in the transaction.
207    pub fn input_notes(&self) -> &InputNotes<InputNote> {
208        &self.input_notes
209    }
210
211    /// Returns the block number referenced by the inputs.
212    pub fn ref_block(&self) -> BlockNumber {
213        self.block_header.block_num()
214    }
215
216    /// Returns the transaction script to be executed.
217    pub fn tx_script(&self) -> Option<&TransactionScript> {
218        self.tx_args.tx_script()
219    }
220
221    /// Returns the foreign account code to be executed.
222    pub fn foreign_account_code(&self) -> &[AccountCode] {
223        &self.foreign_account_code
224    }
225
226    /// Returns the foreign account storage slot names.
227    pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
228        &self.foreign_account_slot_names
229    }
230
231    /// Returns the advice inputs to be consumed in the transaction.
232    pub fn advice_inputs(&self) -> &AdviceInputs {
233        &self.advice_inputs
234    }
235
236    /// Returns the transaction arguments to be consumed in the transaction.
237    pub fn tx_args(&self) -> &TransactionArgs {
238        &self.tx_args
239    }
240
241    // DATA EXTRACTORS
242    // --------------------------------------------------------------------------------------------
243
244    /// Reads the storage map witness for the given account and map key.
245    pub fn read_storage_map_witness(
246        &self,
247        map_root: Word,
248        map_key: StorageMapKey,
249    ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
250        // Convert map key into the index at which the key-value pair for this key is stored
251        let leaf_index = map_key.hash().to_leaf_index();
252
253        // Construct sparse Merkle path.
254        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        // Construct SMT leaf.
258        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        // Construct SMT proof and witness.
267        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    /// Reads the vault asset witnesses for the given account and vault keys.
274    ///
275    /// # Errors
276    /// Returns an error if:
277    /// - A Merkle tree with the specified root is not present in the advice data of these inputs.
278    /// - Witnesses for any of the requested assets are not in the specified Merkle tree.
279    /// - Construction of the Merkle path or the leaf node for the witness fails.
280    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            // Construct sparse Merkle path.
289            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            // Construct SMT leaf.
293            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            // Construct SMT proof and witness.
302            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    /// Returns true if the witness for the specified asset key is present in these inputs.
310    ///
311    /// Note that this does not verify the witness' validity (i.e., that the witness is for a valid
312    /// asset).
313    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        // make sure the path is in the Merkle store
317        if !self.advice_inputs.store.has_path(vault_root, smt_index) {
318            return false;
319        }
320
321        // make sure the node pre-image is in the Merkle store
322        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    /// Reads the asset from the specified vault under the specified key; returns `None` if the
329    /// specified asset is not present in these inputs.
330    ///
331    /// # Errors
332    /// Returns an error if:
333    /// - A Merkle tree with the specified root is not present in the advice data of these inputs.
334    /// - Construction of the leaf node or the asset fails.
335    pub fn read_vault_asset(
336        &self,
337        vault_root: Word,
338        asset_key: AssetVaultKey,
339    ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
340        // Get the node corresponding to the asset_key; if not found return None
341        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        // Construct SMT leaf for this asset key
349        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        // Find the asset in the SMT leaf
357        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    /// Reads `AccountInputs` for a foreign account from the advice inputs.
368    ///
369    /// This function reverses the process of `TransactionAdviceInputs::add_foreign_accounts` by:
370    /// 1. Reading the account header from the advice map using the account_id_key.
371    /// 2. Building a `PartialAccount` from the header and foreign account code.
372    /// 3. Creating an `AccountWitness`.
373    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        // Read the account header elements from the advice map.
382        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        // Parse the header from elements.
390        let header = AccountHeader::try_from_elements(header_elements)?;
391
392        // Construct and return account inputs.
393        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    /// Reads a foreign partial account from the advice inputs based on the account ID corresponding
399    /// to the provided header.
400    fn read_foreign_partial_account(
401        &self,
402        header: &AccountHeader,
403    ) -> Result<PartialAccount, TransactionInputsExtractionError> {
404        // Derive the partial vault from the header.
405        let partial_vault = PartialVault::new(header.vault_root());
406
407        // Find the corresponding foreign account code.
408        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        // Try to get storage header from advice map using storage commitment as key.
416        let storage_header_elements = self
417            .advice_inputs
418            .map
419            .get(&header.storage_commitment())
420            .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
421
422        // Get slot names for this foreign account, or use empty map if not available.
423        let storage_header = AccountStorageHeader::try_from_elements(
424            storage_header_elements,
425            self.foreign_account_slot_names(),
426        )?;
427
428        // Build partial storage.
429        let partial_storage = PartialStorage::new(storage_header, [])?;
430
431        // Create the partial account.
432        let partial_account = PartialAccount::new(
433            header.id(),
434            header.nonce(),
435            account_code,
436            partial_storage,
437            partial_vault,
438            None, // We know that foreign accounts are existing accounts so a seed is not required.
439        )?;
440
441        Ok(partial_account)
442    }
443
444    /// Reads a foreign account witness from the advice inputs based on the account ID corresponding
445    /// to the provided header.
446    fn read_foreign_account_witness(
447        &self,
448        header: &AccountHeader,
449    ) -> Result<AccountWitness, TransactionInputsExtractionError> {
450        // Get the account tree root from the block header.
451        let account_tree_root = self.block_header.account_root();
452        let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
453
454        // Get the Merkle path from the merkle store.
455        let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
456
457        // Convert the Merkle path to SparseMerklePath.
458        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
459
460        // Create the account witness.
461        let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
462
463        Ok(witness)
464    }
465
466    // CONVERSIONS
467    // --------------------------------------------------------------------------------------------
468
469    /// Consumes these transaction inputs and returns their underlying components.
470    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    // HELPER METHODS
483    // --------------------------------------------------------------------------------------------
484
485    /// Replaces the current tx_args with the provided value.
486    ///
487    /// This also appends advice inputs from these transaction inputs to the advice inputs of the
488    /// tx args.
489    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
495// SERIALIZATION / DESERIALIZATION
496// ================================================================================================
497
498impl 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
536// HELPER FUNCTIONS
537// ================================================================================================
538
539/// Validates whether the provided note belongs to the note tree of the specified block.
540fn 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}