miden_lib/transaction/inputs.rs
1use alloc::vec::Vec;
2
3use miden_objects::account::{AccountHeader, AccountId, PartialAccount};
4use miden_objects::block::AccountWitness;
5use miden_objects::crypto::merkle::InnerNodeInfo;
6use miden_objects::transaction::{
7 InputNote,
8 PartialBlockchain,
9 TransactionArgs,
10 TransactionInputs,
11};
12use miden_objects::vm::AdviceInputs;
13use miden_objects::{EMPTY_WORD, Felt, FieldElement, WORD_SIZE, Word, ZERO};
14use thiserror::Error;
15
16use super::TransactionKernel;
17
18// TRANSACTION ADVICE INPUTS
19// ================================================================================================
20
21/// Advice inputs wrapper for inputs that are meant to be used exclusively in the transaction
22/// kernel.
23#[derive(Default, Clone, Debug)]
24pub struct TransactionAdviceInputs(AdviceInputs);
25
26impl TransactionAdviceInputs {
27 /// Creates a [`TransactionAdviceInputs`].
28 ///
29 /// The created advice inputs will be populated with the data required for executing a
30 /// transaction with the specified inputs and arguments. This includes the initial account, an
31 /// optional account seed (required for new accounts), and the input note data, including
32 /// core note data + authentication paths all the way to the root of one of partial
33 /// blockchain peaks.
34 pub fn new(
35 tx_inputs: &TransactionInputs,
36 tx_args: &TransactionArgs,
37 ) -> Result<Self, TransactionAdviceMapMismatch> {
38 let mut inputs = TransactionAdviceInputs::default();
39 let kernel_version = 0; // TODO: replace with user input
40
41 inputs.build_stack(tx_inputs, tx_args, kernel_version);
42 inputs.add_kernel_commitments(kernel_version);
43 inputs.add_partial_blockchain(tx_inputs.blockchain());
44 inputs.add_input_notes(tx_inputs, tx_args)?;
45
46 // Add the script's MAST forest's advice inputs
47 if let Some(tx_script) = tx_args.tx_script() {
48 inputs.extend_map(
49 tx_script
50 .mast()
51 .advice_map()
52 .iter()
53 .map(|(key, values)| (*key, values.to_vec())),
54 );
55 }
56
57 // --- native account injection ---------------------------------------
58
59 let native_acc = PartialAccount::from(tx_inputs.account());
60 inputs.add_account(&native_acc)?;
61
62 // if a seed was provided, extend the map appropriately
63 if let Some(seed) = tx_inputs.account_seed() {
64 // ACCOUNT_ID |-> ACCOUNT_SEED
65 let account_id_key = build_account_id_key(native_acc.id());
66 inputs.add_map_entry(account_id_key, seed.to_vec());
67 }
68
69 // --- foreign account injection --------------------------------------
70
71 for foreign_acc in tx_args.foreign_account_inputs() {
72 inputs.add_account(foreign_acc.account())?;
73 inputs.add_account_witness(foreign_acc.witness());
74
75 // for foreign accounts, we need to insert the id to state mapping
76 // NOTE: keep this in sync with the start_foreign_context kernel procedure
77 let account_id_key = build_account_id_key(foreign_acc.id());
78 let header = AccountHeader::from(foreign_acc.account());
79
80 // ACCOUNT_ID |-> [ID_AND_NONCE, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT]
81 inputs.add_map_entry(account_id_key, header.as_elements());
82 }
83
84 // any extra user-supplied advice
85 inputs.extend(tx_args.advice_inputs().clone());
86
87 Ok(inputs)
88 }
89
90 /// Returns a reference to the underlying advice inputs.
91 pub fn as_advice_inputs(&self) -> &AdviceInputs {
92 &self.0
93 }
94
95 /// Converts these transaction advice inputs into the underlying advice inputs.
96 pub fn into_advice_inputs(self) -> AdviceInputs {
97 self.0
98 }
99
100 // MUTATORS
101 // --------------------------------------------------------------------------------------------
102
103 /// Extends these advice inputs with the provided advice inputs.
104 pub fn extend(&mut self, adv_inputs: AdviceInputs) {
105 self.0.extend(adv_inputs);
106 }
107
108 /// Extend the advice stack with the transaction inputs.
109 ///
110 /// The following data is pushed to the advice stack:
111 ///
112 /// [
113 /// PARENT_BLOCK_COMMITMENT,
114 /// PARTIAL_BLOCKCHAIN_COMMITMENT,
115 /// ACCOUNT_ROOT,
116 /// NULLIFIER_ROOT,
117 /// TX_COMMITMENT,
118 /// TX_KERNEL_COMMITMENT
119 /// PROOF_COMMITMENT,
120 /// [block_num, version, timestamp, 0],
121 /// [native_asset_id_suffix, native_asset_id_prefix, verification_base_fee, 0]
122 /// [0, 0, 0, 0]
123 /// NOTE_ROOT,
124 /// kernel_version
125 /// [account_id, 0, 0, account_nonce],
126 /// ACCOUNT_VAULT_ROOT,
127 /// ACCOUNT_STORAGE_COMMITMENT,
128 /// ACCOUNT_CODE_COMMITMENT,
129 /// number_of_input_notes,
130 /// TX_SCRIPT_ROOT,
131 /// TX_SCRIPT_ARGS,
132 /// AUTH_ARGS,
133 /// ]
134 fn build_stack(
135 &mut self,
136 tx_inputs: &TransactionInputs,
137 tx_args: &TransactionArgs,
138 kernel_version: u8,
139 ) {
140 let header = tx_inputs.block_header();
141
142 // --- block header data (keep in sync with kernel's process_block_data) --
143 self.extend_stack(header.prev_block_commitment());
144 self.extend_stack(header.chain_commitment());
145 self.extend_stack(header.account_root());
146 self.extend_stack(header.nullifier_root());
147 self.extend_stack(header.tx_commitment());
148 self.extend_stack(header.tx_kernel_commitment());
149 self.extend_stack(header.proof_commitment());
150 self.extend_stack([
151 header.block_num().into(),
152 header.version().into(),
153 header.timestamp().into(),
154 ZERO,
155 ]);
156 self.extend_stack([
157 header.fee_parameters().native_asset_id().suffix(),
158 header.fee_parameters().native_asset_id().prefix().as_felt(),
159 header.fee_parameters().verification_base_fee().into(),
160 ZERO,
161 ]);
162 self.extend_stack([ZERO, ZERO, ZERO, ZERO]);
163 self.extend_stack(header.note_root());
164
165 // --- kernel version (keep in sync with process_kernel_data) ---------
166 self.extend_stack([Felt::from(kernel_version)]);
167
168 // --- core account items (keep in sync with process_account_data) ----
169 let account = tx_inputs.account();
170 self.extend_stack([
171 account.id().suffix(),
172 account.id().prefix().as_felt(),
173 ZERO,
174 account.nonce(),
175 ]);
176 self.extend_stack(account.vault().root());
177 self.extend_stack(account.storage().commitment());
178 self.extend_stack(account.code().commitment());
179
180 // --- number of notes, script root and args --------------------------
181 self.extend_stack([Felt::from(tx_inputs.input_notes().num_notes())]);
182 self.extend_stack(tx_args.tx_script().map_or(Word::empty(), |script| script.root()));
183 self.extend_stack(tx_args.tx_script_args());
184
185 // --- auth procedure args --------------------------------------------
186 self.extend_stack(tx_args.auth_args());
187 }
188
189 // BLOCKCHAIN INJECTIONS
190 // --------------------------------------------------------------------------------------------
191
192 /// Inserts the partial blockchain data into the provided advice inputs.
193 ///
194 /// Inserts the following items into the Merkle store:
195 /// - Inner nodes of all authentication paths contained in the partial blockchain.
196 ///
197 /// Inserts the following data to the advice map:
198 ///
199 /// > {MMR_ROOT: [[num_blocks, 0, 0, 0], PEAK_1, ..., PEAK_N]}
200 ///
201 /// Where:
202 /// - MMR_ROOT, is the sequential hash of the padded MMR peaks
203 /// - num_blocks, is the number of blocks in the MMR.
204 /// - PEAK_1 .. PEAK_N, are the MMR peaks.
205 fn add_partial_blockchain(&mut self, mmr: &PartialBlockchain) {
206 // NOTE: keep this code in sync with the `process_chain_data` kernel procedure
207 // add authentication paths from the MMR to the Merkle store
208 self.extend_merkle_store(mmr.inner_nodes());
209
210 // insert MMR peaks info into the advice map
211 let peaks = mmr.peaks();
212 let mut elements = vec![Felt::new(peaks.num_leaves() as u64), ZERO, ZERO, ZERO];
213 elements.extend(peaks.flatten_and_pad_peaks());
214 self.add_map_entry(peaks.hash_peaks(), elements);
215 }
216
217 // KERNEL INJECTIONS
218 // --------------------------------------------------------------------------------------------
219
220 /// Inserts kernel commitments and hashes of their procedures into the advice inputs.
221 ///
222 /// Inserts the following entries into the advice map:
223 /// - The accumulative hash of all kernels |-> array of each kernel commitment.
224 /// - The hash of the selected kernel |-> array of the kernel's procedure roots.
225 fn add_kernel_commitments(&mut self, kernel_version: u8) {
226 const NUM_KERNELS: usize = TransactionKernel::NUM_VERSIONS;
227
228 // insert kernels root with kernel commitments into the advice map
229 let mut kernel_commitments: Vec<Felt> = Vec::with_capacity(NUM_KERNELS * WORD_SIZE);
230 for version in 0..NUM_KERNELS {
231 let kernel_commitment = TransactionKernel::commitment(version as u8);
232 kernel_commitments.extend_from_slice(kernel_commitment.as_elements());
233 }
234 self.add_map_entry(TransactionKernel::kernel_commitment(), kernel_commitments);
235
236 // insert the selected kernel commitment with its procedure roots into the advice map
237 self.add_map_entry(
238 TransactionKernel::commitment(kernel_version),
239 TransactionKernel::procedures_as_elements(kernel_version),
240 );
241 }
242
243 // ACCOUNT INJECTION
244 // --------------------------------------------------------------------------------------------
245
246 /// Inserts account data into the advice inputs.
247 ///
248 /// Inserts the following items into the Merkle store:
249 /// - The Merkle nodes associated with the account vault tree.
250 /// - If present, the Merkle nodes associated with the account storage maps.
251 ///
252 /// Inserts the following entries into the advice map:
253 /// - The account storage commitment |-> storage slots and types vector.
254 /// - The account code commitment |-> procedures vector.
255 /// - The leaf hash |-> (key, value), for all leaves of the partial vault.
256 /// - If present, the Merkle leaves associated with the account storage maps.
257 fn add_account(
258 &mut self,
259 account: &PartialAccount,
260 ) -> Result<(), TransactionAdviceMapMismatch> {
261 // --- account code -------------------------------------------------------
262
263 // CODE_COMMITMENT -> [[ACCOUNT_PROCEDURE_DATA]]
264 let code = account.code();
265 self.add_map_entry(code.commitment(), code.as_elements());
266
267 // Extend the advice map with the account code's advice inputs.
268 // This ensures that the advice map is available during the note script execution when it
269 // calls the account's code that relies on the it's advice map data (data segments) loaded
270 // into the advice provider
271 self.0.map.merge(account.code().mast().advice_map()).map_err(
272 |((key, existing_val), incoming_val)| TransactionAdviceMapMismatch {
273 key,
274 existing_val: existing_val.to_vec(),
275 incoming_val: incoming_val.to_vec(),
276 },
277 )?;
278
279 // --- account storage ----------------------------------------------------
280
281 // STORAGE_COMMITMENT |-> [[STORAGE_SLOT_DATA]]
282 let storage_header = account.storage().header();
283 self.add_map_entry(storage_header.compute_commitment(), storage_header.as_elements());
284
285 // populate Merkle store and advice map with nodes info needed to access storage map entries
286 self.extend_merkle_store(account.storage().inner_nodes());
287 self.extend_map(account.storage().leaves().map(|leaf| (leaf.hash(), leaf.to_elements())));
288
289 // --- account vault ------------------------------------------------------
290
291 // populate Merkle store and advice map with nodes info needed to access vault assets
292 self.extend_merkle_store(account.vault().inner_nodes());
293 self.extend_map(account.vault().leaves().map(|leaf| (leaf.hash(), leaf.to_elements())));
294
295 Ok(())
296 }
297
298 /// Adds an account witness to the advice inputs.
299 ///
300 /// This involves extending the map to include the leaf's hash mapped to its elements, as well
301 /// as extending the merkle store with the nodes of the witness.
302 fn add_account_witness(&mut self, witness: &AccountWitness) {
303 // populate advice map with the account's leaf
304 let leaf = witness.leaf();
305 self.add_map_entry(leaf.hash(), leaf.to_elements());
306
307 // extend the merkle store and map with account witnesses merkle path
308 self.extend_merkle_store(witness.authenticated_nodes());
309 }
310
311 // NOTE INJECTION
312 // --------------------------------------------------------------------------------------------
313
314 /// Populates the advice inputs for all input notes.
315 ///
316 /// The advice provider is populated with:
317 ///
318 /// - For each note:
319 /// - The note's details (serial number, script root, and its input / assets commitment).
320 /// - The note's private arguments.
321 /// - The note's public metadata.
322 /// - The note's public inputs data. Prefixed by its length and padded to an even word
323 /// length.
324 /// - The note's asset padded. Prefixed by its length and padded to an even word length.
325 /// - The note's script MAST forest's advice map inputs
326 /// - For authenticated notes (determined by the `is_authenticated` flag):
327 /// - The note's authentication path against its block's note tree.
328 /// - The block number, sub commitment, note root.
329 /// - The note's position in the note tree
330 ///
331 /// The data above is processed by `prologue::process_input_notes_data`.
332 fn add_input_notes(
333 &mut self,
334 tx_inputs: &TransactionInputs,
335 tx_args: &TransactionArgs,
336 ) -> Result<(), TransactionAdviceMapMismatch> {
337 if tx_inputs.input_notes().is_empty() {
338 return Ok(());
339 }
340
341 let mut note_data = Vec::new();
342 for input_note in tx_inputs.input_notes().iter() {
343 let note = input_note.note();
344 let assets = note.assets();
345 let recipient = note.recipient();
346 let note_arg = tx_args.get_note_args(note.id()).unwrap_or(&EMPTY_WORD);
347
348 // recipient inputs / assets commitments
349 self.add_map_entry(
350 recipient.inputs().commitment(),
351 recipient.inputs().format_for_advice(),
352 );
353 self.add_map_entry(assets.commitment(), assets.to_padded_assets());
354
355 // note details / metadata
356 note_data.extend(recipient.serial_num());
357 note_data.extend(*recipient.script().root());
358 note_data.extend(*recipient.inputs().commitment());
359 note_data.extend(*assets.commitment());
360 note_data.extend(*note_arg);
361 note_data.extend(Word::from(note.metadata()));
362 note_data.push(recipient.inputs().num_values().into());
363 note_data.push((assets.num_assets() as u32).into());
364 note_data.extend(assets.to_padded_assets());
365
366 // authentication vs unauthenticated
367 match input_note {
368 InputNote::Authenticated { note, proof } => {
369 // Push the `is_authenticated` flag
370 note_data.push(Felt::ONE);
371
372 // Merkle path
373 self.extend_merkle_store(proof.authenticated_nodes(note.commitment()));
374
375 let block_num = proof.location().block_num();
376 let block_header = if block_num == tx_inputs.block_header().block_num() {
377 tx_inputs.block_header()
378 } else {
379 tx_inputs
380 .blockchain()
381 .get_block(block_num)
382 .expect("block not found in partial blockchain")
383 };
384
385 note_data.push(block_num.into());
386 note_data.extend(block_header.sub_commitment());
387 note_data.extend(block_header.note_root());
388 note_data.push(proof.location().node_index_in_block().into());
389 },
390 InputNote::Unauthenticated { .. } => {
391 // push the `is_authenticated` flag
392 note_data.push(Felt::ZERO)
393 },
394 }
395
396 self.0.map.merge(note.script().mast().advice_map()).map_err(
397 |((key, existing_val), incoming_val)| TransactionAdviceMapMismatch {
398 key,
399 existing_val: existing_val.to_vec(),
400 incoming_val: incoming_val.to_vec(),
401 },
402 )?;
403 }
404
405 self.add_map_entry(tx_inputs.input_notes().commitment(), note_data);
406 Ok(())
407 }
408
409 // HELPER METHODS
410 // --------------------------------------------------------------------------------------------
411
412 /// Extends the map of values with the given argument, replacing previously inserted items.
413 fn extend_map(&mut self, iter: impl IntoIterator<Item = (Word, Vec<Felt>)>) {
414 self.0.map.extend(iter);
415 }
416
417 fn add_map_entry(&mut self, key: Word, values: Vec<Felt>) {
418 self.0.map.extend([(key, values)]);
419 }
420
421 /// Extends the stack with the given elements.
422 fn extend_stack(&mut self, iter: impl IntoIterator<Item = Felt>) {
423 self.0.stack.extend(iter);
424 }
425
426 /// Extends the [`MerkleStore`](miden_objects::crypto::merkle::MerkleStore) with the given
427 /// nodes.
428 fn extend_merkle_store(&mut self, iter: impl Iterator<Item = InnerNodeInfo>) {
429 self.0.store.extend(iter);
430 }
431}
432
433// CONVERSIONS
434// ================================================================================================
435
436impl From<TransactionAdviceInputs> for AdviceInputs {
437 fn from(wrapper: TransactionAdviceInputs) -> Self {
438 wrapper.0
439 }
440}
441
442impl From<AdviceInputs> for TransactionAdviceInputs {
443 fn from(inner: AdviceInputs) -> Self {
444 Self(inner)
445 }
446}
447
448// HELPER FUNCTIONS
449// ================================================================================================
450
451fn build_account_id_key(id: AccountId) -> Word {
452 Word::from([id.suffix(), id.prefix().as_felt(), ZERO, ZERO])
453}
454
455// CONFLICT ERROR
456// ================================================================================================
457
458#[derive(Debug, Error)]
459#[error(
460 "conflicting map entry for key {key}: existing={existing_val:?}, incoming={incoming_val:?}"
461)]
462pub struct TransactionAdviceMapMismatch {
463 pub key: Word,
464 pub existing_val: Vec<Felt>,
465 pub incoming_val: Vec<Felt>,
466}