miden_protocol/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 storage, 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(¬e_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, STORAGE_COMMITMENT]
159 /// - SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT]
160 /// - SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD]
161 /// - storage_commitment |-> storage_items.
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 storage = note_recipient.storage();
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, storage.commitment())),
177 (storage.commitment(), storage.to_elements()),
178 (script.root(), script_encoded),
179 ];
180
181 self.advice_inputs.extend(AdviceInputs::default().with_map(new_elements));
182 }
183
184 /// Adds the `signature` corresponding to `pub_key` on `message` to the advice inputs' map.
185 ///
186 /// The advice inputs' map is extended with the following key:
187 ///
188 /// - hash(pub_key, message) |-> signature (prepared for VM execution).
189 pub fn add_signature(
190 &mut self,
191 pub_key: PublicKeyCommitment,
192 message: Word,
193 signature: Signature,
194 ) {
195 let pk_word: Word = pub_key.into();
196 self.advice_inputs
197 .map
198 .insert(Hasher::merge(&[pk_word, message]), signature.to_prepared_signature(message));
199 }
200
201 /// Populates the advice inputs with the specified note recipient details.
202 ///
203 /// The advice inputs' map is extended with the following keys:
204 ///
205 /// - recipient |-> recipient details (inputs_hash, script_root, serial_num).
206 /// - storage_commitment |-> storage_items.
207 /// - script_root |-> script.
208 pub fn extend_output_note_recipients<T, L>(&mut self, notes: L)
209 where
210 L: IntoIterator<Item = T>,
211 T: AsRef<NoteRecipient>,
212 {
213 for note in notes {
214 self.add_output_note_recipient(note);
215 }
216 }
217
218 /// Extends the internal advice inputs' map with the provided key-value pairs.
219 pub fn extend_advice_map<T: IntoIterator<Item = (Word, Vec<Felt>)>>(&mut self, iter: T) {
220 self.advice_inputs.map.extend(iter);
221 }
222
223 /// Extends the internal advice inputs' merkle store with the provided nodes.
224 pub fn extend_merkle_store<I: Iterator<Item = InnerNodeInfo>>(&mut self, iter: I) {
225 self.advice_inputs.store.extend(iter);
226 }
227
228 /// Extends the advice inputs in self with the provided ones.
229 pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) {
230 self.advice_inputs.extend(advice_inputs);
231 }
232}
233
234/// Concatenates two [`Word`]s into a [`Vec<Felt>`] containing 8 elements.
235fn concat_words(first: Word, second: Word) -> Vec<Felt> {
236 let mut result = Vec::with_capacity(8);
237 result.extend(first);
238 result.extend(second);
239 result
240}
241
242impl Default for TransactionArgs {
243 fn default() -> Self {
244 Self::new(AdviceMap::default())
245 }
246}
247
248impl Serializable for TransactionArgs {
249 fn write_into<W: ByteWriter>(&self, target: &mut W) {
250 self.tx_script.write_into(target);
251 self.tx_script_args.write_into(target);
252 self.note_args.write_into(target);
253 self.advice_inputs.write_into(target);
254 self.auth_args.write_into(target);
255 }
256}
257
258impl Deserializable for TransactionArgs {
259 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
260 let tx_script = Option::<TransactionScript>::read_from(source)?;
261 let tx_script_args = Word::read_from(source)?;
262 let note_args = BTreeMap::<NoteId, Word>::read_from(source)?;
263 let advice_inputs = AdviceInputs::read_from(source)?;
264 let auth_args = Word::read_from(source)?;
265
266 Ok(Self {
267 tx_script,
268 tx_script_args,
269 note_args,
270 advice_inputs,
271 auth_args,
272 })
273 }
274}
275
276// TRANSACTION SCRIPT
277// ================================================================================================
278
279/// Transaction script.
280///
281/// A transaction script is a program that is executed in a transaction after all input notes
282/// have been executed.
283///
284/// The [TransactionScript] object is composed of an executable program defined by a [MastForest]
285/// and an associated entrypoint.
286#[derive(Clone, Debug, PartialEq, Eq)]
287pub struct TransactionScript {
288 mast: Arc<MastForest>,
289 entrypoint: MastNodeId,
290}
291
292impl TransactionScript {
293 // CONSTRUCTORS
294 // --------------------------------------------------------------------------------------------
295
296 /// Returns a new [TransactionScript] instantiated with the provided code.
297 pub fn new(code: Program) -> Self {
298 Self::from_parts(code.mast_forest().clone(), code.entrypoint())
299 }
300
301 /// Returns a new [TransactionScript] instantiated from the provided MAST forest and entrypoint.
302 ///
303 /// # Panics
304 /// Panics if the specified entrypoint is not in the provided MAST forest.
305 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
306 assert!(mast.get_node_by_id(entrypoint).is_some());
307
308 Self { mast, entrypoint }
309 }
310
311 // PUBLIC ACCESSORS
312 // --------------------------------------------------------------------------------------------
313
314 /// Returns a reference to the [MastForest] backing this transaction script.
315 pub fn mast(&self) -> Arc<MastForest> {
316 self.mast.clone()
317 }
318
319 /// Returns the commitment of this transaction script (i.e., the script's MAST root).
320 pub fn root(&self) -> Word {
321 self.mast[self.entrypoint].digest()
322 }
323
324 /// Returns a new [TransactionScript] with the provided advice map entries merged into the
325 /// underlying [MastForest].
326 ///
327 /// This allows adding advice map entries to an already-compiled transaction script,
328 /// which is useful when the entries are determined after script compilation.
329 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
330 if advice_map.is_empty() {
331 return self;
332 }
333
334 let mut mast = (*self.mast).clone();
335 mast.advice_map_mut().extend(advice_map);
336 Self {
337 mast: Arc::new(mast),
338 entrypoint: self.entrypoint,
339 }
340 }
341}
342
343// SERIALIZATION
344// ================================================================================================
345
346impl Serializable for TransactionScript {
347 fn write_into<W: ByteWriter>(&self, target: &mut W) {
348 self.mast.write_into(target);
349 target.write_u32(u32::from(self.entrypoint));
350 }
351}
352
353impl Deserializable for TransactionScript {
354 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
355 let mast = MastForest::read_from(source)?;
356 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
357
358 Ok(Self::from_parts(Arc::new(mast), entrypoint))
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use miden_core::AdviceMap;
365 use miden_core::utils::{Deserializable, Serializable};
366
367 use crate::transaction::TransactionArgs;
368
369 #[test]
370 fn test_tx_args_serialization() {
371 let tx_args = TransactionArgs::new(AdviceMap::default());
372 let bytes: std::vec::Vec<u8> = tx_args.to_bytes();
373 let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
374
375 assert_eq!(tx_args, decoded);
376 }
377
378 #[test]
379 fn test_transaction_script_with_advice_map() {
380 use miden_core::{Felt, Word};
381
382 use super::TransactionScript;
383 use crate::assembly::Assembler;
384
385 let assembler = Assembler::default();
386 let program = assembler.assemble_program("begin nop end").unwrap();
387 let script = TransactionScript::new(program);
388
389 assert!(script.mast().advice_map().is_empty());
390
391 // Empty advice map should be a no-op
392 let original_root = script.root();
393 let script = script.with_advice_map(AdviceMap::default());
394 assert_eq!(original_root, script.root());
395
396 // Non-empty advice map should add entries
397 let key = Word::from([1u32, 2, 3, 4]);
398 let value = vec![Felt::new(42), Felt::new(43)];
399 let mut advice_map = AdviceMap::default();
400 advice_map.insert(key, value.clone());
401
402 let script = script.with_advice_map(advice_map);
403
404 let mast = script.mast();
405 let stored = mast.advice_map().get(&key).expect("entry should be present");
406 assert_eq!(stored.as_ref(), value.as_slice());
407 }
408}