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