miden_objects/transaction/
tx_args.rs

1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2use core::ops::Deref;
3
4use assembly::{Assembler, Compile};
5use miden_crypto::merkle::InnerNodeInfo;
6
7use super::{Digest, Felt, Word};
8use crate::{
9    MastForest, MastNodeId, TransactionScriptError,
10    note::{NoteDetails, NoteId},
11    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
12    vm::{AdviceInputs, AdviceMap, Program},
13};
14
15// TRANSACTION ARGS
16// ================================================================================================
17
18/// Optional transaction arguments.
19///
20/// - Transaction script: a program that is executed in a transaction after all input notes scripts
21///   have been executed.
22/// - Note arguments: data put onto the stack right before a note script is executed. These are
23///   different from note inputs, as the user executing the transaction can specify arbitrary note
24///   args.
25/// - Advice inputs: Provides data needed by the runtime, like the details of public output notes.
26#[derive(Clone, Debug, Default, PartialEq, Eq)]
27pub struct TransactionArgs {
28    tx_script: Option<TransactionScript>,
29    note_args: BTreeMap<NoteId, Word>,
30    advice_inputs: AdviceInputs,
31}
32
33impl TransactionArgs {
34    // CONSTRUCTORS
35    // --------------------------------------------------------------------------------------------
36
37    /// Returns new [TransactionArgs] instantiated with the provided transaction script and note
38    /// arguments.
39    ///
40    /// If tx_script is provided, this also adds all mappings from the transaction script inputs
41    /// to the advice inputs' map.
42    pub fn new(
43        tx_script: Option<TransactionScript>,
44        note_args: Option<BTreeMap<NoteId, Word>>,
45        advice_map: AdviceMap,
46    ) -> Self {
47        let mut advice_inputs = AdviceInputs::default().with_map(advice_map);
48        // add transaction script inputs to the advice inputs' map
49        if let Some(ref tx_script) = tx_script {
50            advice_inputs
51                .extend_map(tx_script.inputs().iter().map(|(hash, input)| (*hash, input.clone())))
52        }
53
54        Self {
55            tx_script,
56            note_args: note_args.unwrap_or_default(),
57            advice_inputs,
58        }
59    }
60
61    /// Returns new [TransactionArgs] instantiated with the provided transaction script.
62    pub fn with_tx_script(tx_script: TransactionScript) -> Self {
63        Self::new(Some(tx_script), Some(BTreeMap::default()), AdviceMap::default())
64    }
65
66    /// Returns new [TransactionArgs] instantiated with the provided note arguments.
67    pub fn with_note_args(note_args: BTreeMap<NoteId, Word>) -> Self {
68        Self::new(None, Some(note_args), AdviceMap::default())
69    }
70
71    /// Returns the provided [TransactionArgs] with advice inputs extended with the passed-in
72    /// `advice_inputs`.
73    pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
74        self.advice_inputs.extend(advice_inputs);
75        self
76    }
77
78    // PUBLIC ACCESSORS
79    // --------------------------------------------------------------------------------------------
80
81    /// Returns a reference to the transaction script.
82    pub fn tx_script(&self) -> Option<&TransactionScript> {
83        self.tx_script.as_ref()
84    }
85
86    /// Returns a reference to a specific note argument.
87    pub fn get_note_args(&self, note_id: NoteId) -> Option<&Word> {
88        self.note_args.get(&note_id)
89    }
90
91    /// Returns a reference to the args [AdviceInputs].
92    pub fn advice_inputs(&self) -> &AdviceInputs {
93        &self.advice_inputs
94    }
95
96    // STATE MUTATORS
97    // --------------------------------------------------------------------------------------------
98
99    /// Populates the advice inputs with the specified note details.
100    ///
101    /// The advice inputs' map is extended with the following keys:
102    ///
103    /// - recipient |-> recipient details (inputs_hash, script_root, serial_num).
104    /// - inputs_key |-> inputs, where inputs_key is computed by taking note inputs commitment and
105    ///   adding ONE to its most significant element.
106    /// - script_root |-> script.
107    pub fn add_expected_output_note<T: Deref<Target = NoteDetails>>(&mut self, note: &T) {
108        let recipient = note.recipient();
109        let inputs = note.inputs();
110        let script = note.script();
111        let script_encoded: Vec<Felt> = script.into();
112
113        let new_elements = [
114            (recipient.digest(), recipient.to_elements()),
115            (inputs.commitment(), inputs.format_for_advice()),
116            (script.root(), script_encoded),
117        ];
118
119        self.advice_inputs.extend_map(new_elements);
120    }
121
122    /// Populates the advice inputs with the specified note details.
123    ///
124    /// The advice inputs' map is extended with the following keys:
125    ///
126    /// - recipient |-> recipient details (inputs_hash, script_root, serial_num)
127    /// - inputs_key |-> inputs, where inputs_key is computed by taking note inputs commitment and
128    ///   adding ONE to its most significant element.
129    /// - script_root |-> script
130    pub fn extend_expected_output_notes<T, L>(&mut self, notes: L)
131    where
132        L: IntoIterator<Item = T>,
133        T: Deref<Target = NoteDetails>,
134    {
135        for note in notes {
136            self.add_expected_output_note(&note);
137        }
138    }
139
140    /// Extends the internal advice inputs' map with the provided key-value pairs.
141    pub fn extend_advice_map<T: IntoIterator<Item = (Digest, Vec<Felt>)>>(&mut self, iter: T) {
142        self.advice_inputs.extend_map(iter)
143    }
144
145    /// Extends the internal advice inputs' merkle store with the provided nodes.
146    pub fn extend_merkle_store<I: Iterator<Item = InnerNodeInfo>>(&mut self, iter: I) {
147        self.advice_inputs.extend_merkle_store(iter)
148    }
149}
150
151impl Serializable for TransactionArgs {
152    fn write_into<W: ByteWriter>(&self, target: &mut W) {
153        self.tx_script.write_into(target);
154        self.note_args.write_into(target);
155        self.advice_inputs.write_into(target);
156    }
157}
158
159impl Deserializable for TransactionArgs {
160    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
161        let tx_script = Option::<TransactionScript>::read_from(source)?;
162        let note_args = BTreeMap::<NoteId, Word>::read_from(source)?;
163        let advice_inputs = AdviceInputs::read_from(source)?;
164
165        Ok(Self { tx_script, note_args, advice_inputs })
166    }
167}
168
169// TRANSACTION SCRIPT
170// ================================================================================================
171
172/// Transaction script.
173///
174/// A transaction script is a program that is executed in a transaction after all input notes
175/// have been executed.
176///
177/// The [TransactionScript] object is composed of:
178/// - An executable program defined by a [MastForest] and an associated entrypoint.
179/// - A set of transaction script inputs defined by a map of key-value inputs that are loaded into
180///   the advice inputs' map such that the transaction script can access them.
181#[derive(Clone, Debug, PartialEq, Eq)]
182pub struct TransactionScript {
183    mast: Arc<MastForest>,
184    entrypoint: MastNodeId,
185    inputs: BTreeMap<Digest, Vec<Felt>>,
186}
187
188impl TransactionScript {
189    // CONSTRUCTORS
190    // --------------------------------------------------------------------------------------------
191
192    /// Returns a new [TransactionScript] instantiated with the provided code and inputs.
193    pub fn new(code: Program, inputs: impl IntoIterator<Item = (Word, Vec<Felt>)>) -> Self {
194        Self {
195            entrypoint: code.entrypoint(),
196            mast: code.mast_forest().clone(),
197            inputs: inputs.into_iter().map(|(k, v)| (k.into(), v)).collect(),
198        }
199    }
200
201    /// Returns a new [TransactionScript] compiled from the provided source code and inputs using
202    /// the specified assembler.
203    ///
204    /// # Errors
205    /// Returns an error if the compilation of the provided source code fails.
206    pub fn compile(
207        source_code: impl Compile,
208        inputs: impl IntoIterator<Item = (Word, Vec<Felt>)>,
209        assembler: Assembler,
210    ) -> Result<Self, TransactionScriptError> {
211        let program = assembler
212            .assemble_program(source_code)
213            .map_err(TransactionScriptError::AssemblyError)?;
214        Ok(Self::new(program, inputs))
215    }
216
217    /// Returns a new [TransactionScript] instantiated from the provided components.
218    ///
219    /// # Panics
220    /// Panics if the specified entrypoint is not in the provided MAST forest.
221    pub fn from_parts(
222        mast: Arc<MastForest>,
223        entrypoint: MastNodeId,
224        inputs: BTreeMap<Digest, Vec<Felt>>,
225    ) -> Self {
226        assert!(mast.get_node_by_id(entrypoint).is_some());
227        Self { mast, entrypoint, inputs }
228    }
229
230    // PUBLIC ACCESSORS
231    // --------------------------------------------------------------------------------------------
232
233    /// Returns a reference to the [MastForest] backing this transaction script.
234    pub fn mast(&self) -> Arc<MastForest> {
235        self.mast.clone()
236    }
237
238    /// Returns the commitment of this transaction script (i.e., the script's MAST root).
239    pub fn root(&self) -> Digest {
240        self.mast[self.entrypoint].digest()
241    }
242
243    /// Returns a reference to the inputs for this transaction script.
244    pub fn inputs(&self) -> &BTreeMap<Digest, Vec<Felt>> {
245        &self.inputs
246    }
247}
248
249// SERIALIZATION
250// ================================================================================================
251
252impl Serializable for TransactionScript {
253    fn write_into<W: ByteWriter>(&self, target: &mut W) {
254        self.mast.write_into(target);
255        target.write_u32(self.entrypoint.as_u32());
256        self.inputs.write_into(target);
257    }
258}
259
260impl Deserializable for TransactionScript {
261    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
262        let mast = MastForest::read_from(source)?;
263        let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
264        let inputs = BTreeMap::<Digest, Vec<Felt>>::read_from(source)?;
265
266        Ok(Self::from_parts(Arc::new(mast), entrypoint, inputs))
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use vm_core::{
273        AdviceMap,
274        utils::{Deserializable, Serializable},
275    };
276
277    use crate::transaction::TransactionArgs;
278
279    #[test]
280    fn test_tx_args_serialization() {
281        let args = TransactionArgs::new(None, None, AdviceMap::default());
282        let bytes: std::vec::Vec<u8> = args.to_bytes();
283        let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
284
285        assert_eq!(args, decoded);
286    }
287}