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::NodeIndex;
7use miden_crypto::merkle::smt::{SmtLeaf, SmtProof};
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, AssetId, 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 asset IDs.
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        asset_ids: BTreeSet<AssetId>,
284    ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
285        let mut asset_witnesses = Vec::new();
286        for asset_id in asset_ids {
287            let smt_index = asset_id.hash().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, [asset_id])?;
304            asset_witnesses.push(asset_witness);
305        }
306        Ok(asset_witnesses)
307    }
308
309    /// Returns true if the witness for the specified asset ID 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_id: &AssetId) -> bool {
314        let smt_index: NodeIndex = asset_id.hash().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 stored under `asset_id` in the vault with the specified root.
329    ///
330    /// Returns `Ok(None)` when the key's leaf is tracked but holds no asset.
331    ///
332    /// # Errors
333    /// Returns an error if:
334    /// - A Merkle tree with the specified root, or the key's Merkle path, is not present in the
335    ///   advice data of these inputs.
336    /// - Construction of the leaf node or the asset fails.
337    pub fn read_vault_asset(
338        &self,
339        vault_root: Word,
340        asset_id: AssetId,
341    ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
342        let witnesses =
343            self.read_vault_asset_witnesses(vault_root, BTreeSet::from_iter([asset_id]))?;
344        let witness = witnesses
345            .into_iter()
346            .next()
347            .expect("one key requested should yield exactly one witness");
348        Ok(witness.find(asset_id))
349    }
350
351    /// Reads `AccountInputs` for a foreign account from the advice inputs.
352    ///
353    /// This function reverses the process of `TransactionAdviceInputs::add_foreign_accounts` by:
354    /// 1. Reading the account header from the advice map using the account_id_key.
355    /// 2. Building a `PartialAccount` from the header and foreign account code.
356    /// 3. Creating an `AccountWitness`.
357    pub fn read_foreign_account_inputs(
358        &self,
359        account_id: AccountId,
360    ) -> Result<AccountInputs, TransactionInputsExtractionError> {
361        if account_id == self.account().id() {
362            return Err(TransactionInputsExtractionError::AccountNotForeign);
363        }
364
365        // Read the account header elements from the advice map.
366        let account_id_key = AccountIdKey::from(account_id);
367        let header_elements = self
368            .advice_inputs
369            .map
370            .get(&account_id_key.as_word())
371            .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
372
373        // Parse the header from elements.
374        let header = AccountHeader::try_from_elements(header_elements)?;
375
376        // Construct and return account inputs.
377        let partial_account = self.read_foreign_partial_account(&header)?;
378        let witness = self.read_foreign_account_witness(&header)?;
379        Ok(AccountInputs::new(partial_account, witness))
380    }
381
382    /// Reads a foreign partial account from the advice inputs based on the account ID corresponding
383    /// to the provided header.
384    fn read_foreign_partial_account(
385        &self,
386        header: &AccountHeader,
387    ) -> Result<PartialAccount, TransactionInputsExtractionError> {
388        // Derive the partial vault from the header.
389        let partial_vault = PartialVault::new(header.vault_root());
390
391        // Find the corresponding foreign account code.
392        let account_code = self
393            .foreign_account_code
394            .iter()
395            .find(|code| code.commitment() == header.code_commitment())
396            .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
397            .clone();
398
399        // Try to get storage header from advice map using storage commitment as key.
400        let storage_header_elements = self
401            .advice_inputs
402            .map
403            .get(&header.storage_commitment())
404            .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
405
406        // Get slot names for this foreign account, or use empty map if not available.
407        let storage_header = AccountStorageHeader::try_from_elements(
408            storage_header_elements,
409            self.foreign_account_slot_names(),
410        )?;
411
412        // Build partial storage.
413        let partial_storage = PartialStorage::new(storage_header, [])?;
414
415        // Create the partial account.
416        let partial_account = PartialAccount::new(
417            header.id(),
418            header.nonce(),
419            account_code,
420            partial_storage,
421            partial_vault,
422            None, // We know that foreign accounts are existing accounts so a seed is not required.
423        )?;
424
425        Ok(partial_account)
426    }
427
428    /// Reads a foreign account witness from the advice inputs based on the account ID corresponding
429    /// to the provided header.
430    fn read_foreign_account_witness(
431        &self,
432        header: &AccountHeader,
433    ) -> Result<AccountWitness, TransactionInputsExtractionError> {
434        // Get the account tree root from the block header.
435        let account_tree_root = self.block_header.account_root();
436        let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
437
438        // Get the Merkle path from the merkle store.
439        let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
440
441        // Convert the Merkle path to SparseMerklePath.
442        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
443
444        // Create the account witness.
445        let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
446
447        Ok(witness)
448    }
449
450    // CONVERSIONS
451    // --------------------------------------------------------------------------------------------
452
453    /// Consumes these transaction inputs and returns their underlying components.
454    pub fn into_parts(
455        self,
456    ) -> (
457        PartialAccount,
458        BlockHeader,
459        PartialBlockchain,
460        InputNotes<InputNote>,
461        TransactionArgs,
462    ) {
463        (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
464    }
465
466    // HELPER METHODS
467    // --------------------------------------------------------------------------------------------
468
469    /// Replaces the current tx_args with the provided value.
470    ///
471    /// This also appends advice inputs from these transaction inputs to the advice inputs of the
472    /// tx args.
473    fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
474        self.tx_args = tx_args;
475        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
476    }
477}
478
479// SERIALIZATION / DESERIALIZATION
480// ================================================================================================
481
482impl Serializable for TransactionInputs {
483    fn write_into<W: ByteWriter>(&self, target: &mut W) {
484        self.account.write_into(target);
485        self.block_header.write_into(target);
486        self.blockchain.write_into(target);
487        self.input_notes.write_into(target);
488        self.tx_args.write_into(target);
489        self.advice_inputs.write_into(target);
490        self.foreign_account_code.write_into(target);
491        self.foreign_account_slot_names.write_into(target);
492    }
493}
494
495impl Deserializable for TransactionInputs {
496    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
497        let account = PartialAccount::read_from(source)?;
498        let block_header = BlockHeader::read_from(source)?;
499        let blockchain = PartialBlockchain::read_from(source)?;
500        let input_notes = InputNotes::read_from(source)?;
501        let tx_args = TransactionArgs::read_from(source)?;
502        let advice_inputs = AdviceInputs::read_from(source)?;
503        let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
504        let foreign_account_slot_names =
505            BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
506
507        Ok(TransactionInputs {
508            account,
509            block_header,
510            blockchain,
511            input_notes,
512            tx_args,
513            advice_inputs,
514            foreign_account_code,
515            foreign_account_slot_names,
516        })
517    }
518}
519
520// HELPER FUNCTIONS
521// ================================================================================================
522
523/// Validates whether the provided note belongs to the note tree of the specified block.
524fn validate_is_in_block(
525    note: &Note,
526    proof: &NoteInclusionProof,
527    block_header: &BlockHeader,
528) -> Result<(), TransactionInputError> {
529    let note_index = proof.location().block_note_tree_index().into();
530    let note_id = note.id().as_word();
531    proof
532        .note_path()
533        .verify(note_index, note_id, &block_header.note_root())
534        .map_err(|_| {
535            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
536        })
537}