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::{Library, 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, Program};
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 new(code: Program) -> Self {
351 Self::from_parts(code.mast_forest().clone(), code.entrypoint())
352 }
353
354 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
359 assert!(mast.get_node_by_id(entrypoint).is_some());
360
361 Self {
362 mast,
363 entrypoint,
364 package_debug_info: None,
365 }
366 }
367
368 pub fn from_package(package: &Package) -> Result<Self, TransactionScriptError> {
376 let program =
377 package.try_into_program().map_err(TransactionScriptError::PackageNotProgram)?;
378
379 Ok(Self {
380 mast: program.mast_forest().clone(),
381 entrypoint: program.entrypoint(),
382 package_debug_info: package_debug_info(package),
383 })
384 }
385
386 pub fn from_library(library: &Library) -> Result<Self, TransactionScriptError> {
396 let mut entrypoint = None;
397
398 for export in library.manifest.exports() {
399 if let Some(proc_export) = export.as_procedure()
400 && proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE)
401 {
402 if entrypoint.is_some() {
403 return Err(TransactionScriptError::MultipleProceduresWithAttribute);
404 }
405 entrypoint =
406 Some(proc_export.node.ok_or(TransactionScriptError::NoProcedureWithAttribute)?);
407 }
408 }
409
410 let entrypoint = entrypoint.ok_or(TransactionScriptError::NoProcedureWithAttribute)?;
411
412 Ok(Self {
413 mast: library.mast_forest().clone(),
414 entrypoint,
415 package_debug_info: package_debug_info(library),
416 })
417 }
418
419 pub fn from_library_reference(
437 library: &Library,
438 path: &Path,
439 ) -> Result<Self, TransactionScriptError> {
440 let export =
442 library.manifest.exports().find(|e| e.path().as_ref() == path).ok_or_else(|| {
443 TransactionScriptError::ProcedureNotFound(path.to_string().into())
444 })?;
445
446 let proc_export = export
448 .as_procedure()
449 .ok_or_else(|| TransactionScriptError::ProcedureNotFound(path.to_string().into()))?;
450
451 if !proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE) {
452 return Err(TransactionScriptError::ProcedureMissingAttribute(path.to_string().into()));
453 }
454
455 let digest = proc_export.digest;
457
458 let (mast, entrypoint) = create_external_node_forest(digest);
460
461 Ok(Self {
462 mast: Arc::new(mast),
463 entrypoint,
464 package_debug_info: package_debug_info(library),
465 })
466 }
467
468 pub fn mast(&self) -> Arc<MastForest> {
473 self.mast.clone()
474 }
475
476 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
478 loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
479 }
480
481 pub fn root(&self) -> TransactionScriptRoot {
483 TransactionScriptRoot::from_raw(self.mast[self.entrypoint].digest())
484 }
485
486 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
492 if advice_map.is_empty() {
493 return self;
494 }
495
496 let mast = (*self.mast).clone().with_advice_map(advice_map);
497 Self {
498 mast: Arc::new(mast),
499 entrypoint: self.entrypoint,
500 package_debug_info: self.package_debug_info,
501 }
502 }
503}
504
505impl PartialEq for TransactionScript {
506 fn eq(&self, other: &Self) -> bool {
507 self.mast == other.mast && self.entrypoint == other.entrypoint
508 }
509}
510
511impl Eq for TransactionScript {}
512
513impl Serializable for TransactionScript {
517 fn write_into<W: ByteWriter>(&self, target: &mut W) {
518 self.mast.write_into(target);
519 target.write_u32(u32::from(self.entrypoint));
520 }
521}
522
523impl Deserializable for TransactionScript {
524 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
525 let mast = MastForest::read_from(source)?;
526 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
527
528 Ok(Self::from_parts(Arc::new(mast), entrypoint))
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use miden_core::advice::AdviceMap;
535
536 use crate::transaction::TransactionArgs;
537 use crate::utils::serde::{Deserializable, Serializable};
538
539 #[test]
540 fn test_tx_args_serialization() {
541 let tx_args = TransactionArgs::new(AdviceMap::default());
542 let bytes: std::vec::Vec<u8> = tx_args.to_bytes();
543 let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
544
545 assert_eq!(tx_args, decoded);
546 }
547
548 #[test]
549 fn test_transaction_script_preserves_package_debug_info() {
550 use super::TransactionScript;
551 use crate::assembly::Assembler;
552
553 let assembler = Assembler::default();
554 let package =
555 assembler.assemble_program("test-transaction-script", "begin nop end").unwrap();
556 let script = TransactionScript::from_package(&package).unwrap();
557
558 assert!(script.loaded_mast_forest().package_debug_info().unwrap().is_some());
559 }
560
561 #[test]
562 fn test_transaction_script_with_advice_map() {
563 use miden_core::{Felt, Word};
564
565 use super::TransactionScript;
566 use crate::assembly::Assembler;
567
568 let assembler = Assembler::default();
569 let program = assembler
570 .assemble_program("test-transaction-script", "begin nop end")
571 .unwrap()
572 .try_into_program()
573 .unwrap();
574 let script = TransactionScript::new(program);
575
576 assert!(script.mast().advice_map().is_empty());
577
578 let original_root = script.root();
580 let script = script.with_advice_map(AdviceMap::default());
581 assert_eq!(original_root, script.root());
582
583 let key = Word::from([1u32, 2, 3, 4]);
585 let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
586 let mut advice_map = AdviceMap::default();
587 advice_map.insert(key, value.clone());
588
589 let script = script.with_advice_map(advice_map);
590
591 let mast = script.mast();
592 let stored = mast.advice_map().get(&key).expect("entry should be present");
593 assert_eq!(stored.as_ref(), value.as_slice());
594 }
595
596 #[test]
597 fn test_transaction_script_from_library() {
598 use assert_matches::assert_matches;
599
600 use super::TransactionScript;
601 use crate::errors::TransactionScriptError;
602 use crate::testing::assembler::assemble_test_library;
603 use crate::utils::serde::{Deserializable, Serializable};
604
605 let source = "
606 @transaction_script
607 pub proc main
608 push.1 drop
609 end
610 ";
611 let library = assemble_test_library("test-tx-script", "test::tx_script", source);
612
613 let script = TransactionScript::from_library(&library).unwrap();
614
615 let bytes = script.to_bytes();
617 let decoded = TransactionScript::read_from_bytes(&bytes).unwrap();
618 assert_eq!(script, decoded);
619
620 let no_attr = assemble_test_library(
622 "test-tx-script-no-attr",
623 "test::tx_script_no_attr",
624 "pub proc main push.1 drop end",
625 );
626 assert_matches!(
627 TransactionScript::from_library(&no_attr),
628 Err(TransactionScriptError::NoProcedureWithAttribute)
629 );
630
631 let multiple = assemble_test_library(
633 "test-tx-script-multiple",
634 "test::tx_script_multiple",
635 "@transaction_script pub proc main_a push.1 drop end
636 @transaction_script pub proc main_b push.2 drop end",
637 );
638 assert_matches!(
639 TransactionScript::from_library(&multiple),
640 Err(TransactionScriptError::MultipleProceduresWithAttribute)
641 );
642 }
643
644 #[test]
645 fn test_transaction_script_from_library_reference() {
646 use alloc::string::ToString;
647
648 use assert_matches::assert_matches;
649
650 use super::TransactionScript;
651 use crate::Word;
652 use crate::assembly::Path;
653 use crate::errors::TransactionScriptError;
654 use crate::testing::assembler::assemble_test_library;
655
656 let source = "
657 @transaction_script
658 pub proc main_a
659 push.1 drop
660 end
661
662 @transaction_script
663 pub proc main_b
664 push.2 drop
665 end
666
667 pub proc helper
668 push.3 drop
669 end
670 ";
671 let library =
672 assemble_test_library("test-tx-script-reference", "test::tx_script_reference", source);
673
674 for proc_name in ["main_a", "main_b"] {
677 let export = library
678 .manifest
679 .exports()
680 .find(|e| e.path().as_ref().to_string().ends_with(proc_name))
681 .unwrap();
682 let digest = export.as_procedure().unwrap().digest;
683
684 let script =
685 TransactionScript::from_library_reference(&library, export.path().as_ref())
686 .unwrap();
687 assert_eq!(Word::from(script.root()), digest);
688 }
689
690 assert_matches!(
692 TransactionScript::from_library_reference(&library, Path::new("::foo::bar::main")),
693 Err(TransactionScriptError::ProcedureNotFound(_))
694 );
695
696 let helper = library
698 .manifest
699 .exports()
700 .find(|e| e.path().as_ref().to_string().ends_with("helper"))
701 .unwrap();
702 assert_matches!(
703 TransactionScript::from_library_reference(&library, helper.path().as_ref()),
704 Err(TransactionScriptError::ProcedureMissingAttribute(_))
705 );
706 }
707}