miden_protocol/transaction/
tx_args.rs1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5use core::fmt::Display;
6
7use miden_core::mast::MastNodeExt;
8use miden_crypto::merkle::InnerNodeInfo;
9use miden_crypto_derive::WordWrapper;
10use miden_mast_package::Package;
11use miden_mast_package::debug_info::PackageDebugInfo;
12use miden_processor::LoadedMastForest;
13
14use super::{Felt, Hasher, Word};
15use crate::account::auth::{PublicKeyCommitment, Signature};
16use crate::assembly::Path;
17use crate::errors::TransactionScriptError;
18use crate::note::{NoteId, NoteRecipient};
19use crate::package::{loaded_mast_forest, package_debug_info};
20use crate::utils::create_external_node_forest;
21use crate::utils::serde::{
22 ByteReader,
23 ByteWriter,
24 Deserializable,
25 DeserializationError,
26 Serializable,
27};
28use crate::vm::{AdviceInputs, AdviceMap};
29use crate::{EMPTY_WORD, MastForest, MastNodeId};
30
31#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct TransactionArgs {
54 tx_script: Option<TransactionScript>,
55 tx_script_args: Word,
56 note_args: BTreeMap<NoteId, Word>,
57 advice_inputs: AdviceInputs,
58 auth_args: Word,
59}
60
61impl TransactionArgs {
62 pub fn new(advice_map: AdviceMap) -> Self {
68 let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() };
69
70 Self {
71 tx_script: None,
72 tx_script_args: EMPTY_WORD,
73 note_args: Default::default(),
74 advice_inputs,
75 auth_args: EMPTY_WORD,
76 }
77 }
78
79 #[must_use]
84 pub fn with_tx_script(mut self, tx_script: TransactionScript) -> Self {
85 self.tx_script = Some(tx_script);
86 self
87 }
88
89 #[must_use]
95 pub fn with_tx_script_and_args(
96 mut self,
97 tx_script: TransactionScript,
98 tx_script_args: Word,
99 ) -> Self {
100 self.tx_script = Some(tx_script);
101 self.tx_script_args = tx_script_args;
102 self
103 }
104
105 #[must_use]
110 pub fn with_note_args(mut self, note_args: BTreeMap<NoteId, Word>) -> Self {
111 self.note_args = note_args;
112 self
113 }
114
115 #[must_use]
117 pub fn with_auth_args(mut self, auth_args: Word) -> Self {
118 self.auth_args = auth_args;
119 self
120 }
121
122 pub fn tx_script(&self) -> Option<&TransactionScript> {
127 self.tx_script.as_ref()
128 }
129
130 pub fn tx_script_args(&self) -> Word {
138 self.tx_script_args
139 }
140
141 pub fn get_note_args(&self, note_id: NoteId) -> Option<&Word> {
143 self.note_args.get(¬e_id)
144 }
145
146 pub fn advice_inputs(&self) -> &AdviceInputs {
148 &self.advice_inputs
149 }
150
151 pub fn auth_args(&self) -> Word {
159 self.auth_args
160 }
161
162 pub fn add_output_note_recipient<T: AsRef<NoteRecipient>>(&mut self, note_recipient: T) {
174 let note_recipient = note_recipient.as_ref();
175 let storage = note_recipient.storage();
176 let script = note_recipient.script();
177 let script_encoded: Vec<Felt> = script.into();
178
179 let script_root: Word = script.root().into();
181 let sn_hash = Hasher::merge(&[note_recipient.serial_num(), Word::empty()]);
182 let sn_script_hash = Hasher::merge(&[sn_hash, script_root]);
183
184 let new_elements = vec![
185 (sn_hash, concat_words(note_recipient.serial_num(), Word::empty())),
186 (sn_script_hash, concat_words(sn_hash, script_root)),
187 (note_recipient.digest(), concat_words(sn_script_hash, storage.commitment())),
188 (storage.commitment(), storage.to_elements()),
189 (script_root, script_encoded),
190 ];
191
192 self.advice_inputs.extend(AdviceInputs::default().with_map(new_elements));
193 }
194
195 pub fn add_signature(
201 &mut self,
202 pub_key: PublicKeyCommitment,
203 message: Word,
204 signature: Signature,
205 ) {
206 let pk_word: Word = pub_key.into();
207 self.advice_inputs
208 .map
209 .insert(Hasher::merge(&[pk_word, message]), signature.to_prepared_signature(message));
210 }
211
212 pub fn extend_output_note_recipients<T, L>(&mut self, notes: L)
220 where
221 L: IntoIterator<Item = T>,
222 T: AsRef<NoteRecipient>,
223 {
224 for note in notes {
225 self.add_output_note_recipient(note);
226 }
227 }
228
229 pub fn extend_advice_map<T: IntoIterator<Item = (Word, Vec<Felt>)>>(&mut self, iter: T) {
231 self.advice_inputs.map.extend(iter);
232 }
233
234 pub fn extend_merkle_store<I: Iterator<Item = InnerNodeInfo>>(&mut self, iter: I) {
236 self.advice_inputs.store.extend(iter);
237 }
238
239 pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) {
241 self.advice_inputs.extend(advice_inputs);
242 }
243}
244
245fn concat_words(first: Word, second: Word) -> Vec<Felt> {
247 let mut result = Vec::with_capacity(8);
248 result.extend(first);
249 result.extend(second);
250 result
251}
252
253impl Default for TransactionArgs {
254 fn default() -> Self {
255 Self::new(AdviceMap::default())
256 }
257}
258
259impl Serializable for TransactionArgs {
260 fn write_into<W: ByteWriter>(&self, target: &mut W) {
261 self.tx_script.write_into(target);
262 self.tx_script_args.write_into(target);
263 self.note_args.write_into(target);
264 self.advice_inputs.write_into(target);
265 self.auth_args.write_into(target);
266 }
267}
268
269impl Deserializable for TransactionArgs {
270 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
271 let tx_script = Option::<TransactionScript>::read_from(source)?;
272 let tx_script_args = Word::read_from(source)?;
273 let note_args = BTreeMap::<NoteId, Word>::read_from(source)?;
274 let advice_inputs = AdviceInputs::read_from(source)?;
275 let auth_args = Word::read_from(source)?;
276
277 Ok(Self {
278 tx_script,
279 tx_script_args,
280 note_args,
281 advice_inputs,
282 auth_args,
283 })
284 }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
292pub struct TransactionScriptRoot(Word);
293
294impl From<TransactionScriptRoot> for Word {
295 fn from(root: TransactionScriptRoot) -> Self {
296 root.0
297 }
298}
299
300impl Display for TransactionScriptRoot {
301 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
302 Display::fmt(&self.0, f)
303 }
304}
305
306impl Serializable for TransactionScriptRoot {
307 fn write_into<W: ByteWriter>(&self, target: &mut W) {
308 target.write(self.0);
309 }
310
311 fn get_size_hint(&self) -> usize {
312 self.0.get_size_hint()
313 }
314}
315
316impl Deserializable for TransactionScriptRoot {
317 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
318 let word: Word = source.read()?;
319 Ok(Self::from_raw(word))
320 }
321}
322
323pub const TRANSACTION_SCRIPT_ATTRIBUTE: &str = "transaction_script";
328
329#[derive(Clone, Debug)]
337pub struct TransactionScript {
338 mast: Arc<MastForest>,
339 entrypoint: MastNodeId,
340 package_debug_info: Option<Arc<PackageDebugInfo>>,
341}
342
343impl TransactionScript {
344 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
352 assert!(mast.get_node_by_id(entrypoint).is_some());
353
354 Self {
355 mast,
356 entrypoint,
357 package_debug_info: None,
358 }
359 }
360
361 pub fn from_package(package: &Package) -> Result<Self, TransactionScriptError> {
369 let program =
370 package.try_into_program().map_err(TransactionScriptError::PackageNotProgram)?;
371
372 Ok(Self {
373 mast: program.mast_forest().clone(),
374 entrypoint: program.entrypoint(),
375 package_debug_info: package_debug_info(package),
376 })
377 }
378
379 pub fn from_library(library: &Package) -> Result<Self, TransactionScriptError> {
389 let mut entrypoint = None;
390
391 for export in library.manifest.exports() {
392 if let Some(proc_export) = export.as_procedure()
393 && proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE)
394 {
395 if entrypoint.is_some() {
396 return Err(TransactionScriptError::MultipleProceduresWithAttribute);
397 }
398 entrypoint =
399 Some(proc_export.node.ok_or(TransactionScriptError::NoProcedureWithAttribute)?);
400 }
401 }
402
403 let entrypoint = entrypoint.ok_or(TransactionScriptError::NoProcedureWithAttribute)?;
404
405 Ok(Self {
406 mast: library.mast_forest().clone(),
407 entrypoint,
408 package_debug_info: package_debug_info(library),
409 })
410 }
411
412 pub fn from_library_reference(
430 library: &Package,
431 path: &Path,
432 ) -> Result<Self, TransactionScriptError> {
433 let export =
435 library.manifest.exports().find(|e| e.path().as_ref() == path).ok_or_else(|| {
436 TransactionScriptError::ProcedureNotFound(path.to_string().into())
437 })?;
438
439 let proc_export = export
441 .as_procedure()
442 .ok_or_else(|| TransactionScriptError::ProcedureNotFound(path.to_string().into()))?;
443
444 if !proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE) {
445 return Err(TransactionScriptError::ProcedureMissingAttribute(path.to_string().into()));
446 }
447
448 let digest = proc_export.digest;
450
451 let (mast, entrypoint) = create_external_node_forest(digest);
453
454 Ok(Self {
455 mast: Arc::new(mast),
456 entrypoint,
457 package_debug_info: package_debug_info(library),
458 })
459 }
460
461 pub fn mast(&self) -> Arc<MastForest> {
466 self.mast.clone()
467 }
468
469 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
471 loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
472 }
473
474 pub fn root(&self) -> TransactionScriptRoot {
476 TransactionScriptRoot::from_raw(self.mast[self.entrypoint].digest())
477 }
478
479 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
485 if advice_map.is_empty() {
486 return self;
487 }
488
489 let mast = (*self.mast).clone().with_advice_map(advice_map);
490 Self {
491 mast: Arc::new(mast),
492 entrypoint: self.entrypoint,
493 package_debug_info: self.package_debug_info,
494 }
495 }
496}
497
498impl PartialEq for TransactionScript {
499 fn eq(&self, other: &Self) -> bool {
500 self.mast == other.mast && self.entrypoint == other.entrypoint
501 }
502}
503
504impl Eq for TransactionScript {}
505
506impl Serializable for TransactionScript {
510 fn write_into<W: ByteWriter>(&self, target: &mut W) {
511 self.mast.write_into(target);
512 target.write_u32(u32::from(self.entrypoint));
513 }
514}
515
516impl Deserializable for TransactionScript {
517 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
518 let mast = MastForest::read_from(source)?;
519 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
520
521 Ok(Self::from_parts(Arc::new(mast), entrypoint))
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use miden_core::advice::AdviceMap;
528
529 use crate::transaction::TransactionArgs;
530 use crate::utils::serde::{Deserializable, Serializable};
531
532 #[test]
533 fn test_tx_args_serialization() {
534 let tx_args = TransactionArgs::new(AdviceMap::default());
535 let bytes: std::vec::Vec<u8> = tx_args.to_bytes();
536 let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
537
538 assert_eq!(tx_args, decoded);
539 }
540
541 #[test]
542 fn test_transaction_script_preserves_package_debug_info() {
543 use super::TransactionScript;
544 use crate::assembly::Assembler;
545
546 let assembler = Assembler::default();
547 let package =
548 assembler.assemble_program("test-transaction-script", "begin nop end").unwrap();
549 let script = TransactionScript::from_package(&package).unwrap();
550
551 assert!(script.loaded_mast_forest().package_debug_info().unwrap().is_some());
552 }
553
554 #[test]
555 fn test_transaction_script_with_advice_map() {
556 use miden_core::{Felt, Word};
557
558 use super::TransactionScript;
559 use crate::assembly::Assembler;
560
561 let assembler = Assembler::default();
562 let package =
563 assembler.assemble_program("test-transaction-script", "begin nop end").unwrap();
564 let script = TransactionScript::from_package(&package).unwrap();
565 assert!(script.mast().advice_map().is_empty());
566
567 let original_root = script.root();
569 let script = script.with_advice_map(AdviceMap::default());
570 assert_eq!(original_root, script.root());
571
572 let key = Word::from([1u32, 2, 3, 4]);
574 let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
575 let mut advice_map = AdviceMap::default();
576 advice_map.insert(key, value.clone());
577
578 let script = script.with_advice_map(advice_map);
579
580 let mast = script.mast();
581 let stored = mast.advice_map().get(&key).expect("entry should be present");
582 assert_eq!(stored.as_ref(), value.as_slice());
583 }
584
585 #[test]
586 fn test_transaction_script_from_library() {
587 use assert_matches::assert_matches;
588
589 use super::TransactionScript;
590 use crate::errors::TransactionScriptError;
591 use crate::testing::assembler::assemble_test_library;
592 use crate::utils::serde::{Deserializable, Serializable};
593
594 let source = "
595 @transaction_script
596 pub proc main
597 push.1 drop
598 end
599 ";
600 let library = assemble_test_library("test-tx-script", "test::tx_script", source);
601
602 let script = TransactionScript::from_library(&library).unwrap();
603
604 let bytes = script.to_bytes();
606 let decoded = TransactionScript::read_from_bytes(&bytes).unwrap();
607 assert_eq!(script, decoded);
608
609 let no_attr = assemble_test_library(
611 "test-tx-script-no-attr",
612 "test::tx_script_no_attr",
613 "pub proc main push.1 drop end",
614 );
615 assert_matches!(
616 TransactionScript::from_library(&no_attr),
617 Err(TransactionScriptError::NoProcedureWithAttribute)
618 );
619
620 let multiple = assemble_test_library(
622 "test-tx-script-multiple",
623 "test::tx_script_multiple",
624 "@transaction_script pub proc main_a push.1 drop end
625 @transaction_script pub proc main_b push.2 drop end",
626 );
627 assert_matches!(
628 TransactionScript::from_library(&multiple),
629 Err(TransactionScriptError::MultipleProceduresWithAttribute)
630 );
631 }
632
633 #[test]
634 fn test_transaction_script_from_library_reference() {
635 use alloc::string::ToString;
636
637 use assert_matches::assert_matches;
638
639 use super::TransactionScript;
640 use crate::Word;
641 use crate::assembly::Path;
642 use crate::errors::TransactionScriptError;
643 use crate::testing::assembler::assemble_test_library;
644
645 let source = "
646 @transaction_script
647 pub proc main_a
648 push.1 drop
649 end
650
651 @transaction_script
652 pub proc main_b
653 push.2 drop
654 end
655
656 pub proc helper
657 push.3 drop
658 end
659 ";
660 let library =
661 assemble_test_library("test-tx-script-reference", "test::tx_script_reference", source);
662
663 for proc_name in ["main_a", "main_b"] {
666 let export = library
667 .manifest
668 .exports()
669 .find(|e| e.path().as_ref().to_string().ends_with(proc_name))
670 .unwrap();
671 let digest = export.as_procedure().unwrap().digest;
672
673 let script =
674 TransactionScript::from_library_reference(&library, export.path().as_ref())
675 .unwrap();
676 assert_eq!(Word::from(script.root()), digest);
677 }
678
679 assert_matches!(
681 TransactionScript::from_library_reference(&library, Path::new("::foo::bar::main")),
682 Err(TransactionScriptError::ProcedureNotFound(_))
683 );
684
685 let helper = library
687 .manifest
688 .exports()
689 .find(|e| e.path().as_ref().to_string().ends_with("helper"))
690 .unwrap();
691 assert_matches!(
692 TransactionScript::from_library_reference(&library, helper.path().as_ref()),
693 Err(TransactionScriptError::ProcedureMissingAttribute(_))
694 );
695 }
696}