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