Skip to main content

miden_protocol/transaction/kernel/
advice_inputs.rs

1use alloc::vec::Vec;
2
3use miden_processor::advice::AdviceMutation;
4
5use crate::account::PartialAccount;
6use crate::block::account_tree::{AccountIdKey, AccountWitness};
7use crate::crypto::SequentialCommit;
8use crate::crypto::merkle::InnerNodeInfo;
9use crate::protocol_config::ProtocolConfig;
10use crate::transaction::{AccountInputs, InputNote, PartialBlockchain, TransactionInputs};
11use crate::vm::AdviceInputs;
12use crate::{EMPTY_WORD, Felt, Word, ZERO};
13
14// TRANSACTION ADVICE INPUTS
15// ================================================================================================
16
17/// Advice inputs wrapper for inputs that are meant to be used exclusively in the transaction
18/// kernel.
19#[derive(Debug, Clone, Default)]
20pub struct TransactionAdviceInputs(AdviceInputs);
21
22impl TransactionAdviceInputs {
23    /// Creates a [`TransactionAdviceInputs`].
24    ///
25    /// The created advice inputs will be populated with the data required for executing a
26    /// transaction with the specified transaction inputs.
27    pub fn new(tx_inputs: &TransactionInputs) -> Self {
28        let mut inputs = TransactionAdviceInputs(tx_inputs.advice_inputs().clone());
29
30        inputs.build_stack(tx_inputs);
31        inputs.add_protocol_config(tx_inputs.protocol_config());
32        inputs.add_partial_blockchain(tx_inputs.blockchain());
33        inputs.add_input_notes(tx_inputs);
34
35        // Add the script's MAST forest's advice inputs.
36        if let Some(tx_script) = tx_inputs.tx_args().tx_script() {
37            inputs.extend_map(
38                tx_script
39                    .mast()
40                    .advice_map()
41                    .iter()
42                    .map(|(key, values)| (*key, values.to_vec())),
43            );
44        }
45
46        // Inject native account.
47        let partial_native_acc = tx_inputs.account();
48        inputs.add_account(partial_native_acc);
49
50        // If a seed was provided, extend the map appropriately.
51        if let Some(seed) = tx_inputs.account().seed() {
52            // ACCOUNT_ID |-> ACCOUNT_SEED
53            let account_id_key = AccountIdKey::from(partial_native_acc.id());
54            inputs.add_map_entry(account_id_key.as_word(), seed.to_vec());
55        }
56
57        // if the account is new, insert the storage map entries into the advice provider.
58        if partial_native_acc.is_new() {
59            for storage_map in partial_native_acc.storage().maps() {
60                let map_entries = storage_map
61                    .entries()
62                    .flat_map(|(key, value)| {
63                        value.as_elements().iter().chain(key.as_elements().iter()).copied()
64                    })
65                    .collect();
66                inputs.add_map_entry(storage_map.root(), map_entries);
67            }
68        }
69
70        // The host reads the new code from this entry when the kernel initializes the upgrade.
71        if let Some(code_upgrade) = tx_inputs.tx_args().account_code_upgrade() {
72            let (key, elements) = code_upgrade.to_advice_map_entry();
73            inputs.add_map_entry(key, elements);
74        }
75
76        // Extend with extra user-supplied advice.
77        inputs.extend(tx_inputs.tx_args().advice_inputs().clone());
78
79        inputs
80    }
81
82    /// Returns a reference to the underlying advice inputs.
83    pub fn as_advice_inputs(&self) -> &AdviceInputs {
84        &self.0
85    }
86
87    /// Converts these transaction advice inputs into the underlying advice inputs.
88    pub fn into_advice_inputs(self) -> AdviceInputs {
89        self.0
90    }
91
92    /// Consumes self and returns an iterator of [`AdviceMutation`]s in arbitrary order.
93    pub fn into_advice_mutations(self) -> impl Iterator<Item = AdviceMutation> {
94        let (stack, map, store) = self.0.into_parts();
95        [
96            AdviceMutation::extend_map(map),
97            AdviceMutation::extend_merkle_store(store.inner_nodes()),
98            AdviceMutation::extend_advice_stack(stack),
99        ]
100        .into_iter()
101    }
102
103    // PUBLIC UTILITIES
104    // --------------------------------------------------------------------------------------------
105
106    // MUTATORS
107    // --------------------------------------------------------------------------------------------
108
109    /// Extends these advice inputs with the provided advice inputs.
110    pub fn extend(&mut self, adv_inputs: AdviceInputs) {
111        self.0.extend(adv_inputs);
112    }
113
114    /// Adds the provided account inputs into the advice inputs.
115    pub fn add_foreign_accounts<'inputs>(
116        &mut self,
117        foreign_account_inputs: impl IntoIterator<Item = &'inputs AccountInputs>,
118    ) {
119        for foreign_acc in foreign_account_inputs {
120            self.add_account(foreign_acc.account());
121            self.add_account_witness(foreign_acc.witness());
122
123            // for foreign accounts, we need to insert the id to state mapping
124            // NOTE: keep this in sync with the account::load_from_advice procedure
125            let account_id_key = AccountIdKey::from(foreign_acc.id());
126
127            // ACCOUNT_ID |-> [ACCOUNT_METADATA, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT]
128            self.add_map_entry(account_id_key.as_word(), foreign_acc.account().to_elements());
129        }
130    }
131
132    /// Extend the advice stack with the transaction inputs.
133    ///
134    /// The following data is pushed to the advice stack (words shown in memory-order):
135    ///
136    /// [
137    ///     [version, block_num, timestamp, 0],
138    ///     PREV_BLOCK_COMMITMENT,
139    ///     CHAIN_COMMITMENT,
140    ///     ACCOUNT_ROOT,
141    ///     NULLIFIER_ROOT,
142    ///     TX_COMMITMENT,
143    ///     PROTOCOL_CONFIG_COMMITMENT,
144    ///     VALIDATOR_CONFIG_COMMITMENT,
145    ///     NEXT_PROTOCOL_CONFIG_COMMITMENT,
146    ///     [verification_base_fee, 0, 0, 0],
147    ///     NOTE_ROOT,
148    ///     [account_version, account_nonce, account_id_suffix, account_id_prefix],
149    ///     ACCOUNT_VAULT_ROOT,
150    ///     ACCOUNT_STORAGE_COMMITMENT,
151    ///     ACCOUNT_CODE_COMMITMENT,
152    ///     number_of_input_notes,
153    ///     TX_SCRIPT_ROOT,
154    ///     TX_SCRIPT_ARGS,
155    ///     AUTH_ARGS,
156    /// ]
157    fn build_stack(&mut self, tx_inputs: &TransactionInputs) {
158        // --- block header data (keep in sync with kernel's process_block_data) --
159        self.extend_stack(tx_inputs.block_header().to_elements());
160
161        // --- core account items (keep in sync with process_account_data) ----
162        self.extend_stack(tx_inputs.account().to_elements());
163
164        // --- number of notes, script root and args --------------------------
165        self.extend_stack([Felt::from(tx_inputs.input_notes().num_notes())]);
166        let tx_args = tx_inputs.tx_args();
167        self.extend_stack(
168            tx_args.tx_script().map_or(Word::empty(), |script| script.root().as_word()),
169        );
170        self.extend_stack(tx_args.tx_script_args());
171
172        // --- auth procedure args --------------------------------------------
173        self.extend_stack(tx_args.auth_args());
174    }
175
176    // BLOCKCHAIN INJECTIONS
177    // --------------------------------------------------------------------------------------------
178
179    /// Inserts the partial blockchain data into the provided advice inputs.
180    ///
181    /// Inserts the following items into the Merkle store:
182    /// - Inner nodes of all authentication paths contained in the partial blockchain.
183    ///
184    /// Inserts the following data to the advice map:
185    ///
186    /// > {MMR_ROOT: [[num_blocks, 0, 0, 0], PEAK_1, ..., PEAK_N]}
187    ///
188    /// Where:
189    /// - MMR_ROOT, is the sequential hash of the padded MMR peaks
190    /// - num_blocks, is the number of blocks in the MMR.
191    /// - PEAK_1 .. PEAK_N, are the MMR peaks.
192    fn add_partial_blockchain(&mut self, mmr: &PartialBlockchain) {
193        // NOTE: keep this code in sync with the `process_chain_data` kernel procedure
194        // add authentication paths from the MMR to the Merkle store
195        self.extend_merkle_store(mmr.inner_nodes());
196
197        // insert MMR peaks info into the advice map
198        let peaks = mmr.peaks();
199        let num_leaves = Felt::try_from(peaks.num_leaves() as u64)
200            .expect("number of blocks in chain should not exceed BlockNumber::MAX");
201        let mut elements = vec![num_leaves, ZERO, ZERO, ZERO];
202        elements.extend(peaks.flatten_and_pad_peaks());
203        self.add_map_entry(peaks.hash_peaks(), elements);
204    }
205
206    // PROTOCOL CONFIG INJECTIONS
207    // --------------------------------------------------------------------------------------------
208
209    /// Inserts the protocol configuration into the advice map.
210    ///
211    /// The block header only commits to the configuration, so the kernel resolves these commitments
212    /// to their preimage:
213    /// - PROTOCOL_CONFIG_COMMITMENT |-> the protocol config elements.
214    /// - TX_KERNEL_CONFIG_COMMITMENT |-> the transaction kernel config elements.
215    /// - TX_KERNEL_PROCS_COMMITMENT |-> the array of the transaction kernel's procedure roots.
216    ///
217    /// NOTE: keep this in sync with the `process_protocol_config` and `process_kernel_data` kernel
218    /// procedures.
219    fn add_protocol_config(&mut self, protocol_config: &ProtocolConfig) {
220        let tx_kernel = protocol_config.tx_kernel();
221
222        self.add_map_entry(protocol_config.to_commitment(), protocol_config.to_elements());
223        self.add_map_entry(tx_kernel.to_commitment(), tx_kernel.to_elements());
224        self.add_map_entry(
225            tx_kernel.kernel_procs_commitment(),
226            tx_kernel.kernel_procs_elements().to_vec(),
227        );
228    }
229
230    // ACCOUNT INJECTION
231    // --------------------------------------------------------------------------------------------
232
233    /// Inserts account data into the advice inputs.
234    ///
235    /// Inserts the following items into the Merkle store:
236    /// - The Merkle nodes associated with the account vault tree.
237    /// - If present, the Merkle nodes associated with the account storage maps.
238    ///
239    /// Inserts the following entries into the advice map:
240    /// - The account storage commitment |-> storage slots and types vector.
241    /// - The account code commitment |-> procedures vector.
242    /// - The leaf hash |-> (key, value), for all leaves of the partial vault.
243    /// - If present, the Merkle leaves associated with the account storage maps.
244    fn add_account(&mut self, account: &PartialAccount) {
245        // --- account code -------------------------------------------------------
246
247        // CODE_COMMITMENT -> [[ACCOUNT_PROCEDURE_DATA]]
248        let code = account.code();
249        self.add_map_entry(code.commitment(), code.to_elements());
250
251        // --- account storage ----------------------------------------------------
252
253        // STORAGE_COMMITMENT |-> [[STORAGE_SLOT_DATA]]
254        let storage_header = account.storage().header();
255        self.add_map_entry(storage_header.to_commitment(), storage_header.to_elements());
256
257        // populate Merkle store and advice map with nodes info needed to access storage map entries
258        self.extend_merkle_store(account.storage().inner_nodes());
259        self.extend_map(
260            account
261                .storage()
262                .leaves()
263                .map(|leaf| (leaf.hash(), leaf.to_elements().collect())),
264        );
265
266        // --- account vault ------------------------------------------------------
267
268        // populate Merkle store and advice map with nodes info needed to access vault assets
269        self.extend_merkle_store(account.vault().inner_nodes());
270        self.extend_map(
271            account.vault().leaves().map(|leaf| (leaf.hash(), leaf.to_elements().collect())),
272        );
273    }
274
275    /// Adds an account witness to the advice inputs.
276    ///
277    /// This involves extending the map to include the leaf's hash mapped to its elements, as well
278    /// as extending the merkle store with the nodes of the witness.
279    fn add_account_witness(&mut self, witness: &AccountWitness) {
280        // populate advice map with the account's leaf
281        let leaf = witness.leaf();
282        self.add_map_entry(leaf.hash(), leaf.to_elements().collect());
283
284        // extend the merkle store and map with account witnesses merkle path
285        self.extend_merkle_store(witness.authenticated_nodes());
286    }
287
288    // NOTE INJECTION
289    // --------------------------------------------------------------------------------------------
290
291    /// Populates the advice inputs for all input notes.
292    ///
293    /// The advice provider is populated with:
294    ///
295    /// - For each note:
296    ///     - The note's private arguments.
297    ///     - The note's details (serial number, script root, and its storage / assets commitment).
298    ///     - The preimages of the note recipient's hash chain.
299    ///     - The note's public metadata (sender account ID, note type, note tag, attachment
300    ///       schemes).
301    ///     - The note's storage (unpadded).
302    ///     - The note's assets (key and value words).
303    ///     - For authenticated notes (determined by the `is_authenticated` flag):
304    ///         - The note's authentication path against its block's note tree.
305    ///         - The block number, sub commitment, note root.
306    ///         - The note's position in the note tree
307    ///
308    /// The data above is processed by `prologue::process_input_notes_data`.
309    fn add_input_notes(&mut self, tx_inputs: &TransactionInputs) {
310        if tx_inputs.input_notes().is_empty() {
311            return;
312        }
313
314        let mut note_data = Vec::new();
315        for input_note in tx_inputs.input_notes().iter() {
316            let note = input_note.note();
317            let assets = note.assets();
318            let recipient = note.recipient();
319            let note_arg = tx_inputs.tx_args().get_note_args(note.id()).unwrap_or(&EMPTY_WORD);
320
321            // recipient chain entries
322            self.extend_map(recipient.to_advice_map_entries());
323            // assets commitments
324            self.add_map_entry(assets.commitment(), assets.to_elements());
325
326            // ATTACHMENTS_COMMITMENT |-> [[ATTACHMENT_COMMITMENTS]]
327            self.add_map_entry(
328                note.attachments().to_commitment(),
329                note.attachments()
330                    .commitments()
331                    .iter()
332                    .flat_map(Word::as_elements)
333                    .copied()
334                    .collect(),
335            );
336
337            // ATTACHMENT_COMMITMENT |-> [ATTACHMENT_ELEMENTS] for each attachment
338            for attachment in note.attachments().iter() {
339                let commitment = attachment.content().to_commitment();
340                let elements = attachment.content().to_elements();
341                self.add_map_entry(commitment, elements);
342            }
343
344            // note metadata / details
345            note_data.extend(*note_arg);
346            note_data.extend(recipient.serial_num());
347            note_data.extend(Word::from(recipient.script().root()));
348            note_data.extend(*recipient.storage().commitment());
349            note_data.extend(*assets.commitment());
350            note_data.extend(note.metadata().to_metadata_word());
351            note_data.extend(note.attachments().to_commitment());
352            note_data.push(Felt::from(recipient.storage().num_items()));
353            note_data.push(Felt::from(assets.num_assets() as u32));
354            note_data.extend(assets.to_elements());
355
356            // authentication vs unauthenticated
357            match input_note {
358                InputNote::Authenticated { note, proof } => {
359                    // Push the `is_authenticated` flag
360                    note_data.push(Felt::ONE);
361
362                    // Merkle path
363                    self.extend_merkle_store(proof.authenticated_nodes(note.id()));
364
365                    let block_num = proof.location().block_num();
366                    let block_header = if block_num == tx_inputs.block_header().block_num() {
367                        tx_inputs.block_header()
368                    } else {
369                        tx_inputs
370                            .blockchain()
371                            .get_block(block_num)
372                            .expect("block not found in partial blockchain")
373                    };
374
375                    note_data.push(block_num.into());
376                    note_data.extend(block_header.sub_commitment());
377                    note_data.extend(block_header.note_root());
378                    note_data.push(Felt::from(proof.location().block_note_tree_index()));
379                },
380                InputNote::Unauthenticated { .. } => {
381                    // push the `is_authenticated` flag
382                    note_data.push(Felt::ZERO)
383                },
384            }
385        }
386
387        self.add_map_entry(tx_inputs.input_notes().commitment(), note_data);
388    }
389
390    // HELPER METHODS
391    // --------------------------------------------------------------------------------------------
392
393    /// Extends the map of values with the given argument, replacing previously inserted items.
394    fn extend_map(&mut self, iter: impl IntoIterator<Item = (Word, Vec<Felt>)>) {
395        self.0.extend(AdviceInputs::default().with_map(iter));
396    }
397
398    fn add_map_entry(&mut self, key: Word, values: Vec<Felt>) {
399        self.0.extend(AdviceInputs::default().with_map([(key, values)]));
400    }
401
402    /// Extends the stack with the given elements.
403    fn extend_stack(&mut self, iter: impl IntoIterator<Item = Felt>) {
404        // `AdviceInputs` exposes its stack only as a typed `AdviceStack`, so appending goes
405        // through `extend`, which appends the other instance's stack elements to ours.
406        self.0.extend(AdviceInputs::default().with_stack(iter.into_iter().collect()));
407    }
408
409    /// Extends the [`MerkleStore`](crate::crypto::merkle::MerkleStore) with the given
410    /// nodes.
411    fn extend_merkle_store(&mut self, iter: impl Iterator<Item = InnerNodeInfo>) {
412        self.0.extend(AdviceInputs::default().with_merkle_store(iter.collect()));
413    }
414}
415
416// CONVERSIONS
417// ================================================================================================
418
419impl From<TransactionAdviceInputs> for AdviceInputs {
420    fn from(wrapper: TransactionAdviceInputs) -> Self {
421        wrapper.0
422    }
423}
424
425impl From<AdviceInputs> for TransactionAdviceInputs {
426    fn from(inner: AdviceInputs) -> Self {
427        Self(inner)
428    }
429}