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#[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 asset_witnesses: Vec<AssetWitness>,
56 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
58}
59
60impl TransactionInputs {
61 pub fn new(
72 account: PartialAccount,
73 block_header: BlockHeader,
74 blockchain: PartialBlockchain,
75 input_notes: InputNotes<InputNote>,
76 ) -> Result<Self, TransactionInputError> {
77 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 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 pub fn with_asset_witnesses(mut self, witnesses: Vec<AssetWitness>) -> Self {
120 self.asset_witnesses = witnesses;
121 self
122 }
123
124 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 pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
132 self.set_tx_args_inner(tx_args);
133 self
134 }
135
136 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 pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
147 self.set_advice_inputs(advice_inputs);
148 self
149 }
150
151 pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
156 self.input_notes = new_notes.into();
157 }
158
159 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 #[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 pub fn account(&self) -> &PartialAccount {
179 &self.account
180 }
181
182 pub fn block_header(&self) -> &BlockHeader {
184 &self.block_header
185 }
186
187 pub fn blockchain(&self) -> &PartialBlockchain {
190 &self.blockchain
191 }
192
193 pub fn input_notes(&self) -> &InputNotes<InputNote> {
195 &self.input_notes
196 }
197
198 pub fn ref_block(&self) -> BlockNumber {
200 self.block_header.block_num()
201 }
202
203 pub fn tx_script(&self) -> Option<&TransactionScript> {
205 self.tx_args.tx_script()
206 }
207
208 pub fn foreign_account_code(&self) -> &[AccountCode] {
210 &self.foreign_account_code
211 }
212
213 pub fn asset_witnesses(&self) -> &[AssetWitness] {
215 &self.asset_witnesses
216 }
217
218 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
220 &self.foreign_account_slot_names
221 }
222
223 pub fn advice_inputs(&self) -> &AdviceInputs {
225 &self.advice_inputs
226 }
227
228 pub fn tx_args(&self) -> &TransactionArgs {
230 &self.tx_args
231 }
232
233 pub fn read_storage_map_witness(
238 &self,
239 map_root: Word,
240 map_key: Word,
241 ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
242 let leaf_index = StorageMap::map_key_to_leaf_index(map_key);
244
245 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 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 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 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 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 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 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 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 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 let header = AccountHeader::try_from_elements(header_elements)?;
319
320 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 fn read_foreign_partial_account(
329 &self,
330 header: &AccountHeader,
331 ) -> Result<PartialAccount, TransactionInputsExtractionError> {
332 let partial_vault = PartialVault::new(header.vault_root());
334
335 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 let storage_header_elements = self
345 .advice_inputs
346 .map
347 .get(&header.storage_commitment())
348 .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
349
350 let storage_header = AccountStorageHeader::try_from_elements(
352 storage_header_elements,
353 self.foreign_account_slot_names(),
354 )?;
355
356 let partial_storage = PartialStorage::new(storage_header, [])?;
358
359 let partial_account = PartialAccount::new(
361 header.id(),
362 header.nonce(),
363 account_code,
364 partial_storage,
365 partial_vault,
366 None, )?;
368
369 Ok(partial_account)
370 }
371
372 fn read_foreign_account_witness(
375 &self,
376 header: &AccountHeader,
377 ) -> Result<AccountWitness, TransactionInputsExtractionError> {
378 let account_tree_root = self.block_header.account_root();
380 let leaf_index: NodeIndex = account_id_to_smt_index(header.id()).into();
381
382 let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
384
385 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
387
388 let witness = AccountWitness::new(header.id(), header.commitment(), sparse_path)?;
390
391 Ok(witness)
392 }
393
394 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 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
423impl 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
469pub 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 if elements.is_empty() {
482 return Ok(SmtLeaf::new_empty(leaf_index));
483 }
484
485 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 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 let mut entries = Vec::with_capacity(num_entries);
502 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
524fn 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}