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 (_stack, map, store) = new_advice_inputs.into_parts();
177 let mut advice_inputs = AdviceInputs::default().with_merkle_store(store);
178 advice_inputs.map = map;
179 self.advice_inputs = advice_inputs;
180 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
181 }
182
183 #[cfg(feature = "testing")]
185 pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
186 self.set_tx_args_inner(tx_args);
187 }
188
189 pub fn account(&self) -> &PartialAccount {
194 &self.account
195 }
196
197 pub fn block_header(&self) -> &BlockHeader {
199 &self.block_header
200 }
201
202 pub fn blockchain(&self) -> &PartialBlockchain {
205 &self.blockchain
206 }
207
208 pub fn input_notes(&self) -> &InputNotes<InputNote> {
210 &self.input_notes
211 }
212
213 pub fn ref_block(&self) -> BlockNumber {
215 self.block_header.block_num()
216 }
217
218 pub fn tx_script(&self) -> Option<&TransactionScript> {
220 self.tx_args.tx_script()
221 }
222
223 pub fn foreign_account_code(&self) -> &[AccountCode] {
225 &self.foreign_account_code
226 }
227
228 pub fn foreign_account_slot_names(&self) -> &BTreeMap<StorageSlotId, StorageSlotName> {
230 &self.foreign_account_slot_names
231 }
232
233 pub fn advice_inputs(&self) -> &AdviceInputs {
235 &self.advice_inputs
236 }
237
238 pub fn tx_args(&self) -> &TransactionArgs {
240 &self.tx_args
241 }
242
243 pub fn read_storage_map_witness(
248 &self,
249 map_root: Word,
250 map_key: StorageMapKey,
251 ) -> Result<StorageMapWitness, TransactionInputsExtractionError> {
252 let leaf_index = map_key.hash().to_leaf_index();
254
255 let merkle_path = self.advice_inputs.store.get_path(map_root, leaf_index.into())?;
257 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
258
259 let merkle_node = self.advice_inputs.store.get_node(map_root, leaf_index.into())?;
261 let smt_leaf_elements = self
262 .advice_inputs
263 .map
264 .get(&merkle_node)
265 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
266 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, leaf_index)?;
267
268 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
270 let storage_witness = StorageMapWitness::new(smt_proof, [map_key])?;
271
272 Ok(storage_witness)
273 }
274
275 pub fn read_vault_asset_witnesses(
283 &self,
284 vault_root: Word,
285 asset_ids: BTreeSet<AssetId>,
286 ) -> Result<Vec<AssetWitness>, TransactionInputsExtractionError> {
287 let mut asset_witnesses = Vec::new();
288 for asset_id in asset_ids {
289 let smt_index = asset_id.hash().to_leaf_index();
290 let merkle_path = self.advice_inputs.store.get_path(vault_root, smt_index.into())?;
292 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
293
294 let merkle_node = self.advice_inputs.store.get_node(vault_root, smt_index.into())?;
296 let smt_leaf_elements = self
297 .advice_inputs
298 .map
299 .get(&merkle_node)
300 .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?;
301 let smt_leaf = SmtLeaf::try_from_elements(smt_leaf_elements, smt_index)?;
302
303 let smt_proof = SmtProof::new(sparse_path, smt_leaf)?;
305 let asset_witness = AssetWitness::new(smt_proof, [asset_id])?;
306 asset_witnesses.push(asset_witness);
307 }
308 Ok(asset_witnesses)
309 }
310
311 pub fn has_vault_asset_witness(&self, vault_root: Word, asset_id: &AssetId) -> bool {
316 let smt_index: NodeIndex = asset_id.hash().to_leaf_index().into();
317
318 if !self.advice_inputs.store.has_path(vault_root, smt_index) {
320 return false;
321 }
322
323 match self.advice_inputs.store.get_node(vault_root, smt_index) {
325 Ok(node) => self.advice_inputs.map.contains_key(&node),
326 Err(_) => false,
327 }
328 }
329
330 pub fn read_vault_asset(
340 &self,
341 vault_root: Word,
342 asset_id: AssetId,
343 ) -> Result<Option<Asset>, TransactionInputsExtractionError> {
344 let witnesses =
345 self.read_vault_asset_witnesses(vault_root, BTreeSet::from_iter([asset_id]))?;
346 let witness = witnesses
347 .into_iter()
348 .next()
349 .expect("one key requested should yield exactly one witness");
350 Ok(witness.find(asset_id))
351 }
352
353 pub fn read_foreign_account_inputs(
360 &self,
361 account_id: AccountId,
362 ) -> Result<AccountInputs, TransactionInputsExtractionError> {
363 if account_id == self.account().id() {
364 return Err(TransactionInputsExtractionError::AccountNotForeign);
365 }
366
367 let account_id_key = AccountIdKey::from(account_id);
369 let header_elements = self
370 .advice_inputs
371 .map
372 .get(&account_id_key.as_word())
373 .ok_or(TransactionInputsExtractionError::ForeignAccountNotFound(account_id))?;
374
375 let header = AccountHeader::try_from_elements(header_elements)?;
377
378 let partial_account = self.read_foreign_partial_account(&header)?;
380 let witness = self.read_foreign_account_witness(&header)?;
381 Ok(AccountInputs::new(partial_account, witness))
382 }
383
384 fn read_foreign_partial_account(
387 &self,
388 header: &AccountHeader,
389 ) -> Result<PartialAccount, TransactionInputsExtractionError> {
390 let partial_vault = PartialVault::new(header.vault_root());
392
393 let account_code = self
395 .foreign_account_code
396 .iter()
397 .find(|code| code.commitment() == header.code_commitment())
398 .ok_or(TransactionInputsExtractionError::ForeignAccountCodeNotFound(header.id()))?
399 .clone();
400
401 let storage_header_elements = self
403 .advice_inputs
404 .map
405 .get(&header.storage_commitment())
406 .ok_or(TransactionInputsExtractionError::StorageHeaderNotFound(header.id()))?;
407
408 let storage_header = AccountStorageHeader::try_from_elements(
410 storage_header_elements,
411 self.foreign_account_slot_names(),
412 )?;
413
414 let partial_storage = PartialStorage::new(storage_header, [])?;
416
417 let partial_account = PartialAccount::new(
419 header.id(),
420 header.nonce(),
421 account_code,
422 partial_storage,
423 partial_vault,
424 None, )?;
426
427 Ok(partial_account)
428 }
429
430 fn read_foreign_account_witness(
433 &self,
434 header: &AccountHeader,
435 ) -> Result<AccountWitness, TransactionInputsExtractionError> {
436 let account_tree_root = self.block_header.account_root();
438 let leaf_index = AccountIdKey::from(header.id()).to_leaf_index().into();
439
440 let merkle_path = self.advice_inputs.store.get_path(account_tree_root, leaf_index)?;
442
443 let sparse_path = SparseMerklePath::from_sized_iter(merkle_path.path)?;
445
446 let witness = AccountWitness::new(header.id(), header.to_commitment(), sparse_path)?;
448
449 Ok(witness)
450 }
451
452 pub fn into_parts(
457 self,
458 ) -> (
459 PartialAccount,
460 BlockHeader,
461 PartialBlockchain,
462 InputNotes<InputNote>,
463 TransactionArgs,
464 ) {
465 (self.account, self.block_header, self.blockchain, self.input_notes, self.tx_args)
466 }
467
468 fn set_tx_args_inner(&mut self, tx_args: TransactionArgs) {
476 self.tx_args = tx_args;
477 self.tx_args.extend_advice_inputs(self.advice_inputs.clone());
478 }
479}
480
481impl Serializable for TransactionInputs {
485 fn write_into<W: ByteWriter>(&self, target: &mut W) {
486 self.account.write_into(target);
487 self.block_header.write_into(target);
488 self.blockchain.write_into(target);
489 self.input_notes.write_into(target);
490 self.tx_args.write_into(target);
491 self.advice_inputs.write_into(target);
492 self.foreign_account_code.write_into(target);
493 self.foreign_account_slot_names.write_into(target);
494 }
495}
496
497impl Deserializable for TransactionInputs {
498 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
499 let account = PartialAccount::read_from(source)?;
500 let block_header = BlockHeader::read_from(source)?;
501 let blockchain = PartialBlockchain::read_from(source)?;
502 let input_notes = InputNotes::read_from(source)?;
503 let tx_args = TransactionArgs::read_from(source)?;
504 let advice_inputs = AdviceInputs::read_from(source)?;
505 let foreign_account_code = Vec::<AccountCode>::read_from(source)?;
506 let foreign_account_slot_names =
507 BTreeMap::<StorageSlotId, StorageSlotName>::read_from(source)?;
508
509 Ok(TransactionInputs {
510 account,
511 block_header,
512 blockchain,
513 input_notes,
514 tx_args,
515 advice_inputs,
516 foreign_account_code,
517 foreign_account_slot_names,
518 })
519 }
520}
521
522fn validate_is_in_block(
527 note: &Note,
528 proof: &NoteInclusionProof,
529 block_header: &BlockHeader,
530) -> Result<(), TransactionInputError> {
531 let note_index = proof.location().block_note_tree_index().into();
532 let note_id = note.id().as_word();
533 proof
534 .note_path()
535 .verify(note_index, note_id, &block_header.note_root())
536 .map_err(|_| {
537 TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
538 })
539}