miden_lib/transaction/
inputs.rs

1use alloc::vec::Vec;
2
3use miden_objects::{
4    Digest, EMPTY_WORD, Felt, FieldElement, WORD_SIZE, Word, ZERO,
5    account::{AccountHeader, AccountId, PartialAccount},
6    block::AccountWitness,
7    crypto::merkle::InnerNodeInfo,
8    transaction::{InputNote, PartialBlockchain, TransactionArgs, TransactionInputs},
9    vm::AdviceInputs,
10};
11
12use super::TransactionKernel;
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(Default, Clone, Debug)]
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 inputs and arguments. This includes the initial account, an
27    /// optional account seed (required for new accounts), and the input note data, including
28    /// core note data + authentication paths all the way to the root of one of partial
29    /// blockchain peaks.
30    pub fn new(tx_inputs: &TransactionInputs, tx_args: &TransactionArgs) -> Self {
31        let mut inputs = TransactionAdviceInputs::default();
32        let kernel_version = 0; // TODO: replace with user input
33
34        inputs.build_stack(tx_inputs, tx_args, kernel_version);
35        inputs.add_kernel_commitments(kernel_version);
36        inputs.add_partial_blockchain(tx_inputs.blockchain());
37        inputs.add_input_notes(tx_inputs, tx_args);
38
39        // --- native account injection ---------------------------------------
40
41        let native_acc = PartialAccount::from(tx_inputs.account());
42        inputs.add_account(&native_acc);
43
44        // if a seed was provided, extend the map appropriately
45        if let Some(seed) = tx_inputs.account_seed() {
46            // ACCOUNT_ID |-> ACCOUNT_SEED
47            let account_id_key = build_account_id_key(native_acc.id());
48            inputs.add_map_entry(account_id_key, seed.to_vec());
49        }
50
51        // --- foreign account injection --------------------------------------
52
53        for foreign_acc in tx_args.foreign_account_inputs() {
54            inputs.add_account(foreign_acc.account());
55            inputs.add_account_witness(foreign_acc.witness());
56
57            // for foreign accounts, we need to insert the id to state mapping
58            // NOTE: keep this in sync with the start_foreign_context kernel procedure
59            let account_id_key = build_account_id_key(foreign_acc.id());
60            let header = AccountHeader::from(foreign_acc.account());
61
62            // ACCOUNT_ID |-> [ID_AND_NONCE, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT]
63            inputs.add_map_entry(account_id_key, header.as_elements());
64        }
65
66        // any extra user-supplied advice
67        inputs.extend(tx_args.advice_inputs().clone());
68
69        inputs
70    }
71
72    /// Converts these transaction advice inputs into the underlying advice inputs.
73    pub fn into_inner(self) -> AdviceInputs {
74        self.0
75    }
76
77    // MUTATORS
78    // --------------------------------------------------------------------------------------------
79
80    /// Extends these advice inputs with the provided advice inputs.
81    pub fn extend(&mut self, adv_inputs: AdviceInputs) {
82        self.0.extend(adv_inputs);
83    }
84
85    /// Extend the advice stack with the transaction inputs.
86    ///
87    /// The following data is pushed to the advice stack:
88    ///
89    /// [
90    ///     PARENT_BLOCK_COMMITMENT,
91    ///     PARTIAL_BLOCKCHAIN_COMMITMENT,
92    ///     ACCOUNT_ROOT,
93    ///     NULLIFIER_ROOT,
94    ///     TX_COMMITMENT,
95    ///     TX_KERNEL_COMMITMENT
96    ///     PROOF_COMMITMENT,
97    ///     [block_num, version, timestamp, 0],
98    ///     NOTE_ROOT,
99    ///     kernel_version
100    ///     [account_id, 0, 0, account_nonce],
101    ///     ACCOUNT_VAULT_ROOT,
102    ///     ACCOUNT_STORAGE_COMMITMENT,
103    ///     ACCOUNT_CODE_COMMITMENT,
104    ///     number_of_input_notes,
105    ///     TX_SCRIPT_ROOT,
106    ///     TX_SCRIPT_ARGS_KEY,
107    /// ]
108    fn build_stack(
109        &mut self,
110        tx_inputs: &TransactionInputs,
111        tx_args: &TransactionArgs,
112        kernel_version: u8,
113    ) {
114        let header = tx_inputs.block_header();
115
116        // --- block header data (keep in sync with kernel's process_block_data) --
117        self.extend_stack(header.prev_block_commitment());
118        self.extend_stack(header.chain_commitment());
119        self.extend_stack(header.account_root());
120        self.extend_stack(header.nullifier_root());
121        self.extend_stack(header.tx_commitment());
122        self.extend_stack(header.tx_kernel_commitment());
123        self.extend_stack(header.proof_commitment());
124        self.extend_stack([
125            header.block_num().into(),
126            header.version().into(),
127            header.timestamp().into(),
128            ZERO,
129        ]);
130        self.extend_stack(header.note_root());
131
132        // --- kernel version (keep in sync with process_kernel_data) ---------
133        self.extend_stack([Felt::from(kernel_version)]);
134
135        // --- core account items (keep in sync with process_account_data) ----
136        let account = tx_inputs.account();
137        self.extend_stack([
138            account.id().suffix(),
139            account.id().prefix().as_felt(),
140            ZERO,
141            account.nonce(),
142        ]);
143        self.extend_stack(account.vault().root());
144        self.extend_stack(account.storage().commitment());
145        self.extend_stack(account.code().commitment());
146
147        // --- number of notes, script root and args key ----------------------
148        self.extend_stack([Felt::from(tx_inputs.input_notes().num_notes())]);
149        self.extend_stack(tx_args.tx_script().map_or(Word::default(), |script| *script.root()));
150        self.extend_stack(tx_args.tx_script_arg());
151    }
152
153    // BLOCKCHAIN INJECTIONS
154    // --------------------------------------------------------------------------------------------
155
156    /// Inserts the partial blockchain data into the provided advice inputs.
157    ///
158    /// Inserts the following items into the Merkle store:
159    /// - Inner nodes of all authentication paths contained in the partial blockchain.
160    ///
161    /// Inserts the following data to the advice map:
162    ///
163    /// > {MMR_ROOT: [[num_blocks, 0, 0, 0], PEAK_1, ..., PEAK_N]}
164    ///
165    /// Where:
166    /// - MMR_ROOT, is the sequential hash of the padded MMR peaks
167    /// - num_blocks, is the number of blocks in the MMR.
168    /// - PEAK_1 .. PEAK_N, are the MMR peaks.
169    fn add_partial_blockchain(&mut self, mmr: &PartialBlockchain) {
170        // NOTE: keep this code in sync with the `process_chain_data` kernel procedure
171        // add authentication paths from the MMR to the Merkle store
172        self.extend_merkle_store(mmr.inner_nodes());
173
174        // insert MMR peaks info into the advice map
175        let peaks = mmr.peaks();
176        let mut elements = vec![Felt::new(peaks.num_leaves() as u64), ZERO, ZERO, ZERO];
177        elements.extend(peaks.flatten_and_pad_peaks());
178        self.add_map_entry(peaks.hash_peaks(), elements);
179    }
180
181    // KERNEL INJECTIONS
182    // --------------------------------------------------------------------------------------------
183
184    /// Inserts kernel commitments and hashes of their procedures into the advice inputs.
185    ///
186    /// Inserts the following entries into the advice map:
187    /// - The accumulative hash of all kernels |-> array of each kernel commitment.
188    /// - The hash of the selected kernel |-> array of the kernel's procedure roots.
189    fn add_kernel_commitments(&mut self, kernel_version: u8) {
190        const NUM_KERNELS: usize = TransactionKernel::NUM_VERSIONS;
191
192        // insert kernels root with kernel commitments into the advice map
193        let mut kernel_commitments: Vec<Felt> = Vec::with_capacity(NUM_KERNELS * WORD_SIZE);
194        for version in 0..NUM_KERNELS {
195            let kernel_commitment = TransactionKernel::commitment(version as u8);
196            kernel_commitments.extend_from_slice(kernel_commitment.as_elements());
197        }
198        self.add_map_entry(TransactionKernel::kernel_commitment(), kernel_commitments);
199
200        // insert the selected kernel commitment with its procedure roots into the advice map
201        self.add_map_entry(
202            TransactionKernel::commitment(kernel_version),
203            TransactionKernel::procedures_as_elements(kernel_version),
204        );
205    }
206
207    // ACCOUNT INJECTION
208    // --------------------------------------------------------------------------------------------
209
210    /// Inserts account data into the advice inputs.
211    ///
212    /// Inserts the following items into the Merkle store:
213    /// - The Merkle nodes associated with the account vault tree.
214    /// - If present, the Merkle nodes associated with the account storage maps.
215    ///
216    /// Inserts the following entries into the advice map:
217    /// - The account storage commitment |-> storage slots and types vector.
218    /// - The account code commitment |-> procedures vector.
219    /// - The leaf hash |-> (key, value), for all leaves of the partial vault.
220    /// - If present, the Merkle leaves associated with the account storage maps.
221    fn add_account(&mut self, account: &PartialAccount) {
222        // --- account code -------------------------------------------------------
223
224        // CODE_COMMITMENT -> [[ACCOUNT_PROCEDURE_DATA]]
225        let code = account.code();
226        self.add_map_entry(code.commitment(), code.as_elements());
227
228        // --- account storage ----------------------------------------------------
229
230        // STORAGE_COMMITMENT |-> [[STORAGE_SLOT_DATA]]
231        let storage_header = account.storage().header();
232        self.add_map_entry(storage_header.compute_commitment(), storage_header.as_elements());
233
234        // populate Merkle store and advice map with nodes info needed to access storage map entries
235        self.extend_merkle_store(account.storage().inner_nodes());
236        self.extend_map(account.storage().leaves().map(|leaf| (leaf.hash(), leaf.to_elements())));
237
238        // --- account vault ------------------------------------------------------
239
240        // populate Merkle store and advice map with nodes info needed to access vault assets
241        self.extend_merkle_store(account.vault().inner_nodes());
242        self.extend_map(account.vault().leaves().map(|leaf| (leaf.hash(), leaf.to_elements())));
243    }
244
245    /// Adds an account witness to the advice inputs.
246    ///
247    /// This involves extending the map to include the leaf's hash mapped to its elements, as well
248    /// as extending the merkle store with the nodes of the witness.
249    fn add_account_witness(&mut self, witness: &AccountWitness) {
250        // populate advice map with the account's leaf
251        let leaf = witness.leaf();
252        self.add_map_entry(leaf.hash(), leaf.to_elements());
253
254        // extend the merkle store and map with account witnesses merkle path
255        self.extend_merkle_store(witness.inner_nodes());
256    }
257
258    // NOTE INJECTION
259    // --------------------------------------------------------------------------------------------
260
261    /// Populates the advice inputs for all input notes.
262    ///
263    /// The advice provider is populated with:
264    ///
265    /// - For each note:
266    ///     - The note's details (serial number, script root, and its input / assets commitment).
267    ///     - The note's private arguments.
268    ///     - The note's public metadata.
269    ///     - The note's public inputs data. Prefixed by its length and padded to an even word
270    ///       length.
271    ///     - The note's asset padded. Prefixed by its length and padded to an even word length.
272    ///     - For authenticated notes (determined by the `is_authenticated` flag):
273    ///         - The note's authentication path against its block's note tree.
274    ///         - The block number, sub commitment, note root.
275    ///         - The note's position in the note tree
276    ///
277    /// The data above is processed by `prologue::process_input_notes_data`.
278    fn add_input_notes(&mut self, tx_inputs: &TransactionInputs, tx_args: &TransactionArgs) {
279        if tx_inputs.input_notes().is_empty() {
280            return;
281        }
282
283        let mut note_data = Vec::new();
284        for input_note in tx_inputs.input_notes().iter() {
285            let note = input_note.note();
286            let assets = note.assets();
287            let recipient = note.recipient();
288            let note_arg = tx_args.get_note_args(note.id()).unwrap_or(&EMPTY_WORD);
289
290            // recipient inputs / assets commitments
291            self.add_map_entry(
292                recipient.inputs().commitment(),
293                recipient.inputs().format_for_advice(),
294            );
295            self.add_map_entry(assets.commitment(), assets.to_padded_assets());
296
297            // note details / metadata
298            note_data.extend(recipient.serial_num());
299            note_data.extend(*recipient.script().root());
300            note_data.extend(*recipient.inputs().commitment());
301            note_data.extend(*assets.commitment());
302            note_data.extend(Word::from(*note_arg));
303            note_data.extend(Word::from(note.metadata()));
304            note_data.push(recipient.inputs().num_values().into());
305            note_data.push((assets.num_assets() as u32).into());
306            note_data.extend(assets.to_padded_assets());
307
308            // authentication vs unauthenticated
309            match input_note {
310                InputNote::Authenticated { note, proof } => {
311                    // Push the `is_authenticated` flag
312                    note_data.push(Felt::ONE);
313
314                    // Merkle path
315                    self.extend_merkle_store(proof.inner_nodes(note.commitment()));
316
317                    let block_num = proof.location().block_num();
318                    let block_header = if block_num == tx_inputs.block_header().block_num() {
319                        tx_inputs.block_header()
320                    } else {
321                        tx_inputs
322                            .blockchain()
323                            .get_block(block_num)
324                            .expect("block not found in partial blockchain")
325                    };
326
327                    note_data.push(block_num.into());
328                    note_data.extend(block_header.sub_commitment());
329                    note_data.extend(block_header.note_root());
330                    note_data.push(proof.location().node_index_in_block().into());
331                },
332                InputNote::Unauthenticated { .. } => {
333                    // push the `is_authenticated` flag
334                    note_data.push(Felt::ZERO)
335                },
336            }
337        }
338
339        self.add_map_entry(tx_inputs.input_notes().commitment(), note_data);
340    }
341
342    // HELPER METHODS
343    // --------------------------------------------------------------------------------------------
344
345    /// Extends the map of values with the given argument, replacing previously inserted items.
346    fn extend_map(&mut self, iter: impl IntoIterator<Item = (Digest, Vec<Felt>)>) {
347        self.0.extend_map(iter);
348    }
349
350    fn add_map_entry(&mut self, key: Digest, values: Vec<Felt>) {
351        self.0.extend_map([(key, values)]);
352    }
353
354    /// Extends the stack with the given elements.
355    fn extend_stack(&mut self, iter: impl IntoIterator<Item = Felt>) {
356        self.0.extend_stack(iter);
357    }
358
359    /// Extends the [`MerkleStore`](miden_objects::crypto::merkle::MerkleStore) with the given
360    /// nodes.
361    fn extend_merkle_store(&mut self, iter: impl Iterator<Item = InnerNodeInfo>) {
362        self.0.extend_merkle_store(iter);
363    }
364}
365
366// CONVERSIONS
367// ================================================================================================
368
369impl From<TransactionAdviceInputs> for AdviceInputs {
370    fn from(wrapper: TransactionAdviceInputs) -> Self {
371        wrapper.0
372    }
373}
374
375impl From<AdviceInputs> for TransactionAdviceInputs {
376    fn from(inner: AdviceInputs) -> Self {
377        Self(inner)
378    }
379}
380
381// HELPER FUNCTIONS
382// ================================================================================================
383
384fn build_account_id_key(id: AccountId) -> Digest {
385    Digest::from([id.suffix(), id.prefix().as_felt(), ZERO, ZERO])
386}