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 (_stack, map, store) = new_advice_inputs.into_parts();
177        let mut advice_inputs = AdviceInputs::default().with_merkle_store(store);
178        advice_inputs.map = map;
179        self.advice_inputs = advice_inputs;
180        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
181    }
182
183    /// Updates the transaction arguments of the inputs.
184    #[cfg(feature = "testing")]
185    pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
186        self.set_tx_args_inner(tx_args);
187    }
188
189    // PUBLIC ACCESSORS
190    // --------------------------------------------------------------------------------------------
191
192    /// Returns the account against which the transaction is executed.
193    pub fn account(&self) -> &PartialAccount {
194        &self.account
195    }
196
197    /// Returns block header for the block referenced by the transaction.
198    pub fn block_header(&self) -> &BlockHeader {
199        &self.block_header
200    }
201
202    /// Returns partial blockchain containing authentication paths for all notes consumed by the
203    /// transaction.
204    pub fn blockchain(&self) -> &PartialBlockchain {
205        &self.blockchain
206    }
207
208    /// Returns the notes to be consumed in the transaction.
209    pub fn input_notes(&self) -> &InputNotes<InputNote> {
210        &self.input_notes
211    }
212
213    /// Returns the block number referenced by the inputs.
214    pub fn ref_block(&self) -> BlockNumber {
215        self.block_header.block_num()
216    }
217
218    /// Returns the transaction script to be executed.
219    pub fn tx_script(&self) -> Option<&TransactionScript> {
220        self.tx_args.tx_script()
221    }
222
223    /// Returns the foreign account code to be executed.
224    pub fn foreign_account_code(&self) -> &[AccountCode] {
225        &self.foreign_account_code
226    }
227
228    /// Returns the foreign account storage slot names.
229    pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
230        &self.foreign_account_slot_names
231    }
232
233    /// Returns the advice inputs to be consumed in the transaction.
234    pub fn advice_inputs(&self) -> &AdviceInputs {
235        &self.advice_inputs
236    }
237
238    /// Returns the transaction arguments to be consumed in the transaction.
239    pub fn tx_args(&self) -> &TransactionArgs {
240        &self.tx_args
241    }
242
243    // DATA EXTRACTORS
244    // --------------------------------------------------------------------------------------------
245
246    /// Reads the storage map witness for the given account and map key.
247    pub fn read_storage_map_witness(
248        &self,
249        map_root: Word,
250        map_key: StorageMapKey,
251    ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
252        // Convert map key into the index at which the key-value pair for this key is stored
253        let leaf_index = map_key.hash().to_leaf_index();
254
255        // Construct sparse Merkle path.
256        let merkle_path = self.advice_inputs.store.get_path(map_root, leaf_index.into())?;
257        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
258
259        // Construct SMT leaf.
260        let merkle_node = self.advice_inputs.store.get_node(map_root, leaf_index.into())?;
261        let smt_leaf_elements = self
262            .advice_inputs
263            .map
264            .get(&merkle_node)
265            .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
266        let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, leaf_index)?;
267
268        // Construct SMT proof and witness.
269        let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
270        let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
271
272        Ok(storage_witness)
273    }
274
275    /// Reads the vault asset witnesses for the given account and asset IDs.
276    ///
277    /// # Errors
278    /// Returns an error if:
279    /// - A Merkle tree with the specified root is not present in the advice data of these inputs.
280    /// - Witnesses for any of the requested assets are not in the specified Merkle tree.
281    /// - Construction of the Merkle path or the leaf node for the witness fails.
282    pub fn read_vault_asset_witnesses(
283        &self,
284        vault_root: Word,
285        asset_ids: BTreeSet<AssetId>,
286    ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
287        let mut asset_witnesses = Vec::new();
288        for asset_id in asset_ids {
289            let smt_index = asset_id.hash().to_leaf_index();
290            // Construct sparse Merkle path.
291            let merkle_path = self.advice_inputs.store.get_path(vault_root, smt_index.into())?;
292            let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
293
294            // Construct SMT leaf.
295            let merkle_node = self.advice_inputs.store.get_node(vault_root, smt_index.into())?;
296            let smt_leaf_elements = self
297                .advice_inputs
298                .map
299                .get(&merkle_node)
300                .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
301            let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
302
303            // Construct SMT proof and witness.
304            let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
305            let asset_witness = AssetWitness::new(smt_proof, [asset_id])?;
306            asset_witnesses.push(asset_witness);
307        }
308        Ok(asset_witnesses)
309    }
310
311    /// Returns true if the witness for the specified asset ID is present in these inputs.
312    ///
313    /// Note that this does not verify the witness' validity (i.e., that the witness is for a valid
314    /// asset).
315    pub fn has_vault_asset_witness(&self, vault_root: Word, asset_id: &AssetId) -> bool {
316        let smt_index: NodeIndex = asset_id.hash().to_leaf_index().into();
317
318        // make sure the path is in the Merkle store
319        if !self.advice_inputs.store.has_path(vault_root, smt_index) {
320            return false;
321        }
322
323        // make sure the node pre-image is in the Merkle store
324        match self.advice_inputs.store.get_node(vault_root, smt_index) {
325            Ok(node) => self.advice_inputs.map.contains_key(&node),
326            Err(_) => false,
327        }
328    }
329
330    /// Reads the asset stored under `asset_id` in the vault with the specified root.
331    ///
332    /// Returns `Ok(None)` when the key's leaf is tracked but holds no asset.
333    ///
334    /// # Errors
335    /// Returns an error if:
336    /// - A Merkle tree with the specified root, or the key's Merkle path, is not present in the
337    ///   advice data of these inputs.
338    /// - Construction of the leaf node or the asset fails.
339    pub fn read_vault_asset(
340        &self,
341        vault_root: Word,
342        asset_id: AssetId,
343    ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
344        let witnesses =
345            self.read_vault_asset_witnesses(vault_root, BTreeSet::from_iter([asset_id]))?;
346        let witness = witnesses
347            .into_iter()
348            .next()
349            .expect("one key requested should yield exactly one witness");
350        Ok(witness.find(asset_id))
351    }
352
353    /// Reads `AccountInputs` for a foreign account from the advice inputs.
354    ///
355    /// This function reverses the process of `TransactionAdviceInputs::add_foreign_accounts` by:
356    /// 1. Reading the account header from the advice map using the account_id_key.
357    /// 2. Building a `PartialAccount` from the header and foreign account code.
358    /// 3. Creating an `AccountWitness`.
359    pub fn read_foreign_account_inputs(
360        &self,
361        account_id: AccountId,
362    ) -> Result<AccountInputs, TransactionInputsExtractionError> {
363        if account_id == self.account().id() {
364            return Err(TransactionInputsExtractionError::AccountNotForeign);
365        }
366
367        // Read the account header elements from the advice map.
368        let account_id_key = AccountIdKey::from(account_id);
369        let header_elements = self
370            .advice_inputs
371            .map
372            .get(&account_id_key.as_word())
373            .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
374
375        // Parse the header from elements.
376        let header = AccountHeader::try_from_elements(header_elements)?;
377
378        // Construct and return account inputs.
379        let partial_account = self.read_foreign_partial_account(&header)?;
380        let witness = self.read_foreign_account_witness(&header)?;
381        Ok(AccountInputs::new(partial_account, witness))
382    }
383
384    /// Reads a foreign partial account from the advice inputs based on the account ID corresponding
385    /// to the provided header.
386    fn read_foreign_partial_account(
387        &self,
388        header: &AccountHeader,
389    ) -> Result<PartialAccount, TransactionInputsExtractionError> {
390        // Derive the partial vault from the header.
391        let partial_vault = PartialVault::new(header.vault_root());
392
393        // Find the corresponding foreign account code.
394        let account_code = self
395            .foreign_account_code
396            .iter()
397            .find(|code| code.commitment() == header.code_commitment())
398            .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
399            .clone();
400
401        // Try to get storage header from advice map using storage commitment as key.
402        let storage_header_elements = self
403            .advice_inputs
404            .map
405            .get(&header.storage_commitment())
406            .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
407
408        // Get slot names for this foreign account, or use empty map if not available.
409        let storage_header = AccountStorageHeader::try_from_elements(
410            storage_header_elements,
411            self.foreign_account_slot_names(),
412        )?;
413
414        // Build partial storage.
415        let partial_storage = PartialStorage::new(storage_header, [])?;
416
417        // Create the partial account.
418        let partial_account = PartialAccount::new(
419            header.id(),
420            header.nonce(),
421            account_code,
422            partial_storage,
423            partial_vault,
424            None, // We know that foreign accounts are existing accounts so a seed is not required.
425        )?;
426
427        Ok(partial_account)
428    }
429
430    /// Reads a foreign account witness from the advice inputs based on the account ID corresponding
431    /// to the provided header.
432    fn read_foreign_account_witness(
433        &self,
434        header: &AccountHeader,
435    ) -> Result<AccountWitness, TransactionInputsExtractionError> {
436        // Get the account tree root from the block header.
437        let account_tree_root = self.block_header.account_root();
438        let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
439
440        // Get the Merkle path from the merkle store.
441        let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
442
443        // Convert the Merkle path to SparseMerklePath.
444        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
445
446        // Create the account witness.
447        let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
448
449        Ok(witness)
450    }
451
452    // CONVERSIONS
453    // --------------------------------------------------------------------------------------------
454
455    /// Consumes these transaction inputs and returns their underlying components.
456    pub fn into_parts(
457        self,
458    ) -> (
459        PartialAccount,
460        BlockHeader,
461        PartialBlockchain,
462        InputNotes<InputNote>,
463        TransactionArgs,
464    ) {
465        (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
466    }
467
468    // HELPER METHODS
469    // --------------------------------------------------------------------------------------------
470
471    /// Replaces the current tx_args with the provided value.
472    ///
473    /// This also appends advice inputs from these transaction inputs to the advice inputs of the
474    /// tx args.
475    fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
476        self.tx_args = tx_args;
477        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
478    }
479}
480
481// SERIALIZATION / DESERIALIZATION
482// ================================================================================================
483
484impl Serializable for TransactionInputs {
485    fn write_into<W: ByteWriter>(&self, target: &mut W) {
486        self.account.write_into(target);
487        self.block_header.write_into(target);
488        self.blockchain.write_into(target);
489        self.input_notes.write_into(target);
490        self.tx_args.write_into(target);
491        self.advice_inputs.write_into(target);
492        self.foreign_account_code.write_into(target);
493        self.foreign_account_slot_names.write_into(target);
494    }
495}
496
497impl Deserializable for TransactionInputs {
498    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
499        let account = PartialAccount::read_from(source)?;
500        let block_header = BlockHeader::read_from(source)?;
501        let blockchain = PartialBlockchain::read_from(source)?;
502        let input_notes = InputNotes::read_from(source)?;
503        let tx_args = TransactionArgs::read_from(source)?;
504        let advice_inputs = AdviceInputs::read_from(source)?;
505        let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
506        let foreign_account_slot_names =
507            BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
508
509        Ok(TransactionInputs {
510            account,
511            block_header,
512            blockchain,
513            input_notes,
514            tx_args,
515            advice_inputs,
516            foreign_account_code,
517            foreign_account_slot_names,
518        })
519    }
520}
521
522// HELPER FUNCTIONS
523// ================================================================================================
524
525/// Validates whether the provided note belongs to the note tree of the specified block.
526fn validate_is_in_block(
527    note: &Note,
528    proof: &NoteInclusionProof,
529    block_header: &BlockHeader,
530) -> Result<(), TransactionInputError> {
531    let note_index = proof.location().block_note_tree_index().into();
532    let note_id = note.id().as_word();
533    proof
534        .note_path()
535        .verify(note_index, note_id, &block_header.note_root())
536        .map_err(|_| {
537            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
538        })
539}