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