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