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