Skip to main content

miden_protocol/transaction/
tx_args.rs

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