Skip to main content

miden_protocol/transaction/inputs/
mod.rs

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// TRANSACTION INPUTS
42// ================================================================================================
43
44/// Contains the data required to execute a transaction.
45#[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    /// Storage slot names for foreign accounts.
55    foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
56}
57
58impl TransactionInputs {
59    // CONSTRUCTOR
60    // --------------------------------------------------------------------------------------------
61
62    /// Returns new [`TransactionInputs`] instantiated with the specified parameters.
63    ///
64    /// # Errors
65    ///
66    /// Returns an error if:
67    /// - The partial blockchain does not track the block headers required to prove inclusion of any
68    ///   authenticated input note.
69    pub fn new(
70        account: PartialAccount,
71        block_header: BlockHeader,
72        blockchain: PartialBlockchain,
73        input_notes: InputNotes<InputNote>,
74    ) -> Result<Self, TransactionInputError> {
75        // Check that the partial blockchain and block header are consistent.
76        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        // Validate the authentication paths of the input notes.
89        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    /// Replaces the transaction inputs and assigns the given asset witnesses.
116    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    /// Replaces the transaction inputs and assigns the given foreign account code.
129    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    /// Replaces the transaction inputs and assigns the given transaction arguments.
135    pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
136        self.set_tx_args_inner(tx_args);
137        self
138    }
139
140    /// Replaces the transaction inputs and assigns the given foreign account slot names.
141    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    /// Replaces the transaction inputs and assigns the given advice inputs.
150    pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
151        self.set_advice_inputs(advice_inputs);
152        self
153    }
154
155    // MUTATORS
156    // --------------------------------------------------------------------------------------------
157
158    /// Replaces the input notes for the transaction.
159    pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
160        self.input_notes = new_notes.into();
161    }
162
163    /// Replaces the advice inputs for the transaction.
164    ///
165    /// Note: the advice stack from the provided advice inputs is discarded.
166    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    /// Updates the transaction arguments of the inputs.
173    #[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    // PUBLIC ACCESSORS
179    // --------------------------------------------------------------------------------------------
180
181    /// Returns the account against which the transaction is executed.
182    pub fn account(&self) -> &PartialAccount {
183        &self.account
184    }
185
186    /// Returns block header for the block referenced by the transaction.
187    pub fn block_header(&self) -> &BlockHeader {
188        &self.block_header
189    }
190
191    /// Returns partial blockchain containing authentication paths for all notes consumed by the
192    /// transaction.
193    pub fn blockchain(&self) -> &PartialBlockchain {
194        &self.blockchain
195    }
196
197    /// Returns the notes to be consumed in the transaction.
198    pub fn input_notes(&self) -> &InputNotes<InputNote> {
199        &self.input_notes
200    }
201
202    /// Returns the block number referenced by the inputs.
203    pub fn ref_block(&self) -> BlockNumber {
204        self.block_header.block_num()
205    }
206
207    /// Returns the transaction script to be executed.
208    pub fn tx_script(&self) -> Option<&TransactionScript> {
209        self.tx_args.tx_script()
210    }
211
212    /// Returns the foreign account code to be executed.
213    pub fn foreign_account_code(&self) -> &[AccountCode] {
214        &self.foreign_account_code
215    }
216
217    /// Returns the foreign account storage slot names.
218    pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
219        &self.foreign_account_slot_names
220    }
221
222    /// Returns the advice inputs to be consumed in the transaction.
223    pub fn advice_inputs(&self) -> &AdviceInputs {
224        &self.advice_inputs
225    }
226
227    /// Returns the transaction arguments to be consumed in the transaction.
228    pub fn tx_args(&self) -> &TransactionArgs {
229        &self.tx_args
230    }
231
232    // DATA EXTRACTORS
233    // --------------------------------------------------------------------------------------------
234
235    /// Reads the storage map witness for the given account and map key.
236    pub fn read_storage_map_witness(
237        &self,
238        map_root: Word,
239        map_key: StorageMapKey,
240    ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
241        // Convert map key into the index at which the key-value pair for this key is stored
242        let leaf_index = map_key.hash().to_leaf_index();
243
244        // Construct sparse Merkle path.
245        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        // Construct SMT leaf.
249        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        // Construct SMT proof and witness.
258        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    /// Reads the vault asset witnesses for the given account and vault keys.
265    ///
266    /// # Errors
267    /// Returns an error if:
268    /// - A Merkle tree with the specified root is not present in the advice data of these inputs.
269    /// - Witnesses for any of the requested assets are not in the specified Merkle tree.
270    /// - Construction of the Merkle path or the leaf node for the witness fails.
271    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            // Construct sparse Merkle path.
280            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            // Construct SMT leaf.
284            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            // Construct SMT proof and witness.
293            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    /// Returns true if the witness for the specified asset key is present in these inputs.
301    ///
302    /// Note that this does not verify the witness' validity (i.e., that the witness is for a valid
303    /// asset).
304    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        // make sure the path is in the Merkle store
308        if !self.advice_inputs.store.has_path(vault_root, smt_index) {
309            return false;
310        }
311
312        // make sure the node pre-image is in the Merkle store
313        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    /// Reads the asset from the specified vault under the specified key; returns `None` if the
320    /// specified asset is not present in these inputs.
321    ///
322    /// # Errors
323    /// Returns an error if:
324    /// - A Merkle tree with the specified root is not present in the advice data of these inputs.
325    /// - Construction of the leaf node or the asset fails.
326    pub fn read_vault_asset(
327        &self,
328        vault_root: Word,
329        asset_key: AssetVaultKey,
330    ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
331        // Get the node corresponding to the asset_key; if not found return None
332        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        // Construct SMT leaf for this asset key
340        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        // Find the asset in the SMT leaf
348        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    /// Reads AccountInputs for a foreign account from the advice inputs.
359    ///
360    /// This function reverses the process of [`TransactionAdviceInputs::add_foreign_accounts`] by:
361    /// 1. Reading the account header from the advice map using the account_id_key.
362    /// 2. Building a PartialAccount from the header and foreign account code.
363    /// 3. Creating an AccountWitness.
364    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        // Read the account header elements from the advice map.
373        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        // Parse the header from elements.
381        let header = AccountHeader::try_from_elements(header_elements)?;
382
383        // Construct and return account inputs.
384        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    /// Reads a foreign partial account from the advice inputs based on the account ID corresponding
390    /// to the provided header.
391    fn read_foreign_partial_account(
392        &self,
393        header: &AccountHeader,
394    ) -> Result<PartialAccount, TransactionInputsExtractionError> {
395        // Derive the partial vault from the header.
396        let partial_vault = PartialVault::new(header.vault_root());
397
398        // Find the corresponding foreign account code.
399        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        // Try to get storage header from advice map using storage commitment as key.
407        let storage_header_elements = self
408            .advice_inputs
409            .map
410            .get(&header.storage_commitment())
411            .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
412
413        // Get slot names for this foreign account, or use empty map if not available.
414        let storage_header = AccountStorageHeader::try_from_elements(
415            storage_header_elements,
416            self.foreign_account_slot_names(),
417        )?;
418
419        // Build partial storage.
420        let partial_storage = PartialStorage::new(storage_header, [])?;
421
422        // Create the partial account.
423        let partial_account = PartialAccount::new(
424            header.id(),
425            header.nonce(),
426            account_code,
427            partial_storage,
428            partial_vault,
429            None, // We know that foreign accounts are existing accounts so a seed is not required.
430        )?;
431
432        Ok(partial_account)
433    }
434
435    /// Reads a foreign account witness from the advice inputs based on the account ID corresponding
436    /// to the provided header.
437    fn read_foreign_account_witness(
438        &self,
439        header: &AccountHeader,
440    ) -> Result<AccountWitness, TransactionInputsExtractionError> {
441        // Get the account tree root from the block header.
442        let account_tree_root = self.block_header.account_root();
443        let leaf_index: NodeIndex = account_id_to_smt_index(header.id()).into();
444
445        // Get the Merkle path from the merkle store.
446        let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
447
448        // Convert the Merkle path to SparseMerklePath.
449        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
450
451        // Create the account witness.
452        let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
453
454        Ok(witness)
455    }
456
457    // CONVERSIONS
458    // --------------------------------------------------------------------------------------------
459
460    /// Consumes these transaction inputs and returns their underlying components.
461    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    // HELPER METHODS
474    // --------------------------------------------------------------------------------------------
475
476    /// Replaces the current tx_args with the provided value.
477    ///
478    /// This also appends advice inputs from these transaction inputs to the advice inputs of the
479    /// tx args.
480    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
486// SERIALIZATION / DESERIALIZATION
487// ================================================================================================
488
489impl 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
529// HELPER FUNCTIONS
530// ================================================================================================
531
532// TODO(sergerad): Move this fn to crypto SmtLeaf::try_from_elements.
533pub 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    // Based on the miden-crypto SMT leaf serialization format.
540
541    if elements.is_empty() {
542        return Ok(SmtLeaf::new_empty(leaf_index));
543    }
544
545    // Elements should be organized into a contiguous array of K/V Words (4 Felts each).
546    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        // Single entry.
556        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        // Multiple entries.
561        let mut entries = Vec::with_capacity(num_entries);
562        // Read k/v pairs from each entry.
563        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
584/// Validates whether the provided note belongs to the note tree of the specified block.
585fn 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}