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}