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::NodeIndex;
7use miden_crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof};
8
9use super::PartialBlockchain;
10use crate::account::{
11    AccountCode,
12    AccountHeader,
13    AccountId,
14    AccountStorageHeader,
15    PartialAccount,
16    PartialStorage,
17    StorageMap,
18    StorageMapWitness,
19    StorageSlotId,
20    StorageSlotName,
21};
22use crate::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    /// Pre-fetched asset witnesses for note assets and the fee asset.
55    asset_witnesses: Vec<AssetWitness>,
56    /// Storage slot names for foreign accounts.
57    foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
58}
59
60impl TransactionInputs {
61    // CONSTRUCTOR
62    // --------------------------------------------------------------------------------------------
63
64    /// Returns new [`TransactionInputs`] instantiated with the specified parameters.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if:
69    /// - The partial blockchain does not track the block headers required to prove inclusion of any
70    ///   authenticated input note.
71    pub fn new(
72        account: PartialAccount,
73        block_header: BlockHeader,
74        blockchain: PartialBlockchain,
75        input_notes: InputNotes<InputNote>,
76    ) -> Result<Self, TransactionInputError> {
77        // Check that the partial blockchain and block header are consistent.
78        if blockchain.chain_length() != block_header.block_num() {
79            return Err(TransactionInputError::InconsistentChainLength {
80                expected: block_header.block_num(),
81                actual: blockchain.chain_length(),
82            });
83        }
84        if blockchain.peaks().hash_peaks() != block_header.chain_commitment() {
85            return Err(TransactionInputError::InconsistentChainCommitment {
86                expected: block_header.chain_commitment(),
87                actual: blockchain.peaks().hash_peaks(),
88            });
89        }
90        // Validate the authentication paths of the input notes.
91        for note in input_notes.iter() {
92            if let InputNote::Authenticated { note, proof } = note {
93                let note_block_num = proof.location().block_num();
94                let block_header = if note_block_num == block_header.block_num() {
95                    &block_header
96                } else {
97                    blockchain.get_block(note_block_num).ok_or(
98                        TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
99                    )?
100                };
101                validate_is_in_block(note, proof, block_header)?;
102            }
103        }
104
105        Ok(Self {
106            account,
107            block_header,
108            blockchain,
109            input_notes,
110            tx_args: TransactionArgs::default(),
111            advice_inputs: AdviceInputs::default(),
112            foreign_account_code: Vec::new(),
113            asset_witnesses: Vec::new(),
114            foreign_account_slot_names: BTreeMap::new(),
115        })
116    }
117
118    /// Replaces the transaction inputs and assigns the given asset witnesses.
119    pub fn with_asset_witnesses(mut self, witnesses: Vec<AssetWitness>) -> Self {
120        self.asset_witnesses = witnesses;
121        self
122    }
123
124    /// Replaces the transaction inputs and assigns the given foreign account code.
125    pub fn with_foreign_account_code(mut self, foreign_account_code: Vec<AccountCode>) -> Self {
126        self.foreign_account_code = foreign_account_code;
127        self
128    }
129
130    /// Replaces the transaction inputs and assigns the given transaction arguments.
131    pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
132        self.set_tx_args_inner(tx_args);
133        self
134    }
135
136    /// Replaces the transaction inputs and assigns the given foreign account slot names.
137    pub fn with_foreign_account_slot_names(
138        mut self,
139        foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
140    ) -> Self {
141        self.foreign_account_slot_names = foreign_account_slot_names;
142        self
143    }
144
145    /// Replaces the transaction inputs and assigns the given advice inputs.
146    pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
147        self.set_advice_inputs(advice_inputs);
148        self
149    }
150
151    // MUTATORS
152    // --------------------------------------------------------------------------------------------
153
154    /// Replaces the input notes for the transaction.
155    pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
156        self.input_notes = new_notes.into();
157    }
158
159    /// Replaces the advice inputs for the transaction.
160    ///
161    /// Note: the advice stack from the provided advice inputs is discarded.
162    pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
163        let AdviceInputs { map, store, .. } = new_advice_inputs;
164        self.advice_inputs = AdviceInputs { stack: Default::default(), map, store };
165        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
166    }
167
168    /// Updates the transaction arguments of the inputs.
169    #[cfg(feature = "testing")]
170    pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
171        self.set_tx_args_inner(tx_args);
172    }
173
174    // PUBLIC ACCESSORS
175    // --------------------------------------------------------------------------------------------
176
177    /// Returns the account against which the transaction is executed.
178    pub fn account(&self) -> &PartialAccount {
179        &self.account
180    }
181
182    /// Returns block header for the block referenced by the transaction.
183    pub fn block_header(&self) -> &BlockHeader {
184        &self.block_header
185    }
186
187    /// Returns partial blockchain containing authentication paths for all notes consumed by the
188    /// transaction.
189    pub fn blockchain(&self) -> &PartialBlockchain {
190        &self.blockchain
191    }
192
193    /// Returns the notes to be consumed in the transaction.
194    pub fn input_notes(&self) -> &InputNotes<InputNote> {
195        &self.input_notes
196    }
197
198    /// Returns the block number referenced by the inputs.
199    pub fn ref_block(&self) -> BlockNumber {
200        self.block_header.block_num()
201    }
202
203    /// Returns the transaction script to be executed.
204    pub fn tx_script(&self) -> Option<&TransactionScript> {
205        self.tx_args.tx_script()
206    }
207
208    /// Returns the foreign account code to be executed.
209    pub fn foreign_account_code(&self) -> &[AccountCode] {
210        &self.foreign_account_code
211    }
212
213    /// Returns the pre-fetched witnesses for note and fee assets.
214    pub fn asset_witnesses(&self) -> &[AssetWitness] {
215        &self.asset_witnesses
216    }
217
218    /// Returns the foreign account storage slot names.
219    pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
220        &self.foreign_account_slot_names
221    }
222
223    /// Returns the advice inputs to be consumed in the transaction.
224    pub fn advice_inputs(&self) -> &AdviceInputs {
225        &self.advice_inputs
226    }
227
228    /// Returns the transaction arguments to be consumed in the transaction.
229    pub fn tx_args(&self) -> &TransactionArgs {
230        &self.tx_args
231    }
232
233    // DATA EXTRACTORS
234    // --------------------------------------------------------------------------------------------
235
236    /// Reads the storage map witness for the given account and map key.
237    pub fn read_storage_map_witness(
238        &self,
239        map_root: Word,
240        map_key: Word,
241    ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
242        // Convert map key into the index at which the key-value pair for this key is stored
243        let leaf_index = StorageMap::map_key_to_leaf_index(map_key);
244
245        // Construct sparse Merkle path.
246        let merkle_path = self.advice_inputs.store.get_path(map_root, leaf_index.into())?;
247        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
248
249        // Construct SMT leaf.
250        let merkle_node = self.advice_inputs.store.get_node(map_root, leaf_index.into())?;
251        let smt_leaf_elements = self
252            .advice_inputs
253            .map
254            .get(&merkle_node)
255            .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
256        let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, leaf_index)?;
257
258        // Construct SMT proof and witness.
259        let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
260        let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
261
262        Ok(storage_witness)
263    }
264
265    /// Reads the vault asset witnesses for the given account and vault keys.
266    pub fn read_vault_asset_witnesses(
267        &self,
268        vault_root: Word,
269        vault_keys: BTreeSet<AssetVaultKey>,
270    ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
271        let mut asset_witnesses = Vec::new();
272        for vault_key in vault_keys {
273            let smt_index = vault_key.to_leaf_index();
274            // Construct sparse Merkle path.
275            let merkle_path = self.advice_inputs.store.get_path(vault_root, smt_index.into())?;
276            let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
277
278            // Construct SMT leaf.
279            let merkle_node = self.advice_inputs.store.get_node(vault_root, smt_index.into())?;
280            let smt_leaf_elements = self
281                .advice_inputs
282                .map
283                .get(&merkle_node)
284                .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
285            let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, smt_index)?;
286
287            // Construct SMT proof and witness.
288            let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
289            let asset_witness = AssetWitness::new(smt_proof)?;
290            asset_witnesses.push(asset_witness);
291        }
292        Ok(asset_witnesses)
293    }
294
295    /// Reads AccountInputs for a foreign account from the advice inputs.
296    ///
297    /// This function reverses the process of [`TransactionAdviceInputs::add_foreign_accounts`] by:
298    /// 1. Reading the account header from the advice map using the account_id_key.
299    /// 2. Building a PartialAccount from the header and foreign account code.
300    /// 3. Creating an AccountWitness.
301    pub fn read_foreign_account_inputs(
302        &self,
303        account_id: AccountId,
304    ) -> Result<AccountInputs, TransactionInputsExtractionError> {
305        if account_id == self.account().id() {
306            return Err(TransactionInputsExtractionError::AccountNotForeign);
307        }
308
309        // Read the account header elements from the advice map.
310        let account_id_key = TransactionAdviceInputs::account_id_map_key(account_id);
311        let header_elements = self
312            .advice_inputs
313            .map
314            .get(&account_id_key)
315            .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
316
317        // Parse the header from elements.
318        let header = AccountHeader::try_from_elements(header_elements)?;
319
320        // Construct and return account inputs.
321        let partial_account = self.read_foreign_partial_account(&header)?;
322        let witness = self.read_foreign_account_witness(&header)?;
323        Ok(AccountInputs::new(partial_account, witness))
324    }
325
326    /// Reads a foreign partial account from the advice inputs based on the account ID corresponding
327    /// to the provided header.
328    fn read_foreign_partial_account(
329        &self,
330        header: &AccountHeader,
331    ) -> Result<PartialAccount, TransactionInputsExtractionError> {
332        // Derive the partial vault from the header.
333        let partial_vault = PartialVault::new(header.vault_root());
334
335        // Find the corresponding foreign account code.
336        let account_code = self
337            .foreign_account_code
338            .iter()
339            .find(|code| code.commitment() == header.code_commitment())
340            .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
341            .clone();
342
343        // Try to get storage header from advice map using storage commitment as key.
344        let storage_header_elements = self
345            .advice_inputs
346            .map
347            .get(&header.storage_commitment())
348            .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
349
350        // Get slot names for this foreign account, or use empty map if not available.
351        let storage_header = AccountStorageHeader::try_from_elements(
352            storage_header_elements,
353            self.foreign_account_slot_names(),
354        )?;
355
356        // Build partial storage.
357        let partial_storage = PartialStorage::new(storage_header, [])?;
358
359        // Create the partial account.
360        let partial_account = PartialAccount::new(
361            header.id(),
362            header.nonce(),
363            account_code,
364            partial_storage,
365            partial_vault,
366            None, // We know that foreign accounts are existing accounts so a seed is not required.
367        )?;
368
369        Ok(partial_account)
370    }
371
372    /// Reads a foreign account witness from the advice inputs based on the account ID corresponding
373    /// to the provided header.
374    fn read_foreign_account_witness(
375        &self,
376        header: &AccountHeader,
377    ) -> Result<AccountWitness, TransactionInputsExtractionError> {
378        // Get the account tree root from the block header.
379        let account_tree_root = self.block_header.account_root();
380        let leaf_index: NodeIndex = account_id_to_smt_index(header.id()).into();
381
382        // Get the Merkle path from the merkle store.
383        let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
384
385        // Convert the Merkle path to SparseMerklePath.
386        let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
387
388        // Create the account witness.
389        let witness = AccountWitness::new(header.id(), header.commitment(), sparse_path)?;
390
391        Ok(witness)
392    }
393
394    // CONVERSIONS
395    // --------------------------------------------------------------------------------------------
396
397    /// Consumes these transaction inputs and returns their underlying components.
398    pub fn into_parts(
399        self,
400    ) -> (
401        PartialAccount,
402        BlockHeader,
403        PartialBlockchain,
404        InputNotes<InputNote>,
405        TransactionArgs,
406    ) {
407        (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
408    }
409
410    // HELPER METHODS
411    // --------------------------------------------------------------------------------------------
412
413    /// Replaces the current tx_args with the provided value.
414    ///
415    /// This also appends advice inputs from these transaction inputs to the advice inputs of the
416    /// tx args.
417    fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
418        self.tx_args = tx_args;
419        self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
420    }
421}
422
423// SERIALIZATION / DESERIALIZATION
424// ================================================================================================
425
426impl Serializable for TransactionInputs {
427    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
428        self.account.write_into(target);
429        self.block_header.write_into(target);
430        self.blockchain.write_into(target);
431        self.input_notes.write_into(target);
432        self.tx_args.write_into(target);
433        self.advice_inputs.write_into(target);
434        self.foreign_account_code.write_into(target);
435        self.asset_witnesses.write_into(target);
436        self.foreign_account_slot_names.write_into(target);
437    }
438}
439
440impl Deserializable for TransactionInputs {
441    fn read_from<R: miden_core::utils::ByteReader>(
442        source: &mut R,
443    ) -> Result<Self, miden_core::utils::DeserializationError> {
444        let account = PartialAccount::read_from(source)?;
445        let block_header = BlockHeader::read_from(source)?;
446        let blockchain = PartialBlockchain::read_from(source)?;
447        let input_notes = InputNotes::read_from(source)?;
448        let tx_args = TransactionArgs::read_from(source)?;
449        let advice_inputs = AdviceInputs::read_from(source)?;
450        let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
451        let asset_witnesses = Vec::<AssetWitness>::read_from(source)?;
452        let foreign_account_slot_names =
453            BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
454
455        Ok(TransactionInputs {
456            account,
457            block_header,
458            blockchain,
459            input_notes,
460            tx_args,
461            advice_inputs,
462            foreign_account_code,
463            asset_witnesses,
464            foreign_account_slot_names,
465        })
466    }
467}
468
469// HELPER FUNCTIONS
470// ================================================================================================
471
472// TODO(sergerad): Move this fn to crypto SmtLeaf::try_from_elements.
473pub fn smt_leaf_from_elements(
474    elements: &[Felt],
475    leaf_index: LeafIndex<SMT_DEPTH>,
476) -> Result<SmtLeaf, TransactionInputsExtractionError> {
477    use miden_crypto::merkle::smt::SmtLeaf;
478
479    // Based on the miden-crypto SMT leaf serialization format.
480
481    if elements.is_empty() {
482        return Ok(SmtLeaf::new_empty(leaf_index));
483    }
484
485    // Elements should be organized into a contiguous array of K/V Words (4 Felts each).
486    if !elements.len().is_multiple_of(8) {
487        return Err(TransactionInputsExtractionError::LeafConversionError(
488            "invalid SMT leaf format: elements length must be divisible by 8".into(),
489        ));
490    }
491
492    let num_entries = elements.len() / 8;
493
494    if num_entries == 1 {
495        // Single entry.
496        let key = Word::new([elements[0], elements[1], elements[2], elements[3]]);
497        let value = Word::new([elements[4], elements[5], elements[6], elements[7]]);
498        Ok(SmtLeaf::new_single(key, value))
499    } else {
500        // Multiple entries.
501        let mut entries = Vec::with_capacity(num_entries);
502        // Read k/v pairs from each entry.
503        for i in 0..num_entries {
504            let base_idx = i * 8;
505            let key = Word::new([
506                elements[base_idx],
507                elements[base_idx + 1],
508                elements[base_idx + 2],
509                elements[base_idx + 3],
510            ]);
511            let value = Word::new([
512                elements[base_idx + 4],
513                elements[base_idx + 5],
514                elements[base_idx + 6],
515                elements[base_idx + 7],
516            ]);
517            entries.push((key, value));
518        }
519        let leaf = SmtLeaf::new_multiple(entries)?;
520        Ok(leaf)
521    }
522}
523
524/// Validates whether the provided note belongs to the note tree of the specified block.
525fn validate_is_in_block(
526    note: &Note,
527    proof: &NoteInclusionProof,
528    block_header: &BlockHeader,
529) -> Result<(), TransactionInputError> {
530    let note_index = proof.location().node_index_in_block().into();
531    let note_commitment = note.commitment();
532    proof
533        .note_path()
534        .verify(note_index, note_commitment, &block_header.note_root())
535        .map_err(|_| {
536            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
537        })
538}