miden_protocol/transaction/inputs/
mod.rs1use 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#[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 foreign_account_slot_names: BTreeMap<StorageSlotId, StorageSlotName>,
64}
65
66impl TransactionInputs {
67 pub fn new(
78 account: PartialAccount,
79 block_header: BlockHeader,
80 blockchain: PartialBlockchain,
81 input_notes: InputNotes<InputNote>,
82 ) -> Result<Self, TransactionInputError> {
83 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 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 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 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 pub fn with_tx_args(mut self, tx_args: TransactionArgs) -> Self {
145 self.set_tx_args_inner(tx_args);
146 self
147 }
148
149 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 pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
160 self.set_advice_inputs(advice_inputs);
161 self
162 }
163
164 pub fn set_input_notes(&mut self, new_notes: Vec<Note>) {
169 self.input_notes = new_notes.into();
170 }
171
172 pub fn set_advice_inputs(&mut self, new_advice_inputs: AdviceInputs) {
176 let AdviceInputs { map, store, .. } = new_advice_inputs;
177 self.advice_inputs = AdviceInputs { stack: Default::default(), map, store };
178 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
179 }
180
181 #[cfg(feature = "testing")]
183 pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
184 self.set_tx_args_inner(tx_args);
185 }
186
187 pub fn account(&self) -> &PartialAccount {
192 &self.account
193 }
194
195 pub fn block_header(&self) -> &BlockHeader {
197 &self.block_header
198 }
199
200 pub fn blockchain(&self) -> &PartialBlockchain {
203 &self.blockchain
204 }
205
206 pub fn input_notes(&self) -> &InputNotes<InputNote> {
208 &self.input_notes
209 }
210
211 pub fn ref_block(&self) -> BlockNumber {
213 self.block_header.block_num()
214 }
215
216 pub fn tx_script(&self) -> Option<&TransactionScript> {
218 self.tx_args.tx_script()
219 }
220
221 pub fn foreign_account_code(&self) -> &[AccountCode] {
223 &self.foreign_account_code
224 }
225
226 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
228 &self.foreign_account_slot_names
229 }
230
231 pub fn advice_inputs(&self) -> &AdviceInputs {
233 &self.advice_inputs
234 }
235
236 pub fn tx_args(&self) -> &TransactionArgs {
238 &self.tx_args
239 }
240
241 pub fn read_storage_map_witness(
246 &self,
247 map_root: Word,
248 map_key: StorageMapKey,
249 ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
250 let leaf_index = map_key.hash().to_leaf_index();
252
253 let merkle_path = self.advice_inputs.store.get_path(map_root, leaf_index.into())?;
255 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
256
257 let merkle_node = self.advice_inputs.store.get_node(map_root, leaf_index.into())?;
259 let smt_leaf_elements = self
260 .advice_inputs
261 .map
262 .get(&merkle_node)
263 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
264 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, leaf_index)?;
265
266 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
268 let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
269
270 Ok(storage_witness)
271 }
272
273 pub fn read_vault_asset_witnesses(
281 &self,
282 vault_root: Word,
283 asset_ids: BTreeSet<AssetId>,
284 ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
285 let mut asset_witnesses = Vec::new();
286 for asset_id in asset_ids {
287 let smt_index = asset_id.hash().to_leaf_index();
288 let merkle_path = self.advice_inputs.store.get_path(vault_root, smt_index.into())?;
290 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
291
292 let merkle_node = self.advice_inputs.store.get_node(vault_root, smt_index.into())?;
294 let smt_leaf_elements = self
295 .advice_inputs
296 .map
297 .get(&merkle_node)
298 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
299 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
300
301 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
303 let asset_witness = AssetWitness::new(smt_proof, [asset_id])?;
304 asset_witnesses.push(asset_witness);
305 }
306 Ok(asset_witnesses)
307 }
308
309 pub fn has_vault_asset_witness(&self, vault_root: Word, asset_id: &AssetId) -> bool {
314 let smt_index: NodeIndex = asset_id.hash().to_leaf_index().into();
315
316 if !self.advice_inputs.store.has_path(vault_root, smt_index) {
318 return false;
319 }
320
321 match self.advice_inputs.store.get_node(vault_root, smt_index) {
323 Ok(node) => self.advice_inputs.map.contains_key(&node),
324 Err(_) => false,
325 }
326 }
327
328 pub fn read_vault_asset(
338 &self,
339 vault_root: Word,
340 asset_id: AssetId,
341 ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
342 let witnesses =
343 self.read_vault_asset_witnesses(vault_root, BTreeSet::from_iter([asset_id]))?;
344 let witness = witnesses
345 .into_iter()
346 .next()
347 .expect("one key requested should yield exactly one witness");
348 Ok(witness.find(asset_id))
349 }
350
351 pub fn read_foreign_account_inputs(
358 &self,
359 account_id: AccountId,
360 ) -> Result<AccountInputs, TransactionInputsExtractionError> {
361 if account_id == self.account().id() {
362 return Err(TransactionInputsExtractionError::AccountNotForeign);
363 }
364
365 let account_id_key = AccountIdKey::from(account_id);
367 let header_elements = self
368 .advice_inputs
369 .map
370 .get(&account_id_key.as_word())
371 .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
372
373 let header = AccountHeader::try_from_elements(header_elements)?;
375
376 let partial_account = self.read_foreign_partial_account(&header)?;
378 let witness = self.read_foreign_account_witness(&header)?;
379 Ok(AccountInputs::new(partial_account, witness))
380 }
381
382 fn read_foreign_partial_account(
385 &self,
386 header: &AccountHeader,
387 ) -> Result<PartialAccount, TransactionInputsExtractionError> {
388 let partial_vault = PartialVault::new(header.vault_root());
390
391 let account_code = self
393 .foreign_account_code
394 .iter()
395 .find(|code| code.commitment() == header.code_commitment())
396 .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
397 .clone();
398
399 let storage_header_elements = self
401 .advice_inputs
402 .map
403 .get(&header.storage_commitment())
404 .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
405
406 let storage_header = AccountStorageHeader::try_from_elements(
408 storage_header_elements,
409 self.foreign_account_slot_names(),
410 )?;
411
412 let partial_storage = PartialStorage::new(storage_header, [])?;
414
415 let partial_account = PartialAccount::new(
417 header.id(),
418 header.nonce(),
419 account_code,
420 partial_storage,
421 partial_vault,
422 None, )?;
424
425 Ok(partial_account)
426 }
427
428 fn read_foreign_account_witness(
431 &self,
432 header: &AccountHeader,
433 ) -> Result<AccountWitness, TransactionInputsExtractionError> {
434 let account_tree_root = self.block_header.account_root();
436 let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
437
438 let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
440
441 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
443
444 let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
446
447 Ok(witness)
448 }
449
450 pub fn into_parts(
455 self,
456 ) -> (
457 PartialAccount,
458 BlockHeader,
459 PartialBlockchain,
460 InputNotes<InputNote>,
461 TransactionArgs,
462 ) {
463 (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
464 }
465
466 fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
474 self.tx_args = tx_args;
475 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
476 }
477}
478
479impl Serializable for TransactionInputs {
483 fn write_into<W: ByteWriter>(&self, target: &mut W) {
484 self.account.write_into(target);
485 self.block_header.write_into(target);
486 self.blockchain.write_into(target);
487 self.input_notes.write_into(target);
488 self.tx_args.write_into(target);
489 self.advice_inputs.write_into(target);
490 self.foreign_account_code.write_into(target);
491 self.foreign_account_slot_names.write_into(target);
492 }
493}
494
495impl Deserializable for TransactionInputs {
496 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
497 let account = PartialAccount::read_from(source)?;
498 let block_header = BlockHeader::read_from(source)?;
499 let blockchain = PartialBlockchain::read_from(source)?;
500 let input_notes = InputNotes::read_from(source)?;
501 let tx_args = TransactionArgs::read_from(source)?;
502 let advice_inputs = AdviceInputs::read_from(source)?;
503 let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
504 let foreign_account_slot_names =
505 BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
506
507 Ok(TransactionInputs {
508 account,
509 block_header,
510 blockchain,
511 input_notes,
512 tx_args,
513 advice_inputs,
514 foreign_account_code,
515 foreign_account_slot_names,
516 })
517 }
518}
519
520fn validate_is_in_block(
525 note: &Note,
526 proof: &NoteInclusionProof,
527 block_header: &BlockHeader,
528) -> Result<(), TransactionInputError> {
529 let note_index = proof.location().block_note_tree_index().into();
530 let note_id = note.id().as_word();
531 proof
532 .note_path()
533 .verify(note_index, note_id, &block_header.note_root())
534 .map_err(|_| {
535 TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
536 })
537}