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