Skip to main content

miden_protocol/transaction/
tx_args.rs

1use alloc::collections::BTreeMap;
2use alloc::string::String;
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;
11
12use super::{Felt, Hasher, Word};
13use crate::account::auth::{PublicKeyCommitment, Signature};
14use crate::errors::TransactionScriptError;
15use crate::note::{NoteId, NoteRecipient};
16use crate::utils::serde::{
17    ByteReader,
18    ByteWriter,
19    Deserializable,
20    DeserializationError,
21    Serializable,
22};
23use crate::vm::{AdviceInputs, AdviceMap, Program};
24use crate::{EMPTY_WORD, MastForest, MastNodeId};
25
26// TRANSACTION ARGUMENTS
27// ================================================================================================
28
29/// Optional transaction arguments.
30///
31/// - Transaction script: a program that is executed in a transaction after all input notes scripts
32///   have been executed.
33/// - Transaction script arguments: a [`Word`], which will be pushed to the operand stack before the
34///   transaction script execution. If these arguments are not specified, the [`EMPTY_WORD`] would
35///   be used as a default value. If the [AdviceInputs] are propagated with some user defined map
36///   entries, this script arguments word could be used as a key to access the corresponding value.
37/// - Note arguments: data put onto the stack right before a note script is executed. These are
38///   different from note storage, as the user executing the transaction can specify arbitrary note
39///   args.
40/// - Advice inputs: provides data needed by the runtime, like the details of public output notes.
41/// - Foreign account inputs: provides foreign account data that will be used during the foreign
42///   procedure invocation (FPI).
43/// - Auth arguments: data put onto the stack right before authentication procedure execution. If
44///   this argument is not specified, the [`EMPTY_WORD`] would be used as a default value. If the
45///   [AdviceInputs] are propagated with some user defined map entries, this argument could be used
46///   as a key to access the corresponding value.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct TransactionArgs {
49    tx_script: Option<TransactionScript>,
50    tx_script_args: Word,
51    note_args: BTreeMap<NoteId, Word>,
52    advice_inputs: AdviceInputs,
53    auth_args: Word,
54}
55
56impl TransactionArgs {
57    // CONSTRUCTORS
58    // --------------------------------------------------------------------------------------------
59
60    /// Returns new [TransactionArgs] instantiated with the provided transaction script, advice
61    /// map and foreign account inputs.
62    pub fn new(advice_map: AdviceMap) -> Self {
63        let advice_inputs = AdviceInputs { map: advice_map, ..Default::default() };
64
65        Self {
66            tx_script: None,
67            tx_script_args: EMPTY_WORD,
68            note_args: Default::default(),
69            advice_inputs,
70            auth_args: EMPTY_WORD,
71        }
72    }
73
74    /// Returns new [TransactionArgs] instantiated with the provided transaction script.
75    ///
76    /// If the transaction script is already set, it will be overwritten with the newly provided
77    /// one.
78    #[must_use]
79    pub fn with_tx_script(mut self, tx_script: TransactionScript) -> Self {
80        self.tx_script = Some(tx_script);
81        self
82    }
83
84    /// Returns new [TransactionArgs] instantiated with the provided transaction script and its
85    /// arguments.
86    ///
87    /// If the transaction script and arguments are already set, they will be overwritten with the
88    /// newly provided ones.
89    #[must_use]
90    pub fn with_tx_script_and_args(
91        mut self,
92        tx_script: TransactionScript,
93        tx_script_args: Word,
94    ) -> Self {
95        self.tx_script = Some(tx_script);
96        self.tx_script_args = tx_script_args;
97        self
98    }
99
100    /// Returns new [TransactionArgs] instantiated with the provided note arguments.
101    ///
102    /// If the note arguments were already set, they will be overwritten with the newly provided
103    /// ones.
104    #[must_use]
105    pub fn with_note_args(mut self, note_args: BTreeMap<NoteId, Word>) -> Self {
106        self.note_args = note_args;
107        self
108    }
109
110    /// Returns new [TransactionArgs] instantiated with the provided auth arguments.
111    #[must_use]
112    pub fn with_auth_args(mut self, auth_args: Word) -> Self {
113        self.auth_args = auth_args;
114        self
115    }
116
117    // PUBLIC ACCESSORS
118    // --------------------------------------------------------------------------------------------
119
120    /// Returns a reference to the transaction script.
121    pub fn tx_script(&self) -> Option<&TransactionScript> {
122        self.tx_script.as_ref()
123    }
124
125    /// Returns the transaction script arguments, or [`EMPTY_WORD`] if the arguments were not
126    /// specified.
127    ///
128    /// These arguments could be potentially used as a key to access the advice map during the
129    /// transaction script execution. Notice that the corresponding map entry should be provided
130    /// separately during the creation with the [`TransactionArgs::new`] or using the
131    /// [`TransactionArgs::extend_advice_map`] method.
132    pub fn tx_script_args(&self) -> Word {
133        self.tx_script_args
134    }
135
136    /// Returns a reference to a specific note argument.
137    pub fn get_note_args(&self, note_id: NoteId) -> Option<&Word> {
138        self.note_args.get(&note_id)
139    }
140
141    /// Returns a reference to the internal [AdviceInputs].
142    pub fn advice_inputs(&self) -> &AdviceInputs {
143        &self.advice_inputs
144    }
145
146    /// Returns a reference to the authentication procedure argument, or [`EMPTY_WORD`] if the
147    /// argument was not specified.
148    ///
149    /// This argument could be potentially used as a key to access the advice map during the
150    /// transaction script execution. Notice that the corresponding map entry should be provided
151    /// separately during the creation with the [`TransactionArgs::new`] or using the
152    /// [`TransactionArgs::extend_advice_map`] method.
153    pub fn auth_args(&self) -> Word {
154        self.auth_args
155    }
156
157    // STATE MUTATORS
158    // --------------------------------------------------------------------------------------------
159
160    /// Populates the advice inputs with the expected recipient data for creating output notes.
161    ///
162    /// The advice inputs' map is extended with the following entries:
163    /// - RECIPIENT: [SERIAL_SCRIPT_HASH, STORAGE_COMMITMENT]
164    /// - SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT]
165    /// - SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD]
166    /// - storage_commitment |-> storage_items.
167    /// - script_root |-> script.
168    pub fn add_output_note_recipient<T: AsRef<NoteRecipient>>(&mut self, note_recipient: T) {
169        let note_recipient = note_recipient.as_ref();
170        let storage = note_recipient.storage();
171        let script = note_recipient.script();
172        let script_encoded: Vec<Felt> = script.into();
173
174        // Build the advice map entries
175        let script_root: Word = script.root().into();
176        let sn_hash = Hasher::merge(&[note_recipient.serial_num(), Word::empty()]);
177        let sn_script_hash = Hasher::merge(&[sn_hash, script_root]);
178
179        let new_elements = vec![
180            (sn_hash, concat_words(note_recipient.serial_num(), Word::empty())),
181            (sn_script_hash, concat_words(sn_hash, script_root)),
182            (note_recipient.digest(), concat_words(sn_script_hash, storage.commitment())),
183            (storage.commitment(), storage.to_elements()),
184            (script_root, script_encoded),
185        ];
186
187        self.advice_inputs.extend(AdviceInputs::default().with_map(new_elements));
188    }
189
190    /// Adds the `signature` corresponding to `pub_key` on `message` to the advice inputs' map.
191    ///
192    /// The advice inputs' map is extended with the following key:
193    ///
194    /// - hash(pub_key, message) |-> signature (prepared for VM execution).
195    pub fn add_signature(
196        &mut self,
197        pub_key: PublicKeyCommitment,
198        message: Word,
199        signature: Signature,
200    ) {
201        let pk_word: Word = pub_key.into();
202        self.advice_inputs
203            .map
204            .insert(Hasher::merge(&[pk_word, message]), signature.to_prepared_signature(message));
205    }
206
207    /// Populates the advice inputs with the specified note recipient details.
208    ///
209    /// The advice inputs' map is extended with the following keys:
210    ///
211    /// - recipient |-> recipient details (inputs_hash, script_root, serial_num).
212    /// - storage_commitment |-> storage_items.
213    /// - script_root |-> script.
214    pub fn extend_output_note_recipients<T, L>(&mut self, notes: L)
215    where
216        L: IntoIterator<Item = T>,
217        T: AsRef<NoteRecipient>,
218    {
219        for note in notes {
220            self.add_output_note_recipient(note);
221        }
222    }
223
224    /// Extends the internal advice inputs' map with the provided key-value pairs.
225    pub fn extend_advice_map<T: IntoIterator<Item = (Word, Vec<Felt>)>>(&mut self, iter: T) {
226        self.advice_inputs.map.extend(iter);
227    }
228
229    /// Extends the internal advice inputs' merkle store with the provided nodes.
230    pub fn extend_merkle_store<I: Iterator<Item = InnerNodeInfo>>(&mut self, iter: I) {
231        self.advice_inputs.store.extend(iter);
232    }
233
234    /// Extends the advice inputs in self with the provided ones.
235    pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) {
236        self.advice_inputs.extend(advice_inputs);
237    }
238}
239
240/// Concatenates two [`Word`]s into a [`Vec<Felt>`] containing 8 elements.
241fn concat_words(first: Word, second: Word) -> Vec<Felt> {
242    let mut result = Vec::with_capacity(8);
243    result.extend(first);
244    result.extend(second);
245    result
246}
247
248impl Default for TransactionArgs {
249    fn default() -> Self {
250        Self::new(AdviceMap::default())
251    }
252}
253
254impl Serializable for TransactionArgs {
255    fn write_into<W: ByteWriter>(&self, target: &mut W) {
256        self.tx_script.write_into(target);
257        self.tx_script_args.write_into(target);
258        self.note_args.write_into(target);
259        self.advice_inputs.write_into(target);
260        self.auth_args.write_into(target);
261    }
262}
263
264impl Deserializable for TransactionArgs {
265    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
266        let tx_script = Option::<TransactionScript>::read_from(source)?;
267        let tx_script_args = Word::read_from(source)?;
268        let note_args = BTreeMap::<NoteId, Word>::read_from(source)?;
269        let advice_inputs = AdviceInputs::read_from(source)?;
270        let auth_args = Word::read_from(source)?;
271
272        Ok(Self {
273            tx_script,
274            tx_script_args,
275            note_args,
276            advice_inputs,
277            auth_args,
278        })
279    }
280}
281
282// TRANSACTION SCRIPT ROOT
283// ================================================================================================
284
285/// The MAST root of a [`TransactionScript`].
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
287pub struct TransactionScriptRoot(Word);
288
289impl From<TransactionScriptRoot> for Word {
290    fn from(root: TransactionScriptRoot) -> Self {
291        root.0
292    }
293}
294
295impl Display for TransactionScriptRoot {
296    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
297        Display::fmt(&self.0, f)
298    }
299}
300
301impl Serializable for TransactionScriptRoot {
302    fn write_into<W: ByteWriter>(&self, target: &mut W) {
303        target.write(self.0);
304    }
305
306    fn get_size_hint(&self) -> usize {
307        self.0.get_size_hint()
308    }
309}
310
311impl Deserializable for TransactionScriptRoot {
312    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
313        let word: Word = source.read()?;
314        Ok(Self::from_raw(word))
315    }
316}
317
318// TRANSACTION SCRIPT
319// ================================================================================================
320
321/// Transaction script.
322///
323/// A transaction script is a program that is executed in a transaction after all input notes
324/// have been executed.
325///
326/// The [TransactionScript] object is composed of an executable program defined by a [MastForest]
327/// and an associated entrypoint.
328#[derive(Clone, Debug, PartialEq, Eq)]
329pub struct TransactionScript {
330    mast: Arc<MastForest>,
331    entrypoint: MastNodeId,
332}
333
334impl TransactionScript {
335    // CONSTRUCTORS
336    // --------------------------------------------------------------------------------------------
337
338    /// Returns a new [TransactionScript] instantiated with the provided code.
339    pub fn new(code: Program) -> Self {
340        Self::from_parts(code.mast_forest().clone(), code.entrypoint())
341    }
342
343    /// Returns a new [TransactionScript] instantiated from the provided MAST forest and entrypoint.
344    ///
345    /// # Panics
346    /// Panics if the specified entrypoint is not in the provided MAST forest.
347    pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
348        assert!(mast.get_node_by_id(entrypoint).is_some());
349
350        Self { mast, entrypoint }
351    }
352
353    /// Creates a [TransactionScript] from a [`Package`].
354    ///
355    /// The package must be an executable (i.e., its target type must be
356    /// [`TargetType::Executable`](miden_mast_package::TargetType::Executable)).
357    ///
358    /// # Errors
359    /// Returns an error if the package cannot be converted to an executable program.
360    pub fn from_package(package: &Package) -> Result<Self, TransactionScriptError> {
361        let program =
362            package.try_into_program().map_err(TransactionScriptError::PackageNotProgram)?;
363
364        Ok(TransactionScript::new(program))
365    }
366
367    // PUBLIC ACCESSORS
368    // --------------------------------------------------------------------------------------------
369
370    /// Returns a reference to the [MastForest] backing this transaction script.
371    pub fn mast(&self) -> Arc<MastForest> {
372        self.mast.clone()
373    }
374
375    /// Returns the commitment of this transaction script (i.e., the script's MAST root).
376    pub fn root(&self) -> TransactionScriptRoot {
377        TransactionScriptRoot::from_raw(self.mast[self.entrypoint].digest())
378    }
379
380    /// Returns a new [TransactionScript] with the provided advice map entries merged into the
381    /// underlying [MastForest].
382    ///
383    /// This allows adding advice map entries to an already-compiled transaction script,
384    /// which is useful when the entries are determined after script compilation.
385    pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
386        if advice_map.is_empty() {
387            return self;
388        }
389
390        let mut mast = (*self.mast).clone();
391        mast.advice_map_mut().extend(advice_map);
392        Self {
393            mast: Arc::new(mast),
394            entrypoint: self.entrypoint,
395        }
396    }
397}
398
399// SERIALIZATION
400// ================================================================================================
401
402impl Serializable for TransactionScript {
403    fn write_into<W: ByteWriter>(&self, target: &mut W) {
404        self.mast.write_into(target);
405        target.write_u32(u32::from(self.entrypoint));
406    }
407}
408
409impl Deserializable for TransactionScript {
410    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
411        let mast = MastForest::read_from(source)?;
412        let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
413
414        Ok(Self::from_parts(Arc::new(mast), entrypoint))
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use miden_core::advice::AdviceMap;
421
422    use crate::transaction::TransactionArgs;
423    use crate::utils::serde::{Deserializable, Serializable};
424
425    #[test]
426    fn test_tx_args_serialization() {
427        let tx_args = TransactionArgs::new(AdviceMap::default());
428        let bytes: std::vec::Vec<u8> = tx_args.to_bytes();
429        let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
430
431        assert_eq!(tx_args, decoded);
432    }
433
434    #[test]
435    fn test_transaction_script_with_advice_map() {
436        use miden_core::{Felt, Word};
437
438        use super::TransactionScript;
439        use crate::assembly::Assembler;
440
441        let assembler = Assembler::default();
442        let program = assembler.assemble_program("begin nop end").unwrap();
443        let script = TransactionScript::new(program);
444
445        assert!(script.mast().advice_map().is_empty());
446
447        // Empty advice map should be a no-op
448        let original_root = script.root();
449        let script = script.with_advice_map(AdviceMap::default());
450        assert_eq!(original_root, script.root());
451
452        // Non-empty advice map should add entries
453        let key = Word::from([1u32, 2, 3, 4]);
454        let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
455        let mut advice_map = AdviceMap::default();
456        advice_map.insert(key, value.clone());
457
458        let script = script.with_advice_map(advice_map);
459
460        let mast = script.mast();
461        let stored = mast.advice_map().get(&key).expect("entry should be present");
462        assert_eq!(stored.as_ref(), value.as_slice());
463    }
464}