Skip to main content

miden_protocol/transaction/kernel/
advice_inputs.rs

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