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_crypto::merkle::NodeIndex;
6use miden_crypto::merkle::smt::{SmtLeaf, SmtProof};
7
8use super::PartialBlockchain;
9use crate::Word;
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::protocol_config::ProtocolConfig;
29use crate::transaction::{TransactionArgs, TransactionScript};
30use crate::utils::serde::{
31    ByteReader,
32    ByteWriter,
33    Deserializable,
34    DeserializationError,
35    Serializable,
36};
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    /// The chain's protocol configuration, which `block_header` only commits to.
58    protocol_config: ProtocolConfig,
59    blockchain: PartialBlockchain,
60    input_notes: InputNotes<InputNote>,
61    tx_args: TransactionArgs,
62    advice_inputs: AdviceInputs,
63    foreign_account_code: Vec<AccountCode>,
64    /// Storage slot names for foreign accounts.
65    foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
66}
67
68impl TransactionInputs {
69    // CONSTRUCTOR
70    // --------------------------------------------------------------------------------------------
71
72    /// Returns new [`TransactionInputs`] instantiated with the specified parameters.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if:
77    /// - The protocol config does not match the one committed to by the block header.
78    /// - The partial blockchain does not track the block headers required to prove inclusion of any
79    ///   authenticated input note.
80    pub fn new(
81        account: PartialAccount,
82        block_header: BlockHeader,
83        protocol_config: ProtocolConfig,
84        blockchain: PartialBlockchain,
85        input_notes: InputNotes<InputNote>,
86    ) -> Result<Self, TransactionInputError> {
87        Self::try_from_parts(
88            account,
89            block_header,
90            protocol_config,
91            blockchain,
92            input_notes,
93            TransactionArgs::default(),
94            AdviceInputs::default(),
95            Vec::new(),
96            BTreeMap::new(),
97        )
98    }
99
100    /// Creates [`TransactionInputs`] from all transaction-input components.
101    ///
102    /// # Errors
103    ///
104    /// Returns an error under the same conditions as [`Self::new`].
105    #[allow(clippy::too_many_arguments)]
106    pub fn try_from_parts(
107        account: PartialAccount,
108        block_header: BlockHeader,
109        protocol_config: ProtocolConfig,
110        blockchain: PartialBlockchain,
111        input_notes: InputNotes<InputNote>,
112        tx_args: TransactionArgs,
113        advice_inputs: AdviceInputs,
114        foreign_account_code: Vec<AccountCode>,
115        foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
116    ) -> Result<Self, TransactionInputError> {
117        Self::validate(&block_header, &protocol_config, &blockchain, &input_notes)?;
118
119        Ok(Self {
120            account,
121            block_header,
122            protocol_config,
123            blockchain,
124            input_notes,
125            tx_args,
126            advice_inputs,
127            foreign_account_code,
128            foreign_account_slot_names,
129        })
130    }
131
132    // VALIDATION
133    // --------------------------------------------------------------------------------------------
134
135    fn validate(
136        block_header: &BlockHeader,
137        protocol_config: &ProtocolConfig,
138        blockchain: &PartialBlockchain,
139        input_notes: &InputNotes<InputNote>,
140    ) -> Result<(), TransactionInputError> {
141        // Check that the protocol config is the one the block header commits to.
142        let protocol_config_commitment = protocol_config.to_commitment();
143        if protocol_config_commitment != block_header.protocol_config_commitment() {
144            return Err(TransactionInputError::InconsistentProtocolConfig {
145                expected: block_header.protocol_config_commitment(),
146                actual: protocol_config_commitment,
147            });
148        }
149        // Check that the partial blockchain and block header are consistent.
150        if blockchain.chain_length() != block_header.block_num() {
151            return Err(TransactionInputError::InconsistentChainLength {
152                expected: block_header.block_num(),
153                actual: blockchain.chain_length(),
154            });
155        }
156        if blockchain.peaks().hash_peaks() != block_header.chain_commitment() {
157            return Err(TransactionInputError::InconsistentChainCommitment {
158                expected: block_header.chain_commitment(),
159                actual: blockchain.peaks().hash_peaks(),
160            });
161        }
162        // Validate the authentication paths of the input notes.
163        for note in input_notes.iter() {
164            if let InputNote::Authenticated { note, proof } = note {
165                let note_block_num = proof.location().block_num();
166                let block_header = if note_block_num == block_header.block_num() {
167                    block_header
168                } else {
169                    blockchain.get_block(note_block_num).ok_or(
170                        TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
171                    )?
172                };
173                validate_is_in_block(note, proof, block_header)?;
174            }
175        }
176
177        Ok(())
178    }
179
180    /// Replaces the transaction inputs and assigns the given asset witnesses.
181    pub fn with_asset_witnesses(mut self, witnesses: Vec<AssetWitness>) -> Self {
182        for witness in witnesses {
183            let leaf = witness.proof().leaf();
184            let witness_inputs = AdviceInputs::default()
185                .with_merkle_store(witness.authenticated_nodes().collect())
186                .with_map([(leaf.hash(), leaf.to_elements().collect())]);
187            self.advice_inputs.extend(witness_inputs);
188        }
189
190        self
191    }
192
193    /// Replaces the transaction inputs and assigns the given foreign account code.
194    pub fn with_foreign_account_code(mut self, foreign_account_code: Vec<AccountCode>) -> Self {
195        self.foreign_account_code = foreign_account_code;
196        self
197    }
198
199    /// Replaces the transaction inputs and assigns the given transaction arguments.
200    pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
201        self.set_tx_args_inner(tx_args);
202        self
203    }
204
205    /// Replaces the transaction inputs and assigns the given foreign account slot names.
206    pub fn with_foreign_account_slot_names(
207        mut self,
208        foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
209    ) -> Self {
210        self.foreign_account_slot_names = foreign_account_slot_names;
211        self
212    }
213
214    /// Replaces the transaction inputs and assigns the given advice inputs.
215    pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
216        self.set_advice_inputs(advice_inputs);
217        self
218    }
219
220    // MUTATORS
221    // --------------------------------------------------------------------------------------------
222
223    /// Replaces the input notes for the transaction.
224    pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
225        self.input_notes = new_notes.into();
226    }
227
228    /// Replaces the advice inputs for the transaction.
229    ///
230    /// Note: the advice stack from the provided advice inputs is discarded.
231    pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
232        let (_stack, map, store) = new_advice_inputs.into_parts();
233        self.advice_inputs = AdviceInputs::from(map).with_merkle_store(store);
234        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
235    }
236
237    /// Updates the transaction arguments of the inputs.
238    #[cfg(feature = "testing")]
239    pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
240        self.set_tx_args_inner(tx_args);
241    }
242
243    // PUBLIC ACCESSORS
244    // --------------------------------------------------------------------------------------------
245
246    /// Returns the account against which the transaction is executed.
247    pub fn account(&self) -> &PartialAccount {
248        &self.account
249    }
250
251    /// Returns block header for the block referenced by the transaction.
252    pub fn block_header(&self) -> &BlockHeader {
253        &self.block_header
254    }
255
256    /// Returns the protocol configuration committed to by the referenced block header.
257    pub fn protocol_config(&self) -> &ProtocolConfig {
258        &self.protocol_config
259    }
260
261    /// Returns partial blockchain containing authentication paths for all notes consumed by the
262    /// transaction.
263    pub fn blockchain(&self) -> &PartialBlockchain {
264        &self.blockchain
265    }
266
267    /// Returns the notes to be consumed in the transaction.
268    pub fn input_notes(&self) -> &InputNotes<InputNote> {
269        &self.input_notes
270    }
271
272    /// Returns the block number referenced by the inputs.
273    pub fn ref_block(&self) -> BlockNumber {
274        self.block_header.block_num()
275    }
276
277    /// Returns the commitments of the blocks the transaction authenticates, keyed by block number.
278    ///
279    /// These are the reference block and the blocks tracked by the partial blockchain, i.e.
280    /// exactly the blocks whose commitment the transaction kernel can read.
281    pub fn collect_block_commitments(&self) -> BTreeMap<BlockNumber, Word> {
282        let mut commitments: BTreeMap<BlockNumber, Word> = self
283            .blockchain
284            .block_headers()
285            .map(|header| (header.block_num(), header.commitment()))
286            .collect();
287        commitments.insert(self.ref_block(), self.block_header.commitment());
288
289        commitments
290    }
291
292    /// Returns the transaction script to be executed.
293    pub fn tx_script(&self) -> Option<&TransactionScript> {
294        self.tx_args.tx_script()
295    }
296
297    /// Returns the foreign account code to be executed.
298    pub fn foreign_account_code(&self) -> &[AccountCode] {
299        &self.foreign_account_code
300    }
301
302    /// Returns the foreign account storage slot names.
303    pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
304        &self.foreign_account_slot_names
305    }
306
307    /// Returns the advice inputs to be consumed in the transaction.
308    pub fn advice_inputs(&self) -> &AdviceInputs {
309        &self.advice_inputs
310    }
311
312    /// Returns the transaction arguments to be consumed in the transaction.
313    pub fn tx_args(&self) -> &TransactionArgs {
314        &self.tx_args
315    }
316
317    // DATA EXTRACTORS
318    // --------------------------------------------------------------------------------------------
319
320    /// Reads the storage map witness for the given account and map key.
321    pub fn read_storage_map_witness(
322        &self,
323        map_root: Word,
324        map_key: StorageMapKey,
325    ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
326        // Convert map key into the index at which the key-value pair for this key is stored
327        let leaf_index = map_key.hash().to_leaf_index();
328
329        // Construct sparse Merkle path.
330        let merkle_path = self.advice_inputs.store().get_path(map_root, leaf_index.into())?;
331        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
332
333        // Construct SMT leaf.
334        let merkle_node = self.advice_inputs.store().get_node(map_root, leaf_index.into())?;
335        let smt_leaf_elements = self
336            .advice_inputs
337            .map()
338            .get(&merkle_node)
339            .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
340        let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, leaf_index)?;
341
342        // Construct SMT proof and witness.
343        let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
344        let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
345
346        Ok(storage_witness)
347    }
348
349    /// Reads the vault asset witnesses for the given account and asset IDs.
350    ///
351    /// # Errors
352    /// Returns an error if:
353    /// - A Merkle tree with the specified root is not present in the advice data of these inputs.
354    /// - Witnesses for any of the requested assets are not in the specified Merkle tree.
355    /// - Construction of the Merkle path or the leaf node for the witness fails.
356    pub fn read_vault_asset_witnesses(
357        &self,
358        vault_root: Word,
359        asset_ids: BTreeSet<AssetId>,
360    ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
361        let mut asset_witnesses = Vec::new();
362        for asset_id in asset_ids {
363            let smt_index = asset_id.hash().to_leaf_index();
364            // Construct sparse Merkle path.
365            let merkle_path = self.advice_inputs.store().get_path(vault_root, smt_index.into())?;
366            let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
367
368            // Construct SMT leaf.
369            let merkle_node = self.advice_inputs.store().get_node(vault_root, smt_index.into())?;
370            let smt_leaf_elements = self
371                .advice_inputs
372                .map()
373                .get(&merkle_node)
374                .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
375            let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
376
377            // Construct SMT proof and witness.
378            let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
379            let asset_witness = AssetWitness::new(smt_proof, [asset_id])?;
380            asset_witnesses.push(asset_witness);
381        }
382        Ok(asset_witnesses)
383    }
384
385    /// Returns true if the witness for the specified asset ID is present in these inputs.
386    ///
387    /// Note that this does not verify the witness' validity (i.e., that the witness is for a valid
388    /// asset).
389    pub fn has_vault_asset_witness(&self, vault_root: Word, asset_id: &AssetId) -> bool {
390        let smt_index: NodeIndex = asset_id.hash().to_leaf_index().into();
391
392        // make sure the path is in the Merkle store
393        if !self.advice_inputs.store().has_path(vault_root, smt_index) {
394            return false;
395        }
396
397        // make sure the node pre-image is in the Merkle store
398        match self.advice_inputs.store().get_node(vault_root, smt_index) {
399            Ok(node) => self.advice_inputs.map().contains_key(&node),
400            Err(_) => false,
401        }
402    }
403
404    /// Reads the asset stored under `asset_id` in the vault with the specified root.
405    ///
406    /// Returns `Ok(None)` when the key's leaf is tracked but holds no asset.
407    ///
408    /// # Errors
409    /// Returns an error if:
410    /// - A Merkle tree with the specified root, or the key's Merkle path, is not present in the
411    ///   advice data of these inputs.
412    /// - Construction of the leaf node or the asset fails.
413    pub fn read_vault_asset(
414        &self,
415        vault_root: Word,
416        asset_id: AssetId,
417    ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
418        let witnesses =
419            self.read_vault_asset_witnesses(vault_root, BTreeSet::from_iter([asset_id]))?;
420        let witness = witnesses
421            .into_iter()
422            .next()
423            .expect("one key requested should yield exactly one witness");
424        Ok(witness.find(asset_id))
425    }
426
427    /// Reads `AccountInputs` for a foreign account from the advice inputs.
428    ///
429    /// This function reverses the process of `TransactionAdviceInputs::add_foreign_accounts` by:
430    /// 1. Reading the account header from the advice map using the account_id_key.
431    /// 2. Building a `PartialAccount` from the header and foreign account code.
432    /// 3. Creating an `AccountWitness`.
433    pub fn read_foreign_account_inputs(
434        &self,
435        account_id: AccountId,
436    ) -> Result<AccountInputs, TransactionInputsExtractionError> {
437        if account_id == self.account().id() {
438            return Err(TransactionInputsExtractionError::AccountNotForeign);
439        }
440
441        // Read the account header elements from the advice map.
442        let account_id_key = AccountIdKey::from(account_id);
443        let header_elements = self
444            .advice_inputs
445            .map()
446            .get(&account_id_key.as_word())
447            .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
448
449        // Parse the header from elements.
450        let header = AccountHeader::try_from_elements(header_elements)?;
451
452        // Construct and return account inputs.
453        let partial_account = self.read_foreign_partial_account(&header)?;
454        let witness = self.read_foreign_account_witness(&header)?;
455        Ok(AccountInputs::new(partial_account, witness))
456    }
457
458    /// Reads a foreign partial account from the advice inputs based on the account ID corresponding
459    /// to the provided header.
460    fn read_foreign_partial_account(
461        &self,
462        header: &AccountHeader,
463    ) -> Result<PartialAccount, TransactionInputsExtractionError> {
464        // Derive the partial vault from the header.
465        let partial_vault = PartialVault::new(header.vault_root());
466
467        // Find the corresponding foreign account code.
468        let account_code = self
469            .foreign_account_code
470            .iter()
471            .find(|code| code.commitment() == header.code_commitment())
472            .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
473            .clone();
474
475        // Try to get storage header from advice map using storage commitment as key.
476        let storage_header_elements = self
477            .advice_inputs
478            .map()
479            .get(&header.storage_commitment())
480            .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
481
482        // Get slot names for this foreign account, or use empty map if not available.
483        let storage_header = AccountStorageHeader::try_from_elements(
484            storage_header_elements,
485            self.foreign_account_slot_names(),
486        )?;
487
488        // Build partial storage.
489        let partial_storage = PartialStorage::new(storage_header, [])?;
490
491        // Create the partial account.
492        let partial_account = PartialAccount::new(
493            header.id(),
494            header.nonce(),
495            account_code,
496            partial_storage,
497            partial_vault,
498            None, // We know that foreign accounts are existing accounts so a seed is not required.
499        )?;
500
501        Ok(partial_account)
502    }
503
504    /// Reads a foreign account witness from the advice inputs based on the account ID corresponding
505    /// to the provided header.
506    fn read_foreign_account_witness(
507        &self,
508        header: &AccountHeader,
509    ) -> Result<AccountWitness, TransactionInputsExtractionError> {
510        // Get the account tree root from the block header.
511        let account_tree_root = self.block_header.account_root();
512        let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
513
514        // Get the Merkle path from the merkle store.
515        let merkle_path = self.advice_inputs.store().get_path(account_tree_root, leaf_index)?;
516
517        // Convert the Merkle path to SparseMerklePath.
518        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
519
520        // Create the account witness.
521        let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
522
523        Ok(witness)
524    }
525
526    // CONVERSIONS
527    // --------------------------------------------------------------------------------------------
528
529    /// Consumes these transaction inputs and returns their underlying components.
530    pub fn into_parts(
531        self,
532    ) -> (
533        PartialAccount,
534        BlockHeader,
535        PartialBlockchain,
536        InputNotes<InputNote>,
537        TransactionArgs,
538    ) {
539        (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
540    }
541
542    // HELPER METHODS
543    // --------------------------------------------------------------------------------------------
544
545    /// Replaces the current tx_args with the provided value.
546    ///
547    /// This also appends advice inputs from these transaction inputs to the advice inputs of the
548    /// tx args.
549    fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
550        self.tx_args = tx_args;
551        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
552    }
553}
554
555// SERIALIZATION / DESERIALIZATION
556// ================================================================================================
557
558impl Serializable for TransactionInputs {
559    fn write_into<W: ByteWriter>(&self, target: &mut W) {
560        self.account.write_into(target);
561        self.block_header.write_into(target);
562        self.protocol_config.write_into(target);
563        self.blockchain.write_into(target);
564        self.input_notes.write_into(target);
565        self.tx_args.write_into(target);
566        self.advice_inputs.write_into(target);
567        self.foreign_account_code.write_into(target);
568        self.foreign_account_slot_names.write_into(target);
569    }
570}
571
572impl Deserializable for TransactionInputs {
573    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
574        let account = PartialAccount::read_from(source)?;
575        let block_header = BlockHeader::read_from(source)?;
576        let protocol_config = ProtocolConfig::read_from(source)?;
577        let blockchain = PartialBlockchain::read_from(source)?;
578        let input_notes = InputNotes::read_from(source)?;
579        let tx_args = TransactionArgs::read_from(source)?;
580        let advice_inputs = AdviceInputs::read_from(source)?;
581        let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
582        let foreign_account_slot_names =
583            BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
584
585        Ok(TransactionInputs {
586            account,
587            block_header,
588            protocol_config,
589            blockchain,
590            input_notes,
591            tx_args,
592            advice_inputs,
593            foreign_account_code,
594            foreign_account_slot_names,
595        })
596    }
597}
598
599// HELPER FUNCTIONS
600// ================================================================================================
601
602/// Validates whether the provided note belongs to the note tree of the specified block.
603fn validate_is_in_block(
604    note: &Note,
605    proof: &NoteInclusionProof,
606    block_header: &BlockHeader,
607) -> Result<(), TransactionInputError> {
608    let note_index = proof.location().block_note_tree_index().into();
609    let note_id = note.id().as_word();
610    proof
611        .note_path()
612        .verify(note_index, note_id, &block_header.note_root())
613        .map_err(|_| {
614            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
615        })
616}