miden_objects/transaction/
tx_args.rs

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