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 self.advice_inputs.extend(
175 AdviceInputs::default().with_map(note_recipient.as_ref().to_advice_map_entries()),
176 );
177 }
178
179 pub fn add_signature(
185 &mut self,
186 pub_key: PublicKeyCommitment,
187 message: Word,
188 signature: Signature,
189 ) {
190 let pk_word: Word = pub_key.into();
191 self.advice_inputs
192 .map
193 .insert(Hasher::merge(&[pk_word, message]), signature.to_prepared_signature(message));
194 }
195
196 pub fn extend_output_note_recipients<T, L>(&mut self, notes: L)
204 where
205 L: IntoIterator<Item = T>,
206 T: AsRef<NoteRecipient>,
207 {
208 for note in notes {
209 self.add_output_note_recipient(note);
210 }
211 }
212
213 pub fn extend_advice_map<T: IntoIterator<Item = (Word, Vec<Felt>)>>(&mut self, iter: T) {
215 self.advice_inputs.map.extend(iter);
216 }
217
218 pub fn extend_merkle_store<I: Iterator<Item = InnerNodeInfo>>(&mut self, iter: I) {
220 self.advice_inputs.store.extend(iter);
221 }
222
223 pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) {
225 self.advice_inputs.extend(advice_inputs);
226 }
227}
228
229impl Default for TransactionArgs {
231 fn default() -> Self {
232 Self::new(AdviceMap::default())
233 }
234}
235
236impl Serializable for TransactionArgs {
237 fn write_into<W: ByteWriter>(&self, target: &mut W) {
238 self.tx_script.write_into(target);
239 self.tx_script_args.write_into(target);
240 self.note_args.write_into(target);
241 self.advice_inputs.write_into(target);
242 self.auth_args.write_into(target);
243 }
244}
245
246impl Deserializable for TransactionArgs {
247 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
248 let tx_script = Option::<TransactionScript>::read_from(source)?;
249 let tx_script_args = Word::read_from(source)?;
250 let note_args = BTreeMap::<NoteId, Word>::read_from(source)?;
251 let advice_inputs = AdviceInputs::read_from(source)?;
252 let auth_args = Word::read_from(source)?;
253
254 Ok(Self {
255 tx_script,
256 tx_script_args,
257 note_args,
258 advice_inputs,
259 auth_args,
260 })
261 }
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
269pub struct TransactionScriptRoot(Word);
270
271impl From<TransactionScriptRoot> for Word {
272 fn from(root: TransactionScriptRoot) -> Self {
273 root.0
274 }
275}
276
277impl Display for TransactionScriptRoot {
278 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
279 Display::fmt(&self.0, f)
280 }
281}
282
283impl Serializable for TransactionScriptRoot {
284 fn write_into<W: ByteWriter>(&self, target: &mut W) {
285 target.write(self.0);
286 }
287
288 fn get_size_hint(&self) -> usize {
289 self.0.get_size_hint()
290 }
291}
292
293impl Deserializable for TransactionScriptRoot {
294 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
295 let word: Word = source.read()?;
296 Ok(Self::from_raw(word))
297 }
298}
299
300pub const TRANSACTION_SCRIPT_ATTRIBUTE: &str = "transaction_script";
305
306#[derive(Clone, Debug)]
314pub struct TransactionScript {
315 mast: Arc<MastForest>,
316 entrypoint: MastNodeId,
317 package_debug_info: Option<Arc<PackageDebugInfo>>,
318}
319
320impl TransactionScript {
321 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
329 assert!(mast.get_node_by_id(entrypoint).is_some());
330
331 Self {
332 mast,
333 entrypoint,
334 package_debug_info: None,
335 }
336 }
337
338 pub fn from_package(package: &Package) -> Result<Self, TransactionScriptError> {
352 if package.is_program() {
353 let program =
354 package.try_into_program().map_err(TransactionScriptError::PackageNotProgram)?;
355
356 return Ok(Self {
357 mast: program.mast_forest().clone(),
358 entrypoint: program.entrypoint(),
359 package_debug_info: package_debug_info(package),
360 });
361 }
362
363 let mut entrypoint = None;
364
365 for export in package.manifest.exports() {
366 if let Some(proc_export) = export.as_procedure()
367 && proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE)
368 {
369 if entrypoint.is_some() {
370 return Err(TransactionScriptError::MultipleProceduresWithAttribute);
371 }
372 entrypoint =
373 Some(proc_export.node.ok_or(TransactionScriptError::NoProcedureWithAttribute)?);
374 }
375 }
376
377 let entrypoint = entrypoint.ok_or(TransactionScriptError::NoProcedureWithAttribute)?;
378
379 Ok(Self {
380 mast: package.mast_forest().clone(),
381 entrypoint,
382 package_debug_info: package_debug_info(package),
383 })
384 }
385
386 pub fn from_package_reference(
404 package: &Package,
405 path: &Path,
406 ) -> Result<Self, TransactionScriptError> {
407 let export =
409 package.manifest.exports().find(|e| e.path().as_ref() == path).ok_or_else(|| {
410 TransactionScriptError::ProcedureNotFound(path.to_string().into())
411 })?;
412
413 let proc_export = export
415 .as_procedure()
416 .ok_or_else(|| TransactionScriptError::ProcedureNotFound(path.to_string().into()))?;
417
418 if !proc_export.attributes.has(TRANSACTION_SCRIPT_ATTRIBUTE) {
419 return Err(TransactionScriptError::ProcedureMissingAttribute(path.to_string().into()));
420 }
421
422 let digest = proc_export.digest;
424
425 let (mast, entrypoint) = create_external_node_forest(digest);
427
428 Ok(Self {
429 mast: Arc::new(mast),
430 entrypoint,
431 package_debug_info: package_debug_info(package),
432 })
433 }
434
435 pub fn mast(&self) -> Arc<MastForest> {
440 self.mast.clone()
441 }
442
443 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
445 loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
446 }
447
448 pub fn root(&self) -> TransactionScriptRoot {
450 TransactionScriptRoot::from_raw(self.mast[self.entrypoint].digest())
451 }
452
453 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
459 if advice_map.is_empty() {
460 return self;
461 }
462
463 let mast = (*self.mast).clone().with_advice_map(advice_map);
464 Self {
465 mast: Arc::new(mast),
466 entrypoint: self.entrypoint,
467 package_debug_info: self.package_debug_info,
468 }
469 }
470}
471
472impl PartialEq for TransactionScript {
473 fn eq(&self, other: &Self) -> bool {
474 self.mast == other.mast && self.entrypoint == other.entrypoint
475 }
476}
477
478impl Eq for TransactionScript {}
479
480impl Serializable for TransactionScript {
484 fn write_into<W: ByteWriter>(&self, target: &mut W) {
485 self.mast.write_into(target);
486 target.write_u32(u32::from(self.entrypoint));
487 }
488}
489
490impl Deserializable for TransactionScript {
491 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
492 let mast = MastForest::read_from(source)?;
493 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
494
495 Ok(Self::from_parts(Arc::new(mast), entrypoint))
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use miden_core::advice::AdviceMap;
502
503 use crate::transaction::TransactionArgs;
504 use crate::utils::serde::{Deserializable, Serializable};
505
506 #[test]
507 fn test_tx_args_serialization() {
508 let tx_args = TransactionArgs::new(AdviceMap::default());
509 let bytes: std::vec::Vec<u8> = tx_args.to_bytes();
510 let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
511
512 assert_eq!(tx_args, decoded);
513 }
514
515 #[test]
516 fn test_transaction_script_preserves_package_debug_info() {
517 use super::TransactionScript;
518 use crate::assembly::Assembler;
519
520 let assembler = Assembler::default();
521 let package =
522 assembler.assemble_program("test-transaction-script", "begin nop end").unwrap();
523 let script = TransactionScript::from_package(&package).unwrap();
524
525 assert!(script.loaded_mast_forest().package_debug_info().unwrap().is_some());
526 }
527
528 #[test]
529 fn test_transaction_script_with_advice_map() {
530 use miden_core::{Felt, Word};
531
532 use super::TransactionScript;
533 use crate::assembly::Assembler;
534
535 let assembler = Assembler::default();
536 let package =
537 assembler.assemble_program("test-transaction-script", "begin nop end").unwrap();
538 let script = TransactionScript::from_package(&package).unwrap();
539 assert!(script.mast().advice_map().is_empty());
540
541 let original_root = script.root();
543 let script = script.with_advice_map(AdviceMap::default());
544 assert_eq!(original_root, script.root());
545
546 let key = Word::from([1u32, 2, 3, 4]);
548 let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
549 let mut advice_map = AdviceMap::default();
550 advice_map.insert(key, value.clone());
551
552 let script = script.with_advice_map(advice_map);
553
554 let mast = script.mast();
555 let stored = mast.advice_map().get(&key).expect("entry should be present");
556 assert_eq!(stored.as_ref(), value.as_slice());
557 }
558
559 #[test]
560 fn test_transaction_script_from_library_package() {
561 use assert_matches::assert_matches;
562
563 use super::TransactionScript;
564 use crate::errors::TransactionScriptError;
565 use crate::testing::assembler::assemble_test_package;
566 use crate::utils::serde::{Deserializable, Serializable};
567
568 let source = "
569 @transaction_script
570 pub proc main
571 push.1 drop
572 end
573 ";
574 let package = assemble_test_package("test-tx-script", "test::tx_script", source);
575
576 let script = TransactionScript::from_package(&package).unwrap();
577
578 let bytes = script.to_bytes();
580 let decoded = TransactionScript::read_from_bytes(&bytes).unwrap();
581 assert_eq!(script, decoded);
582
583 let no_attr = assemble_test_package(
585 "test-tx-script-no-attr",
586 "test::tx_script_no_attr",
587 "pub proc main push.1 drop end",
588 );
589 assert_matches!(
590 TransactionScript::from_package(&no_attr),
591 Err(TransactionScriptError::NoProcedureWithAttribute)
592 );
593
594 let multiple = assemble_test_package(
596 "test-tx-script-multiple",
597 "test::tx_script_multiple",
598 "@transaction_script pub proc main_a push.1 drop end
599 @transaction_script pub proc main_b push.2 drop end",
600 );
601 assert_matches!(
602 TransactionScript::from_package(&multiple),
603 Err(TransactionScriptError::MultipleProceduresWithAttribute)
604 );
605 }
606
607 #[test]
608 fn test_transaction_script_from_package_reference() {
609 use alloc::string::ToString;
610
611 use assert_matches::assert_matches;
612
613 use super::TransactionScript;
614 use crate::Word;
615 use crate::assembly::Path;
616 use crate::errors::TransactionScriptError;
617 use crate::testing::assembler::assemble_test_package;
618
619 let source = "
620 @transaction_script
621 pub proc main_a
622 push.1 drop
623 end
624
625 @transaction_script
626 pub proc main_b
627 push.2 drop
628 end
629
630 pub proc helper
631 push.3 drop
632 end
633 ";
634 let package =
635 assemble_test_package("test-tx-script-reference", "test::tx_script_reference", source);
636
637 for proc_name in ["main_a", "main_b"] {
640 let export = package
641 .manifest
642 .exports()
643 .find(|e| e.path().as_ref().to_string().ends_with(proc_name))
644 .unwrap();
645 let digest = export.as_procedure().unwrap().digest;
646
647 let script =
648 TransactionScript::from_package_reference(&package, export.path().as_ref())
649 .unwrap();
650 assert_eq!(Word::from(script.root()), digest);
651 }
652
653 assert_matches!(
655 TransactionScript::from_package_reference(&package, Path::new("::foo::bar::main")),
656 Err(TransactionScriptError::ProcedureNotFound(_))
657 );
658
659 let helper = package
661 .manifest
662 .exports()
663 .find(|e| e.path().as_ref().to_string().ends_with("helper"))
664 .unwrap();
665 assert_matches!(
666 TransactionScript::from_package_reference(&package, helper.path().as_ref()),
667 Err(TransactionScriptError::ProcedureMissingAttribute(_))
668 );
669 }
670}