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